From 7d8125d8b2a7a37874f6df50ae5f63a64daa80f9 Mon Sep 17 00:00:00 2001 From: Jurj-Bogdan Date: Thu, 6 Jun 2024 19:04:41 +0300 Subject: [PATCH 01/27] commands documentation pages Signed-off-by: arhimede --- docs/book/v4/commands/create-admin-account.md | 38 ++++++++++ .../commands/display-available-endpoints.md | 72 +++++++++++++++++++ .../commands/generate-database-migrations.md | 64 +++++++++++++++++ docs/book/v4/commands/generate-tokens.md | 64 +++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 docs/book/v4/commands/create-admin-account.md create mode 100644 docs/book/v4/commands/display-available-endpoints.md create mode 100644 docs/book/v4/commands/generate-database-migrations.md create mode 100644 docs/book/v4/commands/generate-tokens.md diff --git a/docs/book/v4/commands/create-admin-account.md b/docs/book/v4/commands/create-admin-account.md new file mode 100644 index 0000000..8b7337e --- /dev/null +++ b/docs/book/v4/commands/create-admin-account.md @@ -0,0 +1,38 @@ +# Creating admin accounts in DotKernel API + +## Usage + +Run the following command in your application’s root directory: +```shell +php ./bin/cli.php admin:create -i {IDENTITY} -p {PASSWORD} -f {FIRST_NAME} -l {LAST_NAME} +``` + +OR + +```shell +php ./bin/cli.php admin:create --identity {IDENTITY} --password {PASSWORD} --firstName {FIRST_NAME} --lastName {LAST_NAME} +``` + +after replacing: + +* {IDENTITY} with a valid username OR email address +* {PASSWORD} with a valid password +* {FIRST_NAME} and {LAST_NAME} with valid names + +**NOTE:** + +* if the specified fields contain special characters, make sure you surround them with double quote signs +* this method does not allow specifying an admin role – newly created accounts will have role of admin + +If the submitted data is valid, the outputted response is: + +```text +Admin account has been created. +``` +The new admin account is ready to use. + +You can get more help with this command by running: + +```shell +php ./bin/cli.php help admin:create +``` diff --git a/docs/book/v4/commands/display-available-endpoints.md b/docs/book/v4/commands/display-available-endpoints.md new file mode 100644 index 0000000..9ff8c27 --- /dev/null +++ b/docs/book/v4/commands/display-available-endpoints.md @@ -0,0 +1,72 @@ +# Displaying DotKernel API endpoints using dot-cli + +## Usage + +Run the following command in your application’s root directory: + +```shell +php ./bin/cli.php route:list +``` + +The command runs through all routes and extracts endpoint information in realtime. +The output should be similar to the following: + +```text ++--------+---------------------------------+--------------------------------+ +| Method | Name | Path | ++--------+---------------------------------+--------------------------------+ +| DELETE | admin.delete | /admin/{uuid} | +| DELETE | user.my-account.delete | /user/my-account | +| DELETE | user.my-avatar.delete | /user/my-avatar | +| DELETE | user.delete | /user/{uuid} | +| DELETE | user.avatar.delete | /user/{uuid}/avatar | +| GET | home | / | +| GET | account.reset-password.validate | /account/reset-password/{hash} | +| GET | admin.list | /admin | +| GET | admin.my-account.view | /admin/my-account | +| GET | admin.role.list | /admin/role | +| GET | admin.role.view | /admin/role/{uuid} | +| GET | admin.view | /admin/{uuid} | +| GET | user.list | /user | +| GET | user.my-account.view | /user/my-account | +| GET | user.my-avatar.view | /user/my-avatar | +| GET | user.role.list | /user/role | +| GET | user.role.view | /user/role/{uuid} | +| GET | user.view | /user/{uuid} | +| GET | user.avatar.view | /user/{uuid}/avatar | +| PATCH | account.activate | /account/activate/{hash} | +| PATCH | account.modify-password | /account/reset-password/{hash} | +| PATCH | admin.my-account.update | /admin/my-account | +| PATCH | admin.update | /admin/{uuid} | +| PATCH | user.my-account.update | /user/my-account | +| PATCH | user.update | /user/{uuid} | +| POST | account.activate.request | /account/activate | +| POST | account.recover-identity | /account/recover-identity | +| POST | account.register | /account/register | +| POST | account.reset-password.request | /account/reset-password | +| POST | admin.create | /admin | +| POST | error.report | /error-report | +| POST | security.generate-token | /security/generate-token | +| POST | security.refresh-token | /security/refresh-token | +| POST | user.create | /user | +| POST | user.my-avatar.create | /user/my-avatar | +| POST | user.activate | /user/{uuid}/activate | +| POST | user.avatar.create | /user/{uuid}/avatar | ++--------+---------------------------------+--------------------------------+ +``` + +## Filtering results + +The following filters can be applied when displaying the routes list: + +* Filter routes by name, using: `-i|--name[=NAME]` +* Filter routes by path, using: `-p|--path[=PATH]` +* Filter routes by method, using: `-m|--method[=METHOD]` + +The filters are case-insensitive and can be combined. + +Get more help by running this command: + +```shell +php ./bin/cli.php route:list --help +``` diff --git a/docs/book/v4/commands/generate-database-migrations.md b/docs/book/v4/commands/generate-database-migrations.md new file mode 100644 index 0000000..5110731 --- /dev/null +++ b/docs/book/v4/commands/generate-database-migrations.md @@ -0,0 +1,64 @@ +# Generate a database migration without dropping custom tables. + +## Usage + +Run the following command in your application’s root directory: + +```shell +vendor/bin/doctrine-migrations diff +``` + +If you have mapping modifications, this will create a new migration file under `data/doctrine/migrations/` directory. +Opening the migration file, you will notice that it contains some queries that will drop your `oauth_*` tables because they are unmapped (there is no doctrine entity describing them). +You should delete your latest migration with the DROP queries in it as we will create another one, without the DROP queries in it. +In order to avoid dropping these tables, you need to add a parameter called `filter-expression`. + +The command to be executed without dropping these tables looks like this: + +On Windows (use double quotes): + +```shell +vendor/bin/doctrine-migrations diff --filter-expression="/^(?!oauth_)/" +``` + +On Linux/macOS (use single quotes): + +```shell +vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/' +``` + +## Filtering multiple unmapped table patterns + +If your database contains multiple unmapped table groups, then the pattern in `filter-expression` should hold all table prefixes concatenated by pipe character (`|`). +For example, if you need to filter tables prefixed with `foo_` and `bar_`, then the command should look like this: + +On Windows: + +```shell +vendor/bin/doctrine-migrations diff --filter-expression="/^(?!foo_|bar_)/" +``` + +On Linux/macOS: + +```shell +vendor/bin/doctrine-migrations diff --filter-expression='/^(?!foo_|bar_)/' +``` + +## Troubleshooting + +On Windows, running the command in PowerShell might still add the `DROP TABLE oauth_*` queries to the migration file. +This happens because for PowerShell the caret (`^`) is a special character, so it gets dropped (`"/^(?!oauth_)/"` becomes `"/(?!oauth_)/"` when it reaches your command). +Escaping it will not help either. +In this case, we recommend running the command: + +* directly from your IDE +* using `Linux shell` +* from the `Command Prompt` + +## Help + +You can get more help with this command by running: + +```shell +vendor/bin/doctrine-migrations help diff +``` diff --git a/docs/book/v4/commands/generate-tokens.md b/docs/book/v4/commands/generate-tokens.md new file mode 100644 index 0000000..424ee1f --- /dev/null +++ b/docs/book/v4/commands/generate-tokens.md @@ -0,0 +1,64 @@ +# Generating tokens in DotKernel API + +This is a multipurpose command that allows creating tokens required by different parts of the API. + +## Usage + +Go to your application's root directory. + +Run the token generator command by executing the following command: + +```shell +php ./bin/cli.php token:generate +``` + +Where `` is one of the following: +* [error-reporting](#generate-error-reporting-token) + +If you need help using the command, execute the following command: + +```shell +php ./bin/cli.php token:generate --help +``` + +### Generate error reporting token + +You can generate an error reporting token by executing the following command: + +``` +php ./bin/cli.php token:generate error-reporting +``` + +The output should look similar to this: + +```text +Error reporting token: + + 0123456789abcdef0123456789abcdef01234567 +``` +Copy the generated token. + +Open `config/autoload/error-handling.global.php` and paste the copied token as shown below: + +```php +return [ + ... + ErrorReportServiceInterface::class => [ + ... + 'tokens' => [ + '0123456789abcdef0123456789abcdef01234567', + ], + ... + ] +] +``` + +Save and close `config/autoload/error-handling.global.php`. + +**Note**: + +If your application is NOT in development mode, make sure you clear your config cache by executing: + +```shell +php ./bin/clear-config-cache.php +``` From cde55fd5a846a9c6b3896fdea8071948712cc44c Mon Sep 17 00:00:00 2001 From: Jurj-Bogdan Date: Thu, 6 Jun 2024 19:10:33 +0300 Subject: [PATCH 02/27] linting Signed-off-by: arhimede --- docs/book/v4/commands/create-admin-account.md | 2 ++ docs/book/v4/commands/generate-database-migrations.md | 2 +- docs/book/v4/commands/generate-tokens.md | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/book/v4/commands/create-admin-account.md b/docs/book/v4/commands/create-admin-account.md index 8b7337e..28c84b6 100644 --- a/docs/book/v4/commands/create-admin-account.md +++ b/docs/book/v4/commands/create-admin-account.md @@ -3,6 +3,7 @@ ## Usage Run the following command in your application’s root directory: + ```shell php ./bin/cli.php admin:create -i {IDENTITY} -p {PASSWORD} -f {FIRST_NAME} -l {LAST_NAME} ``` @@ -29,6 +30,7 @@ If the submitted data is valid, the outputted response is: ```text Admin account has been created. ``` + The new admin account is ready to use. You can get more help with this command by running: diff --git a/docs/book/v4/commands/generate-database-migrations.md b/docs/book/v4/commands/generate-database-migrations.md index 5110731..7718cfe 100644 --- a/docs/book/v4/commands/generate-database-migrations.md +++ b/docs/book/v4/commands/generate-database-migrations.md @@ -1,4 +1,4 @@ -# Generate a database migration without dropping custom tables. +# Generate a database migration without dropping custom tables ## Usage diff --git a/docs/book/v4/commands/generate-tokens.md b/docs/book/v4/commands/generate-tokens.md index 424ee1f..340511d 100644 --- a/docs/book/v4/commands/generate-tokens.md +++ b/docs/book/v4/commands/generate-tokens.md @@ -13,6 +13,7 @@ php ./bin/cli.php token:generate ``` Where `` is one of the following: + * [error-reporting](#generate-error-reporting-token) If you need help using the command, execute the following command: @@ -25,7 +26,7 @@ php ./bin/cli.php token:generate --help You can generate an error reporting token by executing the following command: -``` +```shell php ./bin/cli.php token:generate error-reporting ``` @@ -36,6 +37,7 @@ Error reporting token: 0123456789abcdef0123456789abcdef01234567 ``` + Copy the generated token. Open `config/autoload/error-handling.global.php` and paste the copied token as shown below: From 0875053be110ef49f0e06cd773b0c39b3c0a4c7b Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Fri, 7 Jun 2024 11:42:56 +0300 Subject: [PATCH 03/27] Issue #32: CreateD token authentication page. Signed-off-by: alexmerlin Signed-off-by: arhimede --- .../book/v4/tutorials/token-authentication.md | 354 ++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 355 insertions(+) create mode 100644 docs/book/v4/tutorials/token-authentication.md diff --git a/docs/book/v4/tutorials/token-authentication.md b/docs/book/v4/tutorials/token-authentication.md new file mode 100644 index 0000000..bd0336c --- /dev/null +++ b/docs/book/v4/tutorials/token-authentication.md @@ -0,0 +1,354 @@ +# Token authentication + +## What is token authentication? + +Token authentication means making a request to an API endpoint while also sending a special header that contains an +access token. The access token was previously generated by (usually) the same API as the one you are sending requests to +and it consists of an alphanumeric string. + +## How does it work? + +In order to protect specific resources, clients need to be authenticated with user/admin roles. These roles are +identified from the access token sent via the `Authorization` header. + +When DotKernel API receives a request, it tries to read the access token: + +- if it does not find an access token, client has `guest` role: + - if the requested endpoint needs no authentication, the requested resource is returned + - else, a `403 Forbidden` response is returned +- else, client's account is identified and client has `admin`/`user` role (the one assigned in their account) + - if the requested endpoint is accessible to the client, the requested resource is returned + - else, a `403 Forbidden` response is returned + +DotKernel API provides out-of-the-box both: + +- an account with **role** set to both `superuser` and `admin` with the following credentials: + - **identity**: `admin` + - **password**: `dotkernel` +- an account with **role** set to both `user` and `guest` with the following credentials: + - **identify**: `test@dotkernel.com` + - **password**: `dotkernel` + +## Flow + +- client sends API request with credentials +- API returns a JSON object containing a new access and refresh token +- client sends API request using `Authentication` header containing the previously generated access token +- API returns requested resource + +### Note + +> The first two steps need to executed only once. +> Access token should be stored and reused for all upcoming requests. +> Refresh token should be stored and used to refresh expired access token. + +For a better overview of the flow, see the below image: + +![](https://docs.dotkernel.org/img/api/token-authentication.png "Token authentication flow") + +## Generate admin access token + +Send a `POST` request to the `/security/generate-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "password", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "username": "", + "password": "" +} +``` + +### Note + +> Replace `` with your admin account's `identity` and `` with your admin account's `password`. +> Both fields come from table `admin`. + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "username": "admin", + "password": "dotkernel" +}' +``` + +## Generate user access token + +Send a `POST` request to the `/security/generate-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "", + "password": "" +} +``` + +### Note + +> Replace `` with your user account's `identity` and `` with your user account's `password`. +> Both fields come from table `user`. + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "test@dotkernel.com", + "password": "dotkernel" +}' +``` + +### Response on success + +You should see a `200 OK` response with the following JSON body: + +```json +{ + "token_type": "Bearer", + "expires_in": 86400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e...wuE39ON1mS5mnTKfA_dSpSWxOmNQdny_AKIbc1qZjMfS24qSUV8HIoOw", + "refresh_token": "def502005a035c8dfe5456d27e85069813a4f8...0b844e843cd62865662a0e723165752dfd7012491502d3d819c2a61d" +} +``` + +Field description: + +- `token_type`: token type to be set when sending the `Authorization` header (example: `Authorization: Bearer eyJ0e...`) +- `expires_in`: access token lifetime (modify in: `config/autoload/local.php` > `authentication`.`access_token_expire`) +- `access_token`: generated access token (store it for later use) +- `refresh_token`: generated refresh token (store it for regenerating expired access token) + +### Response on failure + +You should see a `400 Bad Request` response with the following JSON body: + +```json +{ + "error": "Invalid credentials.", + "error_description": "Invalid credentials.", + "message": "Invalid credentials." +} +``` + +## Refresh admin access token + +Send a `POST` request to the `/security/refresh-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "refresh_token", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "refresh_token": "" +} +``` + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/refresh-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "refresh_token", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "refresh_token": "" +}' +``` + +### Note + +> Make sure you replace `` with the refresh token generated with the access token. + +## Refresh user access token + +Send a `POST` request to the `/security/refresh-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "refresh_token", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "refresh_token": "" +} +``` + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/refresh-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "refresh_token", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "refresh_token": "" +}' +``` + +### Note + +> Make sure you replace `` with the refresh token generated with the access token. + +### Response on success + +You should see a `200 OK` response with the following JSON body: + +```json +{ + "token_type": "Bearer", + "expires_in": 86400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e...wuE39ON1mS5mnTKfA_dSpSWxOmNQdny_AKIbc1qZjMfS24qSUV8HIoOw", + "refresh_token": "def502005a035c8dfe5456d27e85069813a4f8...0b844e843cd62865662a0e723165752dfd7012491502d3d819c2a61d" +} +``` + +Field description: + +- `token_type`: token type to be set when sending the `Authorization` header (example: `Authorization: Bearer eyJ0e...`) +- `expires_in`: access token lifetime (change here: `config/autoload/local.php` `authentication`->`access_token_expire`) +- `access_token`: generated access token (store it for later use) +- `refresh_token`: generated refresh token (store it for regenerating expired access token) + +### Response on failure + +You should see a `401 Unauthorized` response with the following JSON body: + +```json +{ + "error": "invalid_request", + "error_description": "The refresh token is invalid.", + "hint": "Cannot decrypt the refresh token", + "message": "The refresh token is invalid." +} +``` + +## Test admin authentication flow + +### Step 1: Fail to fetch protected API content + +Try to view your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/admin/my-account' +``` + +You should get a `403 Forbidden` JSON response. + +### Step 2: Generate access token + +Generate admin access token by executing: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "username": "admin", + "password": "dotkernel" +}' +``` + +You should get a `200 OK` JSON response. + +Store the value of `access_token` for later use. + +### Step 3: Successfully fetch protected API content + +Try again viewing your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/admin/my-account' \ +--header 'Authorization: Bearer ' +``` + +Replace `` with the previously stored access token. + +You should get a `200 OK` JSON response with the requested resource in the body. + +## Test user authentication flow + +### Step 1: Fail to fetch protected API content + +Try to view your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/user/my-account' +``` + +You should get a `403 Forbidden` JSON response. + +### Step 2: Generate access token + +Generate admin access token by executing: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "test@dotkernel.com", + "password": "dotkernel" +}' +``` + +You should get a `200 OK` JSON response. + +Store the value of `access_token` for later use. + +### Step 3: Successfully fetch protected API content + +Try again viewing your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/user/my-account' \ +--header 'Authorization: Bearer ' +``` + +Replace `` with the previously stored access token. + +You should get a `200 OK` JSON response with the requested resource in the body. diff --git a/mkdocs.yml b/mkdocs.yml index 7042b24..bcefa16 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - "Generate tokens": v4/commands/generate-tokens.md - Tutorials: - "Creating a book module": v4/tutorials/create-book-module.md + - "Creating a book module": v4/tutorials/token-authentication.md - Transition from API Tools: - "Laminas API Tools vs DotKernel API": v4/transition-from-api-tools/api-tools-vs-dotkernel-api.md - "Transition Approach": v4/transition-from-api-tools/transition-approach.md From bd1bf9ea293835b7ad4f642b916a86cf89a59c8f Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Fri, 7 Jun 2024 11:49:22 +0300 Subject: [PATCH 04/27] fixes Signed-off-by: alexmerlin Signed-off-by: arhimede --- .../book/v4/tutorials/token-authentication.md | 30 +++++++++---------- mkdocs.yml | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/book/v4/tutorials/token-authentication.md b/docs/book/v4/tutorials/token-authentication.md index bd0336c..90e45b3 100644 --- a/docs/book/v4/tutorials/token-authentication.md +++ b/docs/book/v4/tutorials/token-authentication.md @@ -11,23 +11,23 @@ and it consists of an alphanumeric string. In order to protect specific resources, clients need to be authenticated with user/admin roles. These roles are identified from the access token sent via the `Authorization` header. -When DotKernel API receives a request, it tries to read the access token: +When DotKernel API receives a request, it tries to read the access token. -- if it does not find an access token, client has `guest` role: - - if the requested endpoint needs no authentication, the requested resource is returned - - else, a `403 Forbidden` response is returned -- else, client's account is identified and client has `admin`/`user` role (the one assigned in their account) - - if the requested endpoint is accessible to the client, the requested resource is returned - - else, a `403 Forbidden` response is returned +If it does not find an access token, client has `guest` role: +- if the requested endpoint needs no authentication, the requested resource is returned +- else, a `403 Forbidden` response is returned +Else, client's account is identified and client has `admin`/`user` role (the one assigned in their account) +- if the requested endpoint is accessible to the client, the requested resource is returned +- else, a `403 Forbidden` response is returned -DotKernel API provides out-of-the-box both: +DotKernel API provides out-of-the-box both an `admin` and a `user` account. -- an account with **role** set to both `superuser` and `admin` with the following credentials: - - **identity**: `admin` - - **password**: `dotkernel` -- an account with **role** set to both `user` and `guest` with the following credentials: - - **identify**: `test@dotkernel.com` - - **password**: `dotkernel` +The admin account with **role** set to both `superuser` and `admin` with the following credentials: +- **identity**: `admin` +- **password**: `dotkernel` +The user account with **role** set to both `user` and `guest` with the following credentials: +- **identify**: `test@dotkernel.com` +- **password**: `dotkernel` ## Flow @@ -44,7 +44,7 @@ DotKernel API provides out-of-the-box both: For a better overview of the flow, see the below image: -![](https://docs.dotkernel.org/img/api/token-authentication.png "Token authentication flow") +![Token authentication flow](https://docs.dotkernel.org/img/api/token-authentication.png) ## Generate admin access token diff --git a/mkdocs.yml b/mkdocs.yml index bcefa16..f55695d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,7 +37,7 @@ nav: - "Generate tokens": v4/commands/generate-tokens.md - Tutorials: - "Creating a book module": v4/tutorials/create-book-module.md - - "Creating a book module": v4/tutorials/token-authentication.md + - "Token authentication": v4/tutorials/token-authentication.md - Transition from API Tools: - "Laminas API Tools vs DotKernel API": v4/transition-from-api-tools/api-tools-vs-dotkernel-api.md - "Transition Approach": v4/transition-from-api-tools/transition-approach.md From 3d2700b92383f4be3ab0bf2e531489e88aed06fd Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Fri, 7 Jun 2024 11:52:05 +0300 Subject: [PATCH 05/27] linting Signed-off-by: alexmerlin Signed-off-by: arhimede --- docs/book/v4/tutorials/token-authentication.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/book/v4/tutorials/token-authentication.md b/docs/book/v4/tutorials/token-authentication.md index 90e45b3..1df03df 100644 --- a/docs/book/v4/tutorials/token-authentication.md +++ b/docs/book/v4/tutorials/token-authentication.md @@ -14,9 +14,12 @@ identified from the access token sent via the `Authorization` header. When DotKernel API receives a request, it tries to read the access token. If it does not find an access token, client has `guest` role: + - if the requested endpoint needs no authentication, the requested resource is returned - else, a `403 Forbidden` response is returned + Else, client's account is identified and client has `admin`/`user` role (the one assigned in their account) + - if the requested endpoint is accessible to the client, the requested resource is returned - else, a `403 Forbidden` response is returned From 95a655b4b7febe444beb4972b1823892210b32e4 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Fri, 7 Jun 2024 11:53:27 +0300 Subject: [PATCH 06/27] linting Signed-off-by: alexmerlin Signed-off-by: arhimede --- docs/book/v4/tutorials/token-authentication.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/book/v4/tutorials/token-authentication.md b/docs/book/v4/tutorials/token-authentication.md index 1df03df..8ff01ea 100644 --- a/docs/book/v4/tutorials/token-authentication.md +++ b/docs/book/v4/tutorials/token-authentication.md @@ -8,8 +8,8 @@ and it consists of an alphanumeric string. ## How does it work? -In order to protect specific resources, clients need to be authenticated with user/admin roles. These roles are -identified from the access token sent via the `Authorization` header. +In order to protect specific resources, clients need to be authenticated with user/admin roles. +These roles are identified from the access token sent via the `Authorization` header. When DotKernel API receives a request, it tries to read the access token. @@ -26,9 +26,12 @@ Else, client's account is identified and client has `admin`/`user` role (the one DotKernel API provides out-of-the-box both an `admin` and a `user` account. The admin account with **role** set to both `superuser` and `admin` with the following credentials: + - **identity**: `admin` - **password**: `dotkernel` + The user account with **role** set to both `user` and `guest` with the following credentials: + - **identify**: `test@dotkernel.com` - **password**: `dotkernel` From c88cbe908696bf8e8b541753ac8152b2ac11ca3c Mon Sep 17 00:00:00 2001 From: arhimede Date: Mon, 10 Jun 2024 20:50:35 +0300 Subject: [PATCH 07/27] gains Signed-off-by: arhimede --- LICENSE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LICENSE b/LICENSE index 8fbc26c..4594f73 100644 --- a/LICENSE +++ b/LICENSE @@ -12,6 +12,8 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE From 2c8563e85d918275c94bbe26660dc90b1217193b Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Fri, 7 Jun 2024 13:13:27 +0300 Subject: [PATCH 08/27] Issue #35: Updated routes order in display-available-endpoints.md Signed-off-by: alexmerlin Signed-off-by: arhimede --- .../commands/display-available-endpoints.md | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/book/v4/commands/display-available-endpoints.md b/docs/book/v4/commands/display-available-endpoints.md index 9ff8c27..de197bc 100644 --- a/docs/book/v4/commands/display-available-endpoints.md +++ b/docs/book/v4/commands/display-available-endpoints.md @@ -15,43 +15,43 @@ The output should be similar to the following: +--------+---------------------------------+--------------------------------+ | Method | Name | Path | +--------+---------------------------------+--------------------------------+ -| DELETE | admin.delete | /admin/{uuid} | -| DELETE | user.my-account.delete | /user/my-account | -| DELETE | user.my-avatar.delete | /user/my-avatar | -| DELETE | user.delete | /user/{uuid} | -| DELETE | user.avatar.delete | /user/{uuid}/avatar | -| GET | home | / | +| POST | account.activate.request | /account/activate | +| PATCH | account.activate | /account/activate/{hash} | +| PATCH | account.modify-password | /account/reset-password/{hash} | +| POST | account.recover-identity | /account/recover-identity | +| POST | account.register | /account/register | +| POST | account.reset-password.request | /account/reset-password | | GET | account.reset-password.validate | /account/reset-password/{hash} | +| POST | admin.create | /admin | +| DELETE | admin.delete | /admin/{uuid} | | GET | admin.list | /admin | +| PATCH | admin.my-account.update | /admin/my-account | | GET | admin.my-account.view | /admin/my-account | | GET | admin.role.list | /admin/role | | GET | admin.role.view | /admin/role/{uuid} | +| PATCH | admin.update | /admin/{uuid} | | GET | admin.view | /admin/{uuid} | +| POST | error.report | /error-report | +| GET | home | / | +| POST | security.generate-token | /security/generate-token | +| POST | security.refresh-token | /security/refresh-token | +| POST | user.activate | /user/{uuid}/activate | +| POST | user.avatar.create | /user/{uuid}/avatar | +| DELETE | user.avatar.delete | /user/{uuid}/avatar | +| GET | user.avatar.view | /user/{uuid}/avatar | +| POST | user.create | /user | +| DELETE | user.delete | /user/{uuid} | | GET | user.list | /user | +| DELETE | user.my-account.delete | /user/my-account | +| PATCH | user.my-account.update | /user/my-account | | GET | user.my-account.view | /user/my-account | +| POST | user.my-avatar.create | /user/my-avatar | +| DELETE | user.my-avatar.delete | /user/my-avatar | | GET | user.my-avatar.view | /user/my-avatar | | GET | user.role.list | /user/role | | GET | user.role.view | /user/role/{uuid} | -| GET | user.view | /user/{uuid} | -| GET | user.avatar.view | /user/{uuid}/avatar | -| PATCH | account.activate | /account/activate/{hash} | -| PATCH | account.modify-password | /account/reset-password/{hash} | -| PATCH | admin.my-account.update | /admin/my-account | -| PATCH | admin.update | /admin/{uuid} | -| PATCH | user.my-account.update | /user/my-account | | PATCH | user.update | /user/{uuid} | -| POST | account.activate.request | /account/activate | -| POST | account.recover-identity | /account/recover-identity | -| POST | account.register | /account/register | -| POST | account.reset-password.request | /account/reset-password | -| POST | admin.create | /admin | -| POST | error.report | /error-report | -| POST | security.generate-token | /security/generate-token | -| POST | security.refresh-token | /security/refresh-token | -| POST | user.create | /user | -| POST | user.my-avatar.create | /user/my-avatar | -| POST | user.activate | /user/{uuid}/activate | -| POST | user.avatar.create | /user/{uuid}/avatar | +| GET | user.view | /user/{uuid} | +--------+---------------------------------+--------------------------------+ ``` From 84032f669c6ca6448620dfddc6dd1f7b151c840f Mon Sep 17 00:00:00 2001 From: arhimede Date: Tue, 18 Jun 2024 12:29:16 +0300 Subject: [PATCH 09/27] added DotKernel API version 4 string in documentation Signed-off-by: arhimede --- LICENSE | 2 -- docs/book/v4/installation/test-the-installation.md | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 4594f73..8fbc26c 100644 --- a/LICENSE +++ b/LICENSE @@ -12,8 +12,6 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/docs/book/v4/installation/test-the-installation.md b/docs/book/v4/installation/test-the-installation.md index e3dd516..47f49eb 100644 --- a/docs/book/v4/installation/test-the-installation.md +++ b/docs/book/v4/installation/test-the-installation.md @@ -2,7 +2,7 @@ Sending a GET request to the [home page](http://0.0.0.0:8080/) should output the following message: -> {"message": "Welcome to DotKernel API!"} +> {"message": "DotKernel API version 4"} ## Old way of doing things, using PHP built-in server @@ -12,7 +12,8 @@ php -S 0.0.0.0:8080 -t public ## Running tests -The project has 2 types of tests: functional and unit tests, you can run both types at the same type by executing this command: +The project has 2 types of tests: functional and unit tests, you can run both types at the same type by executing this +command: ```shell php vendor/bin/phpunit From 478d4114e394b8a21ae89e0efacb0ed24a0917c1 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Tue, 18 Jun 2024 13:09:58 +0300 Subject: [PATCH 10/27] Added v5 documentation Signed-off-by: alexmerlin Signed-off-by: arhimede --- docs/book/v5/commands/create-admin-account.md | 40 ++ .../commands/display-available-endpoints.md | 72 ++ .../commands/generate-database-migrations.md | 64 ++ docs/book/v5/commands/generate-tokens.md | 66 ++ docs/book/v5/core-features/authentication.md | 120 ++++ docs/book/v5/core-features/authorization.md | 77 +++ .../v5/core-features/content-validation.md | 112 +++ docs/book/v5/core-features/cors.md | 92 +++ docs/book/v5/core-features/exceptions.md | 137 ++++ docs/book/v5/flow/default-library-flow.md | 5 + docs/book/v5/flow/library-flow-for-email.md | 5 + docs/book/v5/flow/middleware-flow.md | 5 + docs/book/v5/installation/composer.md | 27 + .../v5/installation/configuration-files.md | 23 + docs/book/v5/installation/doctrine-orm.md | 47 ++ docs/book/v5/installation/faq.md | 39 ++ docs/book/v5/installation/getting-started.md | 7 + .../v5/installation/test-the-installation.md | 32 + docs/book/v5/introduction/file-structure.md | 61 ++ docs/book/v5/introduction/introduction.md | 110 +++ docs/book/v5/introduction/packages.md | 30 + .../v5/introduction/server-requirements.md | 35 + .../api-tools-vs-dotkernel-api.md | 26 + .../discovery-phase.md | 40 ++ .../transition-approach.md | 21 + docs/book/v5/tutorials/create-book-module.md | 653 ++++++++++++++++++ .../book/v5/tutorials/token-authentication.md | 360 ++++++++++ mkdocs.yml | 38 +- 28 files changed, 2343 insertions(+), 1 deletion(-) create mode 100644 docs/book/v5/commands/create-admin-account.md create mode 100644 docs/book/v5/commands/display-available-endpoints.md create mode 100644 docs/book/v5/commands/generate-database-migrations.md create mode 100644 docs/book/v5/commands/generate-tokens.md create mode 100644 docs/book/v5/core-features/authentication.md create mode 100644 docs/book/v5/core-features/authorization.md create mode 100644 docs/book/v5/core-features/content-validation.md create mode 100644 docs/book/v5/core-features/cors.md create mode 100644 docs/book/v5/core-features/exceptions.md create mode 100644 docs/book/v5/flow/default-library-flow.md create mode 100644 docs/book/v5/flow/library-flow-for-email.md create mode 100644 docs/book/v5/flow/middleware-flow.md create mode 100644 docs/book/v5/installation/composer.md create mode 100644 docs/book/v5/installation/configuration-files.md create mode 100644 docs/book/v5/installation/doctrine-orm.md create mode 100644 docs/book/v5/installation/faq.md create mode 100644 docs/book/v5/installation/getting-started.md create mode 100644 docs/book/v5/installation/test-the-installation.md create mode 100644 docs/book/v5/introduction/file-structure.md create mode 100644 docs/book/v5/introduction/introduction.md create mode 100644 docs/book/v5/introduction/packages.md create mode 100644 docs/book/v5/introduction/server-requirements.md create mode 100644 docs/book/v5/transition-from-api-tools/api-tools-vs-dotkernel-api.md create mode 100644 docs/book/v5/transition-from-api-tools/discovery-phase.md create mode 100644 docs/book/v5/transition-from-api-tools/transition-approach.md create mode 100644 docs/book/v5/tutorials/create-book-module.md create mode 100644 docs/book/v5/tutorials/token-authentication.md diff --git a/docs/book/v5/commands/create-admin-account.md b/docs/book/v5/commands/create-admin-account.md new file mode 100644 index 0000000..28c84b6 --- /dev/null +++ b/docs/book/v5/commands/create-admin-account.md @@ -0,0 +1,40 @@ +# Creating admin accounts in DotKernel API + +## Usage + +Run the following command in your application’s root directory: + +```shell +php ./bin/cli.php admin:create -i {IDENTITY} -p {PASSWORD} -f {FIRST_NAME} -l {LAST_NAME} +``` + +OR + +```shell +php ./bin/cli.php admin:create --identity {IDENTITY} --password {PASSWORD} --firstName {FIRST_NAME} --lastName {LAST_NAME} +``` + +after replacing: + +* {IDENTITY} with a valid username OR email address +* {PASSWORD} with a valid password +* {FIRST_NAME} and {LAST_NAME} with valid names + +**NOTE:** + +* if the specified fields contain special characters, make sure you surround them with double quote signs +* this method does not allow specifying an admin role – newly created accounts will have role of admin + +If the submitted data is valid, the outputted response is: + +```text +Admin account has been created. +``` + +The new admin account is ready to use. + +You can get more help with this command by running: + +```shell +php ./bin/cli.php help admin:create +``` diff --git a/docs/book/v5/commands/display-available-endpoints.md b/docs/book/v5/commands/display-available-endpoints.md new file mode 100644 index 0000000..de197bc --- /dev/null +++ b/docs/book/v5/commands/display-available-endpoints.md @@ -0,0 +1,72 @@ +# Displaying DotKernel API endpoints using dot-cli + +## Usage + +Run the following command in your application’s root directory: + +```shell +php ./bin/cli.php route:list +``` + +The command runs through all routes and extracts endpoint information in realtime. +The output should be similar to the following: + +```text ++--------+---------------------------------+--------------------------------+ +| Method | Name | Path | ++--------+---------------------------------+--------------------------------+ +| POST | account.activate.request | /account/activate | +| PATCH | account.activate | /account/activate/{hash} | +| PATCH | account.modify-password | /account/reset-password/{hash} | +| POST | account.recover-identity | /account/recover-identity | +| POST | account.register | /account/register | +| POST | account.reset-password.request | /account/reset-password | +| GET | account.reset-password.validate | /account/reset-password/{hash} | +| POST | admin.create | /admin | +| DELETE | admin.delete | /admin/{uuid} | +| GET | admin.list | /admin | +| PATCH | admin.my-account.update | /admin/my-account | +| GET | admin.my-account.view | /admin/my-account | +| GET | admin.role.list | /admin/role | +| GET | admin.role.view | /admin/role/{uuid} | +| PATCH | admin.update | /admin/{uuid} | +| GET | admin.view | /admin/{uuid} | +| POST | error.report | /error-report | +| GET | home | / | +| POST | security.generate-token | /security/generate-token | +| POST | security.refresh-token | /security/refresh-token | +| POST | user.activate | /user/{uuid}/activate | +| POST | user.avatar.create | /user/{uuid}/avatar | +| DELETE | user.avatar.delete | /user/{uuid}/avatar | +| GET | user.avatar.view | /user/{uuid}/avatar | +| POST | user.create | /user | +| DELETE | user.delete | /user/{uuid} | +| GET | user.list | /user | +| DELETE | user.my-account.delete | /user/my-account | +| PATCH | user.my-account.update | /user/my-account | +| GET | user.my-account.view | /user/my-account | +| POST | user.my-avatar.create | /user/my-avatar | +| DELETE | user.my-avatar.delete | /user/my-avatar | +| GET | user.my-avatar.view | /user/my-avatar | +| GET | user.role.list | /user/role | +| GET | user.role.view | /user/role/{uuid} | +| PATCH | user.update | /user/{uuid} | +| GET | user.view | /user/{uuid} | ++--------+---------------------------------+--------------------------------+ +``` + +## Filtering results + +The following filters can be applied when displaying the routes list: + +* Filter routes by name, using: `-i|--name[=NAME]` +* Filter routes by path, using: `-p|--path[=PATH]` +* Filter routes by method, using: `-m|--method[=METHOD]` + +The filters are case-insensitive and can be combined. + +Get more help by running this command: + +```shell +php ./bin/cli.php route:list --help +``` diff --git a/docs/book/v5/commands/generate-database-migrations.md b/docs/book/v5/commands/generate-database-migrations.md new file mode 100644 index 0000000..7718cfe --- /dev/null +++ b/docs/book/v5/commands/generate-database-migrations.md @@ -0,0 +1,64 @@ +# Generate a database migration without dropping custom tables + +## Usage + +Run the following command in your application’s root directory: + +```shell +vendor/bin/doctrine-migrations diff +``` + +If you have mapping modifications, this will create a new migration file under `data/doctrine/migrations/` directory. +Opening the migration file, you will notice that it contains some queries that will drop your `oauth_*` tables because they are unmapped (there is no doctrine entity describing them). +You should delete your latest migration with the DROP queries in it as we will create another one, without the DROP queries in it. +In order to avoid dropping these tables, you need to add a parameter called `filter-expression`. + +The command to be executed without dropping these tables looks like this: + +On Windows (use double quotes): + +```shell +vendor/bin/doctrine-migrations diff --filter-expression="/^(?!oauth_)/" +``` + +On Linux/macOS (use single quotes): + +```shell +vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/' +``` + +## Filtering multiple unmapped table patterns + +If your database contains multiple unmapped table groups, then the pattern in `filter-expression` should hold all table prefixes concatenated by pipe character (`|`). +For example, if you need to filter tables prefixed with `foo_` and `bar_`, then the command should look like this: + +On Windows: + +```shell +vendor/bin/doctrine-migrations diff --filter-expression="/^(?!foo_|bar_)/" +``` + +On Linux/macOS: + +```shell +vendor/bin/doctrine-migrations diff --filter-expression='/^(?!foo_|bar_)/' +``` + +## Troubleshooting + +On Windows, running the command in PowerShell might still add the `DROP TABLE oauth_*` queries to the migration file. +This happens because for PowerShell the caret (`^`) is a special character, so it gets dropped (`"/^(?!oauth_)/"` becomes `"/(?!oauth_)/"` when it reaches your command). +Escaping it will not help either. +In this case, we recommend running the command: + +* directly from your IDE +* using `Linux shell` +* from the `Command Prompt` + +## Help + +You can get more help with this command by running: + +```shell +vendor/bin/doctrine-migrations help diff +``` diff --git a/docs/book/v5/commands/generate-tokens.md b/docs/book/v5/commands/generate-tokens.md new file mode 100644 index 0000000..340511d --- /dev/null +++ b/docs/book/v5/commands/generate-tokens.md @@ -0,0 +1,66 @@ +# Generating tokens in DotKernel API + +This is a multipurpose command that allows creating tokens required by different parts of the API. + +## Usage + +Go to your application's root directory. + +Run the token generator command by executing the following command: + +```shell +php ./bin/cli.php token:generate +``` + +Where `` is one of the following: + +* [error-reporting](#generate-error-reporting-token) + +If you need help using the command, execute the following command: + +```shell +php ./bin/cli.php token:generate --help +``` + +### Generate error reporting token + +You can generate an error reporting token by executing the following command: + +```shell +php ./bin/cli.php token:generate error-reporting +``` + +The output should look similar to this: + +```text +Error reporting token: + + 0123456789abcdef0123456789abcdef01234567 +``` + +Copy the generated token. + +Open `config/autoload/error-handling.global.php` and paste the copied token as shown below: + +```php +return [ + ... + ErrorReportServiceInterface::class => [ + ... + 'tokens' => [ + '0123456789abcdef0123456789abcdef01234567', + ], + ... + ] +] +``` + +Save and close `config/autoload/error-handling.global.php`. + +**Note**: + +If your application is NOT in development mode, make sure you clear your config cache by executing: + +```shell +php ./bin/clear-config-cache.php +``` diff --git a/docs/book/v5/core-features/authentication.md b/docs/book/v5/core-features/authentication.md new file mode 100644 index 0000000..b58769a --- /dev/null +++ b/docs/book/v5/core-features/authentication.md @@ -0,0 +1,120 @@ +# Authentication + +Authentication is the process by which an identity is presented to the application. It ensures that the entity +making the request has the proper credentials to access the API. + +**DotKernel API** identities are delivered to the application from the client through the `Authorization` request. +If it is present, the application tries to find and assign the identity to the application. If it is not presented, +DotKernel API assigns a default `guest` identity, represented by an instance of the class +`Mezzio\Authentication\UserInterface`. + +## Configuration + +Authentication in DotKernel API is built around the `mezzio/mezzio-authentication-oauth2` component and is already +configured out of the box. But if you want to dig more, the configuration is stored in +`config/autoload/local.php` under the `authentication` key. + +> You can check the +> [mezzio/mezzio-authentication-oauth2](https://docs.mezzio.dev/mezzio-authentication-oauth2/v1/intro/#configuration) +> configuration part for more info. + +## How it works + +DotKernels API authentication system can be used for SPAs (single-page applications), mobile applications, and +simple, token-based APIs. It allows each user of your application to generate API tokens for their accounts. + +The authentication happens through the middleware in the `Api\App\Middleware\AuthenticationMiddleware`. + +## Database + +When you install **DotKernel API** for the first time, you need to run the migrations and seeders. All the tables +required for authentication are automatically created and populated. + +In DotKernel API, authenticated users come from either the `admin` or the `user` table. We choose to keep the admin +table separated from the users to prevent users of the application from accessing sensitive data, which only the +administrators of the application should access. + +The `oauth_clients` table is pre-populated with the default `admin` and `frontend` clients with the same password as +their names (**we recommend you change the default passwords**). + +As you guessed each client serves to authenticate `admin` or `user`. + +Another table that is pre-populated is the `oauth_scopes` table, with the `api` scope. + +### Issuing API Tokens + +Token generation in DotKernel API is done using the `password` `grand_type` scenario, which in this case allows +authentication to an API using the user's credentials (generally a username and password). + +The client sends a POST request to the `/security/generate-token` with the following parameters: + +- `grant_type` = password. +- `client_id` = column `name` from the `oauth_clients` table +- `client_secret` = column `secret` from the `oauth_clients` table +- `scope` = column `scope` from the `oauth_scopes` table +- `username` = column `identity` from table `admin`/`user` +- `password` = column `password` from table `admin`/`user` + +```shell +POST /security/generate-token HTTP/1.1 +Accept: application/json +Content-Type: application/json +{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "test@dotkernel.com", + "password": "dotkernel" +} +``` + +The server responds with a JSON as follows: + +```json +{ + "token_type": "Bearer", + "expires_in": 86400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", + "refresh_token": "def5020087199939a49d0f2f818..." +} +``` + +Next time when you make a request to the server to an authenticated endpoint, the client should use +the `Authorization` header request. + +```shell +GET /users/1 HTTP/1.1 +Accept: application/json +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9... +``` + +### Refreshing tokens + +DotKernel API can refresh the access token, based on the expired access token's `refresh_token`. + +The clients need to send a `POST` request to the `/security/refresh-token` with the following request + +```shell +POST /security/refresh-token HTTP/1.1 +Accept: application/json +Content-Type: application/json +{ + "grant_type": "refresh_token", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "refresh_token" : "def5020087199939a49d0f2f818..." +} +``` + +The server responds with a JSON as follows: + +```json +{ + "token_type": "Bearer", + "expires_in": 86400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", + "refresh_token": "def5020087199939a49d0f2f818..." +} +``` diff --git a/docs/book/v5/core-features/authorization.md b/docs/book/v5/core-features/authorization.md new file mode 100644 index 0000000..cb98254 --- /dev/null +++ b/docs/book/v5/core-features/authorization.md @@ -0,0 +1,77 @@ +# Authorization + +Authorization is the process by which a system takes a validated identity and checks if that identity has access to a +given resource. + +**DotKernel API**'s implementation of authorization uses `Mezzio\Authorization\Rbac\LaminasRbac` as a model of +Role-Based Access Control (RBAC). + +## How it works + +In DotKernel API each authenticatable entity (admin/user) comes with their roles table where you can define +roles for each entity. RBAC comes in to ensure that each entity has the appropriate role and permission to access a +resource. + +The authorization happens through the `Api\App\Middleware\AuthorizationMiddleware` middleware. + +## Configuration + +DotKernel API makes use of `mezzio-authorization-rbac` and includes the full configuration. + +The configuration file for the role and permission definitions is `config/autoload/authorization.global.php`. + +```php +'mezzio-authorization-rbac' => [ + 'roles' => [ + AdminRole::ROLE_SUPERUSER => [], + AdminRole::ROLE_ADMIN => [ + AdminRole::ROLE_SUPERUSER, + ], + UserRole::ROLE_GUEST => [ + UserRole::ROLE_USER, + ], + ], + 'permissions' => [ + AdminRole::ROLE_SUPERUSER => [], + AdminRole::ROLE_ADMIN => [ + 'other.routes' + 'admin.list', + 'home' + ], + UserRole::ROLE_USER => [ + 'other.routes', + 'user.my-account.update', + 'user.my-account.view', + ], + UserRole::ROLE_GUEST => [ + 'other.routes', + 'security.refresh-token', + 'error.report', + 'home', + ], + ], +], +``` + +> See [mezzio-authorization-rbac](https://docs.mezzio.dev/mezzio-authorization-rbac/v1/basic-usage/) +> for more information. + +## Usage + +Based on the configuration file above, we have 2 admins roles (`superuser`, `admin`) and 2 users +roles (`user`, `guest`). + +Roles inherit the permissions from their parents: + +- `superuser` has no parent +- `admin` has `superuser` as a parent which means `superuser` also has `admin` permissions +- `user` has no parent +- `guest` has `user` as a parent which means `user` also has `guest` permissions + +For each role we defined an array of permissions. A permission in DotKernel API is basically a route name. + +As you can see, the `superuser` does not have its own permissions, because it gains all the permissions +from `admin`, no need to define explicit permissions. + +The `user` role, gains all the permission from `guest` so no need to define that `user` can access `home` route, but +`guest` cannot access user-specific routes. diff --git a/docs/book/v5/core-features/content-validation.md b/docs/book/v5/core-features/content-validation.md new file mode 100644 index 0000000..316e842 --- /dev/null +++ b/docs/book/v5/core-features/content-validation.md @@ -0,0 +1,112 @@ +# Content Negotiation + +**Content Negotiation** is performed by an application in order : + +- To match the requested representation as specified by the client via the Accept header with a representation the + application can deliver. +- To determine the `Content-Type` of incoming data and deserialize it so the application can utilize it. + +Essentially, content negotiation is the *client* telling the server what it is sending and what it wants in return, and +the server determining if it can do what the client requests. + +Content negotiation validation in **DotKernel API** happens through middleware, and it ensures that the incoming +request and the outgoing response conform to the content types specified in the config file for all routes or for a +specific route. + +It performs validation on the `Accept` and `Content-Type` headers of the request and response and returning appropriate +errors responses when necessary. + +## Configuration + +In DotKernel API the configuration file for content negotiation is held +in `config/autoload/content-negotiation.global.php` +and the array looks like this: + +```php +return [ + 'content-negotiation' => [ + 'default' => [ + 'Accept' => [ + 'application/json', + 'application/hal+json', + ], + 'Content-Type' => [ + 'application/json', + 'application/hal+json', + ], + ], + 'your.route.name' => [ + 'Accept' => [], + 'Content-Type' => [], + ], + ], +]; +``` + +Except the `default` key, all your keys must match the route name, for example in DotKernel API we have the route to +list all admins, which name is `admin.list`. + +If you did not specify a route name to configure your specifications about content negotiation, the `default` one will +be in place. The `default` key is `mandatory`. + +Every route configuration must come with `Accept` and `Content-Type` keys, basically this will be the keys that the +request headers will be validated against. + +## Accept Negotiation + +This specifies that your server can return that representation, or at least one of the representation sent by the +client. + +```shell +GET /admin HTTP/1.1 +Accept: application/json +``` + +This request indicates the client wants `application/json` in return. Now the server, through the config file will try +to validate if that representation can be returned, basically if `application/json` is presented in the `Accept` key. + +If the representation cannot be returned, a status code `406 - Not Acceptable` will be returned. + +If the representation can be returned, the server should report the media type through `Content-Type` header of the +response. + +> Due to how these validations are made, for a `json` media type, the server can return a more generic media type, +> for example, if the clients send `Accept: application/vnd.api+json` and you configured your `Accept` key +> as `application/json` the representation will still be returned as `json`. + +> If the `Accept` header of the request contains `*/*` it means that whatever the server can return it is OK, so it can +> return anything. + +## Content-Type Negotiation + +The second aspect of content negotiation is the `Content-Type` header and determine the server can deserialize the data. + +```shell +POST /admin/1 HTTP/1.1 +Accept: application/json +Content-Type: application/json +{ + "foo": "bar" +} +``` + +The server will try to validate the `Content-Type` header against your configured `Content-Type` key from the config +file, and if the format is not supported, a status code `415 - Unsupported Media Type` will be returned. + +For example, if you have a route that needs a file to be uploaded , normally you will configure the `Content-Type` of +that route to be `multipart/form-data`. The above request will fail as the client send `application/json` as +`Content-Type`. + +> If the request does not contain "Content-Type" header, that means that the server will try to deserialize the data as +> it can. + +## The `Request <-> Response` validation + +In addition to the validation described above, a third one is happening and is the last one: the server will check if +the request `Accept` header can really be returned by the response. + +Through the way **DotKernel API** is returning a response in handler, a content type is always set. + +This cannot be the case in any custom response but in any case the server will check what `Content-Type` the response is +returning and will try to validate that against the `Accept` header of the request. +If the validation fails, a status code `406 - Not Acceptable` will be returned. diff --git a/docs/book/v5/core-features/cors.md b/docs/book/v5/core-features/cors.md new file mode 100644 index 0000000..7becc51 --- /dev/null +++ b/docs/book/v5/core-features/cors.md @@ -0,0 +1,92 @@ +# CORS + +## What is CORS? + +**Cross-Origin Resource Sharing** or _CORS_ is an HTTP-header based mechanism that allows a server to indicate any other +origins (domain, scheme, or port) than its own from which a browser should permit loading of resources. + +## Why do we need CORS? + +When integrating an API, most developers have encountered the following error message: + +> Access to fetch at _RESOURCE_URL_ from origin _ORIGIN_URL_ has been blocked by CORS policy: +> No ‘Access-Control-Allow-Origin’ header is present on the requested resource. + +This happens because the API (_RESOURCE_URL_) is not configured to accept requests from the client (_ORIGIN_URL_). + +## How to fix? + +DotKernel API fixes this issue using the [mezzio/mezzio-cors](https://github.com/mezzio/mezzio-cors) library. + +### Step 1: Install library + +In order to install `mezzio/mezzio-cors`, run the following command: + +```shell +composer require mezzio/mezzio-cors +``` + +### Step 2: Configure your API + +#### Register ConfigProvider + +Register `mezzio/mezzio-cors` in your application by adding its ConfigProvider to your application's config aggregator. +Open the file `config/config.php` and paste the below lines at the beginning of the array passed to `ConfigAggregator`: + +```php +Laminas\Diactoros\ConfigProvider::class, +Mezzio\Cors\ConfigProvider::class, +``` + +Save and close the file. + +#### Add middleware + +Add `mezzio/mezzio-cors` middleware to your application's pipeline. +Open `config/pipeline.php` and paste the below line before the one with `RouteMiddleware::class`: + +```php +$app->pipe(\Mezzio\Cors\Middleware\CorsMiddleware::class); +``` + +Save and close the file. + +#### Create config file + +Create and open file `config/autoload/cors.local.php` and add the following code inside it: + +```php + [ + 'allowed_origins' => [ + ConfigurationInterface::ANY_ORIGIN, + ], + 'allowed_headers' => ['Accept', 'Content-Type', 'Authorization'], + 'allowed_max_age' => '600', + 'credentials_allowed' => true, + 'exposed_headers' => [], + ], +]; +``` + +This list explains the above configuration values: + +- `allowed_origins`: an array of domains that are allowed to interact with the API + (default `ConfigurationInterface::ANY_ORIGIN` which means that any domain can make requests to the API) +- `allowed_headers`: an array of allowed custom headers +- `allowed_max_age`: the maximum duration, since the preflight response may be cached by a client +- `credentials_allowed`: allows a request to pass cookies +- `exposed_headers`: an array of headers which are being exposed by the endpoint + +Save and close the file. + +> On the **production** environment, make sure you allow only specific origins by adding them to the `allowed_origins` +> array and removing the current value of `ConfigurationInterface::ANY_ORIGIN`. + +For more info, see [mezzio/mezzio-cors documentation](https://docs.mezzio.dev/mezzio-cors/v1/middleware/#configuration). diff --git a/docs/book/v5/core-features/exceptions.md b/docs/book/v5/core-features/exceptions.md new file mode 100644 index 0000000..649e65f --- /dev/null +++ b/docs/book/v5/core-features/exceptions.md @@ -0,0 +1,137 @@ +# Exceptions + +## What are exceptions? + +Exceptions are a powerful mechanism for handling errors and other exceptional conditions that may occur during the +execution of a script. +They provide a way to manage errors in a structured and controlled manner, separating error-handling code from regular +code. + +## How we use exceptions? + +When it comes to handling exceptions, **DotKernel API** relies on the usage of easy-to-understand, problem-specific +exceptions. + +Out-of-the-box we provide the following custom exceptions: + +### `BadRequestException` thrown when + +* client tries to create/update resource, but the data from the request is invalid/incomplete (example: client tries to + create an account, but does not send the required `identity` field) + +### `ConflictException` thrown when + +* resource cannot be created because a different resource with the same identifier already exists (example: cannot + change existing user's identity because another user with the same identity already exists) +* resource cannot change its state because it is already in the specified state (example: user cannot be activated + because it is already active) + +### `ExpiredException` thrown when + +* resource cannot be accessed because it expired (example: account activation link) +* resource cannot be accessed because it has been consumed (example: one-time password) + +### `ForbiddenException` thrown when + +* resource cannot be accessed by the authenticated client (example: client authenticated as regular user sends + a `GET /admin` request) + +### `MethodNotAllowedException` thrown when + +* client tries to interact with a resource via an invalid HTTP request method (example: client sends a `PATCH /avatar` + request) + +### `NotFoundException` thrown when + +* client tries to interact with a resource that does not exist on the server (example: client sends + a `GET /resource-does-not-exist` request) + +### `UnauthorizedException` thrown when + +* resource cannot be accessed because the client is not authenticated (example: unauthenticated client sends + a `GET /admin` request) + +## How it works? + +During a request, if there is no uncaught exception **DotKernel API** will return a JSON response with the data provided +by the handler that handled the request. + +Else, it will build and send a response based on the exception thrown: + +* `BadRequestException` will return a `400 Bad Request` response +* `UnauthorizedException` will return a `401 Unauthorized` response +* `ForbiddenException` will return a `403 Forbidden` response +* `OutOfBoundsException` and `NotFoundException` will return a `404 Not Found` response +* `MethodNotAllowedException` will return a `405 Method Not Allowed` response +* `ConflictException` will return a `409 Conflict` response +* `ExpiredException` will return a `410 Gone` response +* `MailException`, `RuntimeException` and the generic `Exception` will return a `500 Internal Server Error` response + +## How to extend? + +In this example we will create a custom exception called `CustomException`, place it next to the already existing custom +exceptions (you can use your preferred location) and finally return a custom HTTP status code when `CustomException` is +encountered. + +### Step 1: Create exception file + +Navigate to the directory `src/App/src/Handler/Exception` and create a PHP class called `CustomException.php`. +Open `CustomException.php` and add the following content: + +```php +errorResponse($exception->getMessage(), StatusCodeInterface::STATUS_IM_A_TEAPOT); +``` + +Save and close the file. + +### Step 5: Test for success + +Again, access your API's home page URL, which should return the same content. +Notice that this time it returns `418 I'm a teapot` HTTP status code. diff --git a/docs/book/v5/flow/default-library-flow.md b/docs/book/v5/flow/default-library-flow.md new file mode 100644 index 0000000..bbc6cef --- /dev/null +++ b/docs/book/v5/flow/default-library-flow.md @@ -0,0 +1,5 @@ +# Default Library Flow + +The graph below demonstrates a default flow between DotKernel's libraries. + +![Dotkernel API Default Library Flow!](https://docs.dotkernel.org/img/api/dotkernel-library-flow.png) diff --git a/docs/book/v5/flow/library-flow-for-email.md b/docs/book/v5/flow/library-flow-for-email.md new file mode 100644 index 0000000..090035c --- /dev/null +++ b/docs/book/v5/flow/library-flow-for-email.md @@ -0,0 +1,5 @@ +# Library Flow for Email + +The graph below demonstrates the simplified flow between DotKernel's libraries for sending an email. + +![Dotkernel API Default Library Flow!](https://docs.dotkernel.org/img/api/dotkernel-library-flow-email.png) diff --git a/docs/book/v5/flow/middleware-flow.md b/docs/book/v5/flow/middleware-flow.md new file mode 100644 index 0000000..95f73b4 --- /dev/null +++ b/docs/book/v5/flow/middleware-flow.md @@ -0,0 +1,5 @@ +# Middleware flow + +The graph below demonstrates a default flow between DotKernel's middlewares. + +![Dotkernel API Middleware Flow!](https://docs.dotkernel.org/img/api/dotkernel-middleware-flow.png) diff --git a/docs/book/v5/installation/composer.md b/docs/book/v5/installation/composer.md new file mode 100644 index 0000000..9e20350 --- /dev/null +++ b/docs/book/v5/installation/composer.md @@ -0,0 +1,27 @@ +# Composer Installation of Packages + +## Install dependencies + +```shell +composer install +``` + +## Development mode + +If you're installing the project for development, make sure you have development mode enabled, by running: + +```shell +composer development-enable +``` + +You can disable development mode by running: + +```shell +composer development-disable +``` + +You can check if you have development mode enabled by running: + +```shell +composer development-status +``` diff --git a/docs/book/v5/installation/configuration-files.md b/docs/book/v5/installation/configuration-files.md new file mode 100644 index 0000000..a8cad3b --- /dev/null +++ b/docs/book/v5/installation/configuration-files.md @@ -0,0 +1,23 @@ +# Configuration Files + +## Prepare config files + +* duplicate `config/autoload/cors.local.php.dist` as `config/autoload/cors.local.php` + +### Note + +> if your API will be consumed by another application, make sure to configure the `allowed_origins` variable + +* duplicate `config/autoload/local.php.dist` as `config/autoload/local.php` + +* duplicate `config/autoload/mail.local.php.dist` as `config/autoload/mail.local.php` + +### Note + +> if your API will send emails, make sure to fill in SMTP connection params + +* **optional**: in order to run/create tests, duplicate `config/autoload/local.test.php.dist` as `config/autoload/local.test.php` + +### Note + +> this creates a new in-memory database that your tests will run on. diff --git a/docs/book/v5/installation/doctrine-orm.md b/docs/book/v5/installation/doctrine-orm.md new file mode 100644 index 0000000..f36dcf3 --- /dev/null +++ b/docs/book/v5/installation/doctrine-orm.md @@ -0,0 +1,47 @@ +# Doctrine ORM + +## Setup database + +Make sure you fill out the database credentials in `config/autoload/local.php` under `$databases['default']`. + +Create a new MySQL database - set collation to `utf8mb4_general_ci` + +## Running migrations + +Run the database migrations by using the following command: + +```shell +php vendor/bin/doctrine-migrations migrate +``` + +This command will prompt you to confirm that you want to run it. + +> WARNING! You are about to execute a migration in database "..." that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]: + +Hit `Enter` to confirm the operation. + +## Executing fixtures + +**Fixtures are used to seed the database with initial values and should be executed after migrating the database.** + +To list all the fixtures, run: + +```shell +php bin/doctrine fixtures:list +``` + +This will output all the fixtures in the order of execution. + +To execute all fixtures, run: + +```shell +php bin/doctrine fixtures:execute +``` + +To execute a specific fixture, run: + +```shell +php bin/doctrine fixtures:execute --class=FixtureClassName +``` + +More details on how fixtures work can be found here: https://github.com/dotkernel/dot-data-fixtures#creating-fixtures diff --git a/docs/book/v5/installation/faq.md b/docs/book/v5/installation/faq.md new file mode 100644 index 0000000..6e33591 --- /dev/null +++ b/docs/book/v5/installation/faq.md @@ -0,0 +1,39 @@ +# Frequently Asked Questions + +## How do I fix common permission issues? + +If running your project you encounter some permission issues, follow the below steps. + +### Errors + +> PHP Fatal error: Uncaught InvalidArgumentException: The directory "/var/www/_example.local_/html/data" is not writable... + +> PHP Fatal error: Uncaught InvalidArgumentException: The directory "/var/www/_example.local_/html/data/cache" is not writable... + +> PHP Fatal error: Uncaught InvalidArgumentException: The directory "/var/www/_example.local_/html/data/cache/doctrine" is not writable... + +**Fix:** + +```shell +chmod -R 777 data +``` + +### Error + +> PHP Fatal error: Uncaught InvalidArgumentException: The directory "/var/www/_example.local_/html/public/uploads" is not writable... + +**Fix:** + +```shell +chmod -R 777 public/uploads +``` + +### Error + +> PHP Fatal error: Uncaught ErrorException: fopen(/var/www/_example.local_/config/autoload/../../log/error-log-_yyyy-mm-dd.log_): Failed to open stream: Permission denied... + +**Fix:** + +```shell +chmod -R 777 log +``` diff --git a/docs/book/v5/installation/getting-started.md b/docs/book/v5/installation/getting-started.md new file mode 100644 index 0000000..fc13998 --- /dev/null +++ b/docs/book/v5/installation/getting-started.md @@ -0,0 +1,7 @@ +# Clone the project + +Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the directory is empty before proceeding to the download process. Once there, run the following command: + +```shell +git clone https://github.com/dotkernel/api.git . +``` diff --git a/docs/book/v5/installation/test-the-installation.md b/docs/book/v5/installation/test-the-installation.md new file mode 100644 index 0000000..af8ca3c --- /dev/null +++ b/docs/book/v5/installation/test-the-installation.md @@ -0,0 +1,32 @@ +# Test the installation + +Sending a GET request to the [home page](http://0.0.0.0:8080/) should output the following message: + +> {"message": "DotKernel API version 5"} + +## Old way of doing things, using PHP built-in server + +```shell +php -S 0.0.0.0:8080 -t public +``` + +## Running tests + +The project has 2 types of tests: functional and unit tests, you can run both types at the same type by executing this +command: + +```shell +php vendor/bin/phpunit +``` + +## Running unit tests + +```shell +vendor/bin/phpunit --testsuite=UnitTests --testdox --colors=always +``` + +## Running functional tests + +```shell +vendor/bin/phpunit --testsuite=FunctionalTests --testdox --colors=always +``` diff --git a/docs/book/v5/introduction/file-structure.md b/docs/book/v5/introduction/file-structure.md new file mode 100644 index 0000000..43e4d72 --- /dev/null +++ b/docs/book/v5/introduction/file-structure.md @@ -0,0 +1,61 @@ +# File structure + +Dotkernel API follows the [PSR-4](https://www.php-fig.org/psr/psr-4/) standards. + +It is a good practice to standardize the file structure of projects. + +When using DotKernel API the following structure is installed by default: + +![Dotkernel API File Structure!](https://docs.dotkernel.org/img/api/file-structure-dk-api.png) + +## Main directories + +* `bin` - executable files from CLI +* `config` - various configuration files +* `data` - should contain project-related data (AVOID storing sensitive data on VCS) +* `documentation` - should contain project-related documentation +* `log` - storage of log files generated by dot-error-log library +* `public` - publicly visible files. The webserver need to have this folder as www-document root folder. +* `src` - should contain the source code files +* `test` - should contain the test files + +## Special purpose folders + +* `.github` - containes workflow files +* `.laminas-ci` - contains laminas-ci workflow files + +## `src` directory + +This directory contains all source code related to the Module. It should contain following directories, if they’re not empty: + +* Handler - Action classes (similar to Controllers but can only perform one action) +* Entity - For database entities +* Service - Service classes +* Collection - Database entities collections +* Repository - Entity repository folder + +> The above example is just some of the directories a project may include, but these should give you an idea of how the structure should look like. + +Other classes in the `src` directory may include `InputFilter`, `EventListener`, `Helper`, `Command`, `Factory` etc. + +The `src` directory should also contain 2 files: + +* `ConfigProvider.php` - Provides configuration data +* `RoutesDelegator.php` - Module main routes entry file + +## `templates` directory + +This directory contains the template files, used for example to help render e-mail templates. + +> DotKernel API uses twig as Templating Engine. All template files have the extension .html.twig + +## `data` directory + +This directory contains project-related data (such as cache, file uploads) + +We recommend using the following directory structure: + +* `data/cache` - location where caches are stored +* `data/oauth` - encryption, private and public keys needed for authentication. +* `data/doctrine` - fixtures and migrations +* `data/lock` - lock files generated by `dotkernel/dot-cli` [See more](https://docs.dotkernel.org/dot-cli/v3/lock-files/) diff --git a/docs/book/v5/introduction/introduction.md b/docs/book/v5/introduction/introduction.md new file mode 100644 index 0000000..b91a68e --- /dev/null +++ b/docs/book/v5/introduction/introduction.md @@ -0,0 +1,110 @@ +# Introduction + +Based on Enrico Zimuel’s Zend Expressive API – Skeleton example, DotKernel API runs on Laminas and Mezzio components and implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15. + +Here is a list of the core components: + +* Middleware Microframework (mezzio/mezzio) +* Error Handler (dotkernel/dot-errorhandler) +* Problem Details (mezzio/mezzio-problem-details) +* CORS (mezzio/mezzio-cors) +* Routing (mezzio/mezzio-fastroute) +* Authentication (mezzio/mezzio-authentication) +* Authorization (mezzio/mezzio-authorization) +* Config Aggregator (laminas/laminas-config-aggregator) +* Container (roave/psr-container-doctrine) +* Annotations (dotkernel/dot-annotated-services) +* Input Filter (laminas/laminas-inputfilter) +* Doctrine 2 ORM (doctrine/orm) +* Serializer/Deserializer (laminas/laminas-hydrator) +* Paginator (laminas/laminas-paginator) +* HAL (mezzio/mezzio-hal) +* CLI (dotkernel/dot-cli) +* TwigRenderer (mezzio/mezzio-twigrenderer) +* Fixtures (dotkernel/dot-data-fixtures) +* UUID (ramsey/uuid-doctrine) + +## Doctrine 2 ORM + +For the persistence in a relational database management system we chose Doctrine ORM (object-relational mapper). + +The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary priority. + +## Documentation + +Our documentation is Postman based. We use the following files in which we store information about every available endpoint ready to be tested: + +* documentation/DotKernel_API.postman_collection.json +* documentation/DotKernel_API.postman_environment.json + +## Hypertext Application Language + +For our API payloads (a value object for describing the API resource, its relational links and any embedded/child resources related to it) we chose mezzio-hal. + +## CORS + +By using `MezzioCorsMiddlewareCorsMiddleware`, the CORS preflight will be recognized and the middleware will start to detect the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods. Therefore, for every preflight request, there is at least one Router request. + +## OAuth 2.0 + +OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on your DotKernel API. We are using mezzio/mezzio-authentication-oauth2 which provides OAuth 2.0 authentication for Mezzio and PSR-7/PSR-15 applications by using league/oauth2-server package. + +## Email + +It is not unlikely for an API to send emails depending on the use case. Here is another area where DotKernel API shines. Using `DotMailServiceMailService` provided by dotkernel/dot-mail you can easily send custom email templates. + +## Configuration + +From authorization at request route level to API keys for your application, you can find every configuration variable in the config directory. + +Registering a new module can be done by including its ConfigProvider.php in config.php. + +Brand new middlewares should go into pipeline.php. Here you can edit the order in which they run and find more info about the currently included ones. + +You can further customize your api within the autoload directory where each configuration category has its own file. + +## Routing + +Each module has a `RoutesDelegator.php` file for managing existing routes inside that specific module. It also allows a quick way of adding new routes by providing the route path, Middlewares that the route will use and the route name. + +You can allocate permissions per route name in order to restrict access for a user role to a specific route in `config/autoload/authorization.global.php`. + +## Commands + +For registering new commands first make sure your command class extends `SymfonyComponentConsoleCommandCommand`. Then you can enable it by registering it in `config/autoload/cli.global.php`. + +## File locker + +Here you will also find our brand-new file locker configuration, so you can easily turn it on or off (by default: `'enabled' => true`). + +Note: The File Locker System will create a `command-{command-default-name}.lock` file which will not let another instance of the same command to run until the previous one has finished. + +## PSR Standards + +* [PSR-3](https://www.php-fig.org/psr/psr-3/): Logger Interface – the application uses `LoggerInterface` for error logging +* [PSR-4](https://www.php-fig.org/psr/psr-4): Autoloader – the application locates classes using an autoloader +* [PSR-7](https://www.php-fig.org/psr/psr-7): HTTP message interfaces – the handlers return `ResponseInterface` +* [PSR-11](https://www.php-fig.org/psr/psr-11): Container interface – the application is container-based +* [PSR-15](https://www.php-fig.org/psr/psr-15): HTTP Server Request Handlers – the handlers implement `RequestHandlerInterface` + +## Tests + +One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find factory-made tests in the tests/AppTest/ folder, and you can also register your own. + +We have 2 types of tests: functional and unit tests, you can run both types at the same type by executing this command: + +```shell +php vendor/bin/phpunit +``` + +## Running unit tests + +```shell +vendor/bin/phpunit --testsuite=UnitTests --testdox --colors=always +``` + +## Running functional tests + +```shell +vendor/bin/phpunit --testsuite=FunctionalTests --testdox --colors=always +``` diff --git a/docs/book/v5/introduction/packages.md b/docs/book/v5/introduction/packages.md new file mode 100644 index 0000000..d64ca86 --- /dev/null +++ b/docs/book/v5/introduction/packages.md @@ -0,0 +1,30 @@ +# Packages + +* `dotkernel/dot-annotated-services` - Dependency injection component using class attributes. +* `dotkernel/dot-cache` - Cache component extending symfony-cache +* `dotkernel/dot-cli` - Component for creating console applications based on laminas-cli +* `dotkernel/dot-data-fixtures` - Provides a CLI interface for listing & executing doctrine data fixtures +* `dotkernel/dot-errorhandler` - Logging Error Handler for Middleware Applications +* `dotkernel/dot-mail` - Mail component based on laminas-mail +* `dotkernel/dot-response-header` - Middleware for setting custom response headers. +* `laminas/laminas-component-installer` - Composer plugin for injecting modules and configuration providers into application configuration +* `laminas/laminas-config` - Provides a nested object property based user interface for accessing this configuration data within application code +* `laminas/laminas-config-aggregator` - Lightweight library for collecting and merging configuration from different sources +* `laminas/laminas-http` - Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests +* `laminas/laminas-hydrator` - Serialize objects to arrays, and vice versa +* `laminas/laminas-inputfilter` - Normalize and validate input sets from the web, APIs, the CLI, and more, including files +* `laminas/laminas-paginator` - Paginate collections of data from arbitrary sources +* `laminas/laminas-stdlib` - SPL extensions, array utilities, error handlers, and more +* `laminas/laminas-text` - Create FIGlets and text-based tables +* `mezzio/mezzio` - PSR-15 Middleware Microframework +* `mezzio/mezzio-authentication-oauth2` - OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications +* `mezzio/mezzio-authorization-acl` - laminas-permissions-acl adapter for mezzio-authorization +* `mezzio/mezzio-authorization-rbac` - mezzio authorization rbac adapter for laminas/laminas-permissions-rbac +* `mezzio/mezzio-cors` - CORS component for Mezzio and other PSR-15 middleware runners +* `mezzio/mezzio-fastroute` - FastRoute integration for Mezzio +* `mezzio/mezzio-hal` - Hypertext Application Language implementation for PHP and PSR-7 +* `mezzio/mezzio-problem-details` - Problem Details for PSR-7 HTTP APIs addressing the RFC 7807 standard +* `mezzio/mezzio-twigrenderer` - Twig integration for Mezzio +* `ramsey/uuid-doctrine` - Use ramsey/uuid as a Doctrine field type +* `roave/psr-container-doctrine` - Doctrine Factories for PSR-11 Containers +* `symfony/filesystem` - Provides basic utilities for the filesystem diff --git a/docs/book/v5/introduction/server-requirements.md b/docs/book/v5/introduction/server-requirements.md new file mode 100644 index 0000000..789238b --- /dev/null +++ b/docs/book/v5/introduction/server-requirements.md @@ -0,0 +1,35 @@ +# Server Requirements + +For production, we highly recommend a *nix based system. + +## Webserver + +* Apache >= 2.2 **or** Nginx +* mod_rewrite +* .htaccess support `(AllowOverride All)` + +## PHP >= 8.2 + +Both mod_php and FCGI (FPM) are supported. + +## Required Settings and Modules & Extensions + +* memory_limit >= 128M +* upload_max_filesize and post_max_size >= 100M (depending on your data) +* mbstring +* CLI SAPI (for Cron Jobs) +* Composer (added to $PATH) + +## RDBMS + +* MySQL / MariaDB >= 5.5.3 + +## Recommended extensions + +* opcache +* pdo_mysql or mysqli (if using MySQL or MariaDB as RDBMS) +* dom - if working with markup files structure (html, xml, etc) +* simplexml - working with xml files +* gd, exif - if working with images +* zlib, zip, bz2 - if compessing files +* curl (required if APIs are used) diff --git a/docs/book/v5/transition-from-api-tools/api-tools-vs-dotkernel-api.md b/docs/book/v5/transition-from-api-tools/api-tools-vs-dotkernel-api.md new file mode 100644 index 0000000..8ef9023 --- /dev/null +++ b/docs/book/v5/transition-from-api-tools/api-tools-vs-dotkernel-api.md @@ -0,0 +1,26 @@ +# Laminas API Tools compared to DotKernel API + +| | API Tools (formerly Apigility) | DotKernel API | +|---------------------|------------------------------------------------|---------------------------------------------------------------------------------------| +| URL | [api-tools](https://api-tools.getlaminas.org/) | [Dotkernel API](https://www.dotkernel.org) | +| First Release | 2012 | 2018 | +| PHP Version | <= 8.2 | >= 8.1 | +| Architecture | MVC, Event Driven | Middleware | +| OSS Lifecycle | Archived | ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/api?style=flat&label=) | +| Style | REST, RPC | REST | +| Versioning | Yes | Deprecations * | +| Documentation | Swagger (Automated) | Postman (Manual) * | +| Content-Negotiation | Custom | Custom | +| License | BSD-3 | MIT | +| Default DB Layer | laminas-db | doctrine-orm | +| Authorization | ACL | RBAC-guard | +| Authentication | HTTP Basic/Digest OAuth2.0 | OAuth2.0 | +| CI/CD | Yes | Yes | +| Unit Tests | Yes | Yes | +| Endpoint Generator | Yes | Under development | +| PSR | PSR-7 | PSR-7, PSR-15 | + +## Note + +> * Versioning is replaced by Deprecations, using evolution strategy +> * Version 5 ([Roadmap](https://github.com/orgs/dotkernel/projects/15/views/1)) will implement OpenAPi 3.0 diff --git a/docs/book/v5/transition-from-api-tools/discovery-phase.md b/docs/book/v5/transition-from-api-tools/discovery-phase.md new file mode 100644 index 0000000..6a9aa0f --- /dev/null +++ b/docs/book/v5/transition-from-api-tools/discovery-phase.md @@ -0,0 +1,40 @@ +# Discovery phase for a current system built using API Tools [WIP] + +In order to transition a system built using api-tools to Dotkernel API , we need to analyze the core components +of it. + +## Database + +- there is a database in the current API ? +- which is the connection to database +- which library is used for database interaction ( laminas-db, doctrine 2, eloquent, or else ) + +### Note + +> Dotkernel API is tested only with MariaDB version 10.6 and 10.11 LTS + +## Authentication and Authorization + +- how authentication is done ? (basic, digest, oauth2, etc.) +- how authorization is done ? (acl, rbac) + +## Modules + +- analyze configuration files of the modules (what needs to be configured in order to use a module) +- analyze routes (which are the routes, protection rules, which one need auth, etc.) +- analyze response format (content negotiation and validation, which ones are json, hal, views, etc.) +- analyze input field validations + +## Custom functionalities + +Analyze the custom code (code that cannot be generated through Admin UI and require manual implementation) + +For instance: + +- caching +- events +- services +- extra installed packages and libraries +- jobs and queues +- third-parties +- tests diff --git a/docs/book/v5/transition-from-api-tools/transition-approach.md b/docs/book/v5/transition-from-api-tools/transition-approach.md new file mode 100644 index 0000000..a55fb0a --- /dev/null +++ b/docs/book/v5/transition-from-api-tools/transition-approach.md @@ -0,0 +1,21 @@ +# Transition approach [WIP] + +Dotkernel API is not a one-to-one replacement of api-tools ( former Apigility), but is only a potential solution to +migrate to. + +Functionalities, components and architecture are different. + +See +the [Comparison between Dotkernel APi and api-tools](https://docs.dotkernel.org/api-documentation/v4/transition-from-api-tools/api-tools-vs-dotkernel-api/) + +## Business cases + +There are at least 2 approaches for this transition: + +### Clone 1:1 + +and recreate all endpoints and entities + +### Build a new version of the current API using Dotkernel API + +and keep it running as separate platforms until the sunset of the current version of api-tools diff --git a/docs/book/v5/tutorials/create-book-module.md b/docs/book/v5/tutorials/create-book-module.md new file mode 100644 index 0000000..4dbc9d3 --- /dev/null +++ b/docs/book/v5/tutorials/create-book-module.md @@ -0,0 +1,653 @@ +# Implementing a book module in DotKernel API + +## File structure + +The below file structure is just an example, you can have multiple components such as event listeners, wrappers, etc. + +```markdown +. +└── src/ + └── Book/ + └── src/ + ├── Collection/ + │ └── BookCollection.php + ├── Entity/ + │ └── Book.php + ├── Handler/ + │ └── BookHandler.php + ├── InputFilter/ + │ ├── Input/ + │ │ ├── AuthorInput.php + │ │ ├── NameInput.php + │ │ └── ReleaseDateInput.php + │ └── BookInputFilter.php + ├── Repository/ + │ └── BookRepository.php + ├── Service/ + │ ├── BookService.php + │ └── BookServiceInterface.php + ├── ConfigProvider.php + └── RoutesDelegator.php +``` + +* `src/Book/src/Collection/BookCollection.php` - a collection refers to a container for a group of related objects, typically used to manage sets of related entities fetched from a database +* `src/Book/src/Entity/Book.php` - an entity refers to a PHP class that represents a persistent object or data structure +* `src/Book/src/Handler/BookHandler.php` - handlers are middleware that can handle requests based on an action +* `src/Book/src/Repository/BookRepository.php` - a repository is a class responsible for querying and retrieving entities from the database +* `src/Book/src/Service/BookService.php` - is a class or component responsible for performing a specific task or providing functionality to other parts of the application +* `src/Book/src/ConfigProvider.php` - is a class that provides configuration for various aspects of the framework or application +* `src/Book/src/RoutesDelegator.php` - a routes delegator is a delegator factory responsible for configuring routing middleware based on routing configuration provided by the application +* `src/Book/src/InputFilter/BookInputFilter.php` - input filters and validators +* `src/Book/src/InputFilter/Input/*` - input filters and validator configurations + +## File creation and contents + +* `src/Book/src/Collection/BookCollection.php` + +```php +setName($name); + $this->setAuthor($author); + $this->setReleaseDate($releaseDate); + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function setAuthor(string $author): self + { + $this->author = $author; + + return $this; + } + + public function getReleaseDate(): DateTimeImmutable + { + return $this->releaseDate; + } + + public function setReleaseDate(DateTimeImmutable $releaseDate): self + { + $this->releaseDate = $releaseDate; + + return $this; + } + + public function getArrayCopy(): array + { + return [ + 'uuid' => $this->getUuid()->toString(), + 'name' => $this->getName(), + 'author' => $this->getAuthor(), + 'releaseDate' => $this->getReleaseDate(), + ]; + } +} +``` + +* `src/Book/src/Repository/BookRepository.php` + +```php + + */ +class BookRepository extends EntityRepository +{ + public function saveBook(Book $book): Book + { + $this->getEntityManager()->persist($book); + $this->getEntityManager()->flush(); + + return $book; + } + + public function getBooks(array $filters = []): BookCollection + { + $page = PaginationHelper::getOffsetAndLimit($filters); + + $qb = $this + ->getEntityManager() + ->createQueryBuilder() + ->select('book') + ->from(Book::class, 'book') + ->orderBy($filters['order'] ?? 'book.created', $filters['dir'] ?? 'desc') + ->setFirstResult($page['offset']) + ->setMaxResults($page['limit']); + + $qb->getQuery()->useQueryCache(true); + + return new BookCollection($qb, false); + } +} +``` + +* `src/Book/src/Service/BookService.php` + +```php +bookRepository->saveBook($book); + } + + public function getBooks(array $filters = []) + { + return $this->bookRepository->getBooks($filters); + } +} +``` + +* `src/Book/src/Service/BookServiceInterface.php` + +```php + $this->getDependencies(), + MetadataMap::class => $this->getHalConfig(), + ]; + } + + public function getDependencies(): array + { + return [ + 'factories' => [ + BookHandler::class => AnnotatedServiceFactory::class, + BookService::class => AnnotatedServiceFactory::class, + BookRepository::class => AnnotatedRepositoryFactory::class, + ], + 'aliases' => [ + BookServiceInterface::class => BookService::class, + ], + ]; + } + + public function getHalConfig(): array + { + return [ + AppConfigProvider::getCollection(BookCollection::class, 'books.list', 'books'), + AppConfigProvider::getResource(Book::class, 'book.create'), + ]; + } +} +``` + +* `src/Book/src/RoutesDelegator.php` + +```php +get( + '/books', + BookHandler::class, + 'books.list' + ); + + $app->post( + '/book', + BookHandler::class, + 'book.create' + ); + + return $app; + } +} +``` + +* `src/Book/src/InputFilter/BookInputFilter.php` + +```php +add(new NameInput('name')); + $this->add(new AuthorInput('author')); + $this->add(new ReleaseDateInput('releaseDate')); + } +} +``` + +* `src/Book/src/InputFilter/Input/AuthorInput.php` + +```php +setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'author'), + ], true); + } +} +``` + +* `src/Book/src/InputFilter/Input/NameInput.php` + +```php +setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'name'), + ], true); + } +} +``` + +* `src/Book/src/InputFilter/Input/ReleaseDateInput.php` + +```php +setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(Date::class, [ + 'message' => sprintf(Message::INVALID_VALUE, 'releaseDate'), + ], true); + } +} +``` + +* `src/Book/src/Handler/BookHandler.php` + +```php +bookService->getBooks($request->getQueryParams()); + + return $this->createResponse($request, $books); + } + + public function post(ServerRequestInterface $request): ResponseInterface + { + $inputFilter = (new BookInputFilter())->setData($request->getParsedBody()); + if (! $inputFilter->isValid()) { + return $this->errorResponse($inputFilter->getMessages()); + } + + $book = $this->bookService->createBook($inputFilter->getValues()); + + return $this->createResponse($request, $book); + } +} +``` + +## Configuring and registering the new module + +Once you set up all the files as in the example above, you will need to do a few additional configurations: + +* register the namespace by adding this line `"Api\\Book\\": "src/Book/src/",` in `composer.json` under the `autoload.psr-4` key +* register the module by adding `Api\Book\ConfigProvider::class,` under `Api\User\ConfigProvider::class,` +* register the module's routes by adding `\Api\Book\RoutesDelegator::class,` under `\Api\User\RoutesDelegator::class,` in `src/App/src/ConfigProvider.php` +* update Composer autoloader by running the command: + +```shell +composer dump-autoload +``` + +It should look like this: + +```php +public function getDependencies(): array +{ + return [ + 'delegators' => [ + Application::class => [ + RoutesDelegator::class, + \Api\Admin\RoutesDelegator::class, + \Api\User\RoutesDelegator::class, + \Api\Book\RoutesDelegator::class, + ], + ], + 'factories' => [ + ... + ] + ... +``` + +* In `src/config/autoload/doctrine.global.php` add this under the `doctrine.driver` key: + +```php +'BookEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/Book/src/Entity', +], +``` + +* `Api\\Book\Entity' => 'BookEntities',` add this under the `doctrine.driver.drivers` key + +Example: + +```php + [ + ... + 'driver' => [ + 'orm_default' => [ + 'class' => MappingDriverChain::class, + 'drivers' => [ + 'Api\\App\Entity' => 'AppEntities', + 'Api\\Admin\\Entity' => 'AdminEntities', + 'Api\\User\\Entity' => 'UserEntities', + 'Api\\Book\Entity' => 'BookEntities', + ], + ], + 'AdminEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/Admin/src/Entity', + ], + 'UserEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/User/src/Entity', + ], + 'AppEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/App/src/Entity', + ], + 'BookEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/Book/src/Entity', + ], + ], + ... +``` + +Next we need to configure access to the newly created endpoints, add `books.list` and `book.create` to the authorization rbac array, under the `UserRole::ROLE_GUEST` key. +> Make sure you read and understand the rbac documentation. + +## Migrations + +We created the `Book` entity, but we didn't create the associated table for it. + +Doctrine can handle the table creation, run the following command: + +```shell +vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/' +``` + +This will check for differences between your entities and database structure and create migration files if necessary, in `data/doctrine/migrations`. + +To execute the migrations run: + +```shell +vendor/bin/doctrine-migrations migrate +``` + +## Checking endpoints + +If we did everything as planned we can call the `http://0.0.0.0:8080/book` endpoint and create a new book: + +```shell +curl -X POST http://0.0.0.0:8080/book + -H "Content-Type: application/json" + -d '{"name": "test", "author": "author name", "releaseDate": "2023-03-03"}' +``` + +To list the books use: + +```shell +curl http://0.0.0.0:8080/books +``` diff --git a/docs/book/v5/tutorials/token-authentication.md b/docs/book/v5/tutorials/token-authentication.md new file mode 100644 index 0000000..8ff01ea --- /dev/null +++ b/docs/book/v5/tutorials/token-authentication.md @@ -0,0 +1,360 @@ +# Token authentication + +## What is token authentication? + +Token authentication means making a request to an API endpoint while also sending a special header that contains an +access token. The access token was previously generated by (usually) the same API as the one you are sending requests to +and it consists of an alphanumeric string. + +## How does it work? + +In order to protect specific resources, clients need to be authenticated with user/admin roles. +These roles are identified from the access token sent via the `Authorization` header. + +When DotKernel API receives a request, it tries to read the access token. + +If it does not find an access token, client has `guest` role: + +- if the requested endpoint needs no authentication, the requested resource is returned +- else, a `403 Forbidden` response is returned + +Else, client's account is identified and client has `admin`/`user` role (the one assigned in their account) + +- if the requested endpoint is accessible to the client, the requested resource is returned +- else, a `403 Forbidden` response is returned + +DotKernel API provides out-of-the-box both an `admin` and a `user` account. + +The admin account with **role** set to both `superuser` and `admin` with the following credentials: + +- **identity**: `admin` +- **password**: `dotkernel` + +The user account with **role** set to both `user` and `guest` with the following credentials: + +- **identify**: `test@dotkernel.com` +- **password**: `dotkernel` + +## Flow + +- client sends API request with credentials +- API returns a JSON object containing a new access and refresh token +- client sends API request using `Authentication` header containing the previously generated access token +- API returns requested resource + +### Note + +> The first two steps need to executed only once. +> Access token should be stored and reused for all upcoming requests. +> Refresh token should be stored and used to refresh expired access token. + +For a better overview of the flow, see the below image: + +![Token authentication flow](https://docs.dotkernel.org/img/api/token-authentication.png) + +## Generate admin access token + +Send a `POST` request to the `/security/generate-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "password", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "username": "", + "password": "" +} +``` + +### Note + +> Replace `` with your admin account's `identity` and `` with your admin account's `password`. +> Both fields come from table `admin`. + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "username": "admin", + "password": "dotkernel" +}' +``` + +## Generate user access token + +Send a `POST` request to the `/security/generate-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "", + "password": "" +} +``` + +### Note + +> Replace `` with your user account's `identity` and `` with your user account's `password`. +> Both fields come from table `user`. + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "test@dotkernel.com", + "password": "dotkernel" +}' +``` + +### Response on success + +You should see a `200 OK` response with the following JSON body: + +```json +{ + "token_type": "Bearer", + "expires_in": 86400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e...wuE39ON1mS5mnTKfA_dSpSWxOmNQdny_AKIbc1qZjMfS24qSUV8HIoOw", + "refresh_token": "def502005a035c8dfe5456d27e85069813a4f8...0b844e843cd62865662a0e723165752dfd7012491502d3d819c2a61d" +} +``` + +Field description: + +- `token_type`: token type to be set when sending the `Authorization` header (example: `Authorization: Bearer eyJ0e...`) +- `expires_in`: access token lifetime (modify in: `config/autoload/local.php` > `authentication`.`access_token_expire`) +- `access_token`: generated access token (store it for later use) +- `refresh_token`: generated refresh token (store it for regenerating expired access token) + +### Response on failure + +You should see a `400 Bad Request` response with the following JSON body: + +```json +{ + "error": "Invalid credentials.", + "error_description": "Invalid credentials.", + "message": "Invalid credentials." +} +``` + +## Refresh admin access token + +Send a `POST` request to the `/security/refresh-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "refresh_token", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "refresh_token": "" +} +``` + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/refresh-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "refresh_token", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "refresh_token": "" +}' +``` + +### Note + +> Make sure you replace `` with the refresh token generated with the access token. + +## Refresh user access token + +Send a `POST` request to the `/security/refresh-token` endpoint with `Content-Type` header set to `application/json`. + +Set request body to: + +```json +{ + "grant_type": "refresh_token", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "refresh_token": "" +} +``` + +### Test using curl + +Execute the below command: + +```shell +curl --location 'https://api.dotkernel.net/security/refresh-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "refresh_token", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "refresh_token": "" +}' +``` + +### Note + +> Make sure you replace `` with the refresh token generated with the access token. + +### Response on success + +You should see a `200 OK` response with the following JSON body: + +```json +{ + "token_type": "Bearer", + "expires_in": 86400, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e...wuE39ON1mS5mnTKfA_dSpSWxOmNQdny_AKIbc1qZjMfS24qSUV8HIoOw", + "refresh_token": "def502005a035c8dfe5456d27e85069813a4f8...0b844e843cd62865662a0e723165752dfd7012491502d3d819c2a61d" +} +``` + +Field description: + +- `token_type`: token type to be set when sending the `Authorization` header (example: `Authorization: Bearer eyJ0e...`) +- `expires_in`: access token lifetime (change here: `config/autoload/local.php` `authentication`->`access_token_expire`) +- `access_token`: generated access token (store it for later use) +- `refresh_token`: generated refresh token (store it for regenerating expired access token) + +### Response on failure + +You should see a `401 Unauthorized` response with the following JSON body: + +```json +{ + "error": "invalid_request", + "error_description": "The refresh token is invalid.", + "hint": "Cannot decrypt the refresh token", + "message": "The refresh token is invalid." +} +``` + +## Test admin authentication flow + +### Step 1: Fail to fetch protected API content + +Try to view your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/admin/my-account' +``` + +You should get a `403 Forbidden` JSON response. + +### Step 2: Generate access token + +Generate admin access token by executing: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "admin", + "client_secret": "admin", + "scope": "api", + "username": "admin", + "password": "dotkernel" +}' +``` + +You should get a `200 OK` JSON response. + +Store the value of `access_token` for later use. + +### Step 3: Successfully fetch protected API content + +Try again viewing your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/admin/my-account' \ +--header 'Authorization: Bearer ' +``` + +Replace `` with the previously stored access token. + +You should get a `200 OK` JSON response with the requested resource in the body. + +## Test user authentication flow + +### Step 1: Fail to fetch protected API content + +Try to view your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/user/my-account' +``` + +You should get a `403 Forbidden` JSON response. + +### Step 2: Generate access token + +Generate admin access token by executing: + +```shell +curl --location 'https://api.dotkernel.net/security/generate-token' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "grant_type": "password", + "client_id": "frontend", + "client_secret": "frontend", + "scope": "api", + "username": "test@dotkernel.com", + "password": "dotkernel" +}' +``` + +You should get a `200 OK` JSON response. + +Store the value of `access_token` for later use. + +### Step 3: Successfully fetch protected API content + +Try again viewing your admin account by executing: + +```shell +curl --location 'https://api.dotkernel.net/user/my-account' \ +--header 'Authorization: Bearer ' +``` + +Replace `` with the previously stored access token. + +You should get a `200 OK` JSON response with the requested resource in the body. diff --git a/mkdocs.yml b/mkdocs.yml index f55695d..fff070d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,11 +2,47 @@ docs_dir: docs/book site_dir: docs/html extra: project: API - current_version: v4 + current_version: v5 versions: - v4 + - v5 nav: - Home: index.md + - v5: + - Introduction: + - "Introduction": v5/introduction/introduction.md + - "Server Requirements": v5/introduction/server-requirements.md + - "File Structure": v5/introduction/file-structure.md + - "Packages": v5/introduction/packages.md + - Installation: + - "Getting Started": v5/installation/getting-started.md + - "Composer": v5/installation/composer.md + - "Configuration Files": v5/installation/configuration-files.md + - "Doctrine ORM": v5/installation/doctrine-orm.md + - "Test the Installation": v5/installation/test-the-installation.md + - "FAQ": v5/installation/faq.md + - Flow: + - "Middleware Flow": v5/flow/middleware-flow.md + - "Default Library Flow": v5/flow/default-library-flow.md + - "Library Flow for Email": v5/flow/library-flow-for-email.md + - Core Features: + - "Authentication": v5/core-features/authentication.md + - "Authorization": v5/core-features/authorization.md + - "Content Validation": v5/core-features/content-validation.md + - "Exceptions": v5/core-features/exceptions.md + - "CORS": v5/core-features/cors.md + - Commands: + - "Create admin account": v5/commands/create-admin-account.md + - "Generate database migrations": v5/commands/generate-database-migrations.md + - "Display available endpoints": v5/commands/display-available-endpoints.md + - "Generate tokens": v5/commands/generate-tokens.md + - Tutorials: + - "Creating a book module": v5/tutorials/create-book-module.md + - "Token authentication": v5/tutorials/token-authentication.md + - Transition from API Tools: + - "Laminas API Tools vs DotKernel API": v5/transition-from-api-tools/api-tools-vs-dotkernel-api.md + - "Transition Approach": v5/transition-from-api-tools/transition-approach.md + - "Discovery Phase": v5/transition-from-api-tools/discovery-phase.md - v4: - Introduction: - "Introduction": v4/introduction/introduction.md From acabf8b4b902bbabdda060fa7fae82b21d146719 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Thu, 20 Jun 2024 14:44:22 +0300 Subject: [PATCH 11/27] Fixed multiversion docs Signed-off-by: alexmerlin Signed-off-by: arhimede --- .gitignore | 40 ++++------------------------------------ mkdocs.yml | 8 ++++---- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 2b4aea1..20ddb20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,7 @@ -clover.xml -coveralls-upload.json -phpunit.xml - -# Created by .ignore support plugin (hsz.mobi) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# Admin-specific stuff: .idea +docs/html +documentation-theme -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -### Composer template composer.phar -/vendor/ - -# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock +composer.lock +vendor diff --git a/mkdocs.yml b/mkdocs.yml index fff070d..415a517 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,8 +9,8 @@ extra: nav: - Home: index.md - v5: - - Introduction: - - "Introduction": v5/introduction/introduction.md + - Introduction: v5/introduction/introduction.md + - Overview: - "Server Requirements": v5/introduction/server-requirements.md - "File Structure": v5/introduction/file-structure.md - "Packages": v5/introduction/packages.md @@ -44,8 +44,8 @@ nav: - "Transition Approach": v5/transition-from-api-tools/transition-approach.md - "Discovery Phase": v5/transition-from-api-tools/discovery-phase.md - v4: - - Introduction: - - "Introduction": v4/introduction/introduction.md + - Introduction: v4/introduction/introduction.md + - Overview: - "Server Requirements": v4/introduction/server-requirements.md - "File Structure": v4/introduction/file-structure.md - "Packages": v4/introduction/packages.md From 294477afd997501756a4f2d3f321db6ad5efc621 Mon Sep 17 00:00:00 2001 From: Claudiu Pintiuta Date: Thu, 20 Jun 2024 16:02:14 +0300 Subject: [PATCH 12/27] added di docs Signed-off-by: arhimede --- .../v5/core-features/dependency-injection.md | 59 +++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 60 insertions(+) create mode 100644 docs/book/v5/core-features/dependency-injection.md diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md new file mode 100644 index 0000000..8e5755a --- /dev/null +++ b/docs/book/v5/core-features/dependency-injection.md @@ -0,0 +1,59 @@ +# Dependency Injection + +Dependency Injection is a design pattern used in software development to implement inversion of control or in simple +terms is the act of providing dependencies for an object during instantiation. + +In PHP, dependency injection can be implemented in various ways, including through constructor injection, +setter injection, and property injection. + +DotKernel API, through it's +[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package focuses only on constructor +injection. + +## Usage +DotKernel API comes out of the box with [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) +package, which provide all we need for injecting dependencies in any object you want. + +`dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, +added to the constructor of a class. Dependencies are specified as separate parameters of the `#[Inject]` +attribute. + +For our example we will inject a `UserService` and `config` dependencies in a `UseHandler`. + +```php +use Dot\DependencyInjection\Attribute\Inject; + +class UserHandler implements RequestHandlerInterface +{ + #[Inject( + UserService::class, + "config", + )] + public function __construct( + protected UserServiceInterface $userService, + protected array $config, + ) { + } +} +``` + +>If your class needs the value of a specific configuration key, you can specify the path using dot notation: `config.example` + +After register the class in the `ConfigProvider`, under `factories`, using `Dot\DependencyInjection\Factory\AttributedServiceFactory::class` + +```php +public function getDependencies(): array +{ + return [ + 'factories' => [ + UserHandler::class => AttributedServiceFactory::class + ] + ]; +} +``` + +That's it, by registering this, when your object will instantiate from the container, it will automatically resolve +the dependencies needed for you object. + +>Dependencies injection applies to any object within DotKernel API, for example, you could inject dependencies in +> a service and so on, just need to register it in the `ConfigProvider` diff --git a/mkdocs.yml b/mkdocs.yml index 415a517..3f4c143 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ nav: - "Content Validation": v5/core-features/content-validation.md - "Exceptions": v5/core-features/exceptions.md - "CORS": v5/core-features/cors.md + - "Dependency Injection": v5/core-features/dependency-injection.md - Commands: - "Create admin account": v5/commands/create-admin-account.md - "Generate database migrations": v5/commands/generate-database-migrations.md From e9250eef022bd556441e9b22e0cbacc143f29e1f Mon Sep 17 00:00:00 2001 From: Claudiu Pintiuta Date: Thu, 20 Jun 2024 16:04:13 +0300 Subject: [PATCH 13/27] fixed linting Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 8e5755a..1963108 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -4,13 +4,14 @@ Dependency Injection is a design pattern used in software development to impleme terms is the act of providing dependencies for an object during instantiation. In PHP, dependency injection can be implemented in various ways, including through constructor injection, -setter injection, and property injection. +setter injection, and property injection. DotKernel API, through it's [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package focuses only on constructor injection. ## Usage + DotKernel API comes out of the box with [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provide all we need for injecting dependencies in any object you want. From 509f0f081300a9a6255fa5c9988815282b6379e1 Mon Sep 17 00:00:00 2001 From: Claudiu Pintiuta Date: Thu, 20 Jun 2024 21:17:27 +0300 Subject: [PATCH 14/27] changes from review Signed-off-by: arhimede --- .../v5/core-features/dependency-injection.md | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 1963108..2965102 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -1,23 +1,22 @@ # Dependency Injection -Dependency Injection is a design pattern used in software development to implement inversion of control or in simple +Dependency injection is a design pattern used in software development to implement inversion of control or in simple terms is the act of providing dependencies for an object during instantiation. -In PHP, dependency injection can be implemented in various ways, including through constructor injection, -setter injection, and property injection. +In PHP, dependency injection can be implemented in various ways, including through constructor injection, setter +injection, and property injection. -DotKernel API, through it's -[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package focuses only on constructor -injection. +DotKernel API, through it's [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package +focuses only on constructor injection. ## Usage -DotKernel API comes out of the box with [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) -package, which provide all we need for injecting dependencies in any object you want. +DotKernel API comes out of the box with +[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection)package, which provide all we need for +injecting dependencies in any object you want. `dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, -added to the constructor of a class. Dependencies are specified as separate parameters of the `#[Inject]` -attribute. +added to the constructor of a class. Dependencies are specified as separate parameters of the `#[Inject]` attribute. For our example we will inject a `UserService` and `config` dependencies in a `UseHandler`. @@ -38,9 +37,11 @@ class UserHandler implements RequestHandlerInterface } ``` ->If your class needs the value of a specific configuration key, you can specify the path using dot notation: `config.example` +> If your class needs the value of a specific configuration key, you can specify the path using dot notation: +> `config.example` -After register the class in the `ConfigProvider`, under `factories`, using `Dot\DependencyInjection\Factory\AttributedServiceFactory::class` +After, register the class in the `ConfigProvider`, under `factories`, using +`Dot\DependencyInjection\Factory\AttributedServiceFactory::class` ```php public function getDependencies(): array @@ -53,8 +54,8 @@ public function getDependencies(): array } ``` -That's it, by registering this, when your object will instantiate from the container, it will automatically resolve -the dependencies needed for you object. +That's it. By registering this, when your object will be instantiated from the container, it will automatically have +its dependencies resolved. ->Dependencies injection applies to any object within DotKernel API, for example, you could inject dependencies in +> Dependencies injection applies to any object within DotKernel API, for example, you could inject dependencies in > a service and so on, just need to register it in the `ConfigProvider` From f32260b4ca92b9a92b006d7e8314cdb3daaf86ff Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 09:56:03 +0300 Subject: [PATCH 15/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 2965102..d36962c 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -12,7 +12,7 @@ focuses only on constructor injection. ## Usage DotKernel API comes out of the box with -[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection)package, which provide all we need for +[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provide all we need for injecting dependencies in any object you want. `dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, From 2531476a958358a06852ccb1be3ffabeaf7da3d0 Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 09:58:13 +0300 Subject: [PATCH 16/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index d36962c..3d86068 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -15,7 +15,7 @@ DotKernel API comes out of the box with [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provide all we need for injecting dependencies in any object you want. -`dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, +`dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, added to the constructor added to the constructor of a class. Dependencies are specified as separate parameters of the `#[Inject]` attribute. For our example we will inject a `UserService` and `config` dependencies in a `UseHandler`. From 9ba24b37e27ae3199efc9a8e95d87fe8af22f8d7 Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 09:58:57 +0300 Subject: [PATCH 17/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 3d86068..b6f1b36 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -16,7 +16,7 @@ DotKernel API comes out of the box with injecting dependencies in any object you want. `dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, added to the constructor -added to the constructor of a class. Dependencies are specified as separate parameters of the `#[Inject]` attribute. +of a class. Dependencies are specified as separate parameters of the `#[Inject]` attribute. For our example we will inject a `UserService` and `config` dependencies in a `UseHandler`. From a0fb134e06d263c68ab8288969f5b937a7447470 Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 10:00:14 +0300 Subject: [PATCH 18/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index b6f1b36..fd894d0 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -54,7 +54,7 @@ public function getDependencies(): array } ``` -That's it. By registering this, when your object will be instantiated from the container, it will automatically have +That's it. By registering this, when your object will be instantiated from the container, it will automatically have its its dependencies resolved. > Dependencies injection applies to any object within DotKernel API, for example, you could inject dependencies in From b8b29e19f1a92b3922cf84531d62f13907649f27 Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 10:00:31 +0300 Subject: [PATCH 19/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index fd894d0..0b0c998 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -55,7 +55,7 @@ public function getDependencies(): array ``` That's it. By registering this, when your object will be instantiated from the container, it will automatically have its -its dependencies resolved. +dependencies resolved. > Dependencies injection applies to any object within DotKernel API, for example, you could inject dependencies in > a service and so on, just need to register it in the `ConfigProvider` From b6a20cae962b43b61ff9db4668fdf264869fc177 Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 10:01:57 +0300 Subject: [PATCH 20/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 0b0c998..bb3435f 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -57,5 +57,5 @@ public function getDependencies(): array That's it. By registering this, when your object will be instantiated from the container, it will automatically have its dependencies resolved. -> Dependencies injection applies to any object within DotKernel API, for example, you could inject dependencies in +> Dependencies injection applies to any object within DotKernel API. For example, you could inject dependencies in a > a service and so on, just need to register it in the `ConfigProvider` From 017c2346060ab89577c11398b9745cb191139f1e Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 10:02:13 +0300 Subject: [PATCH 21/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index bb3435f..9190c3d 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -58,4 +58,4 @@ That's it. By registering this, when your object will be instantiated from the c dependencies resolved. > Dependencies injection applies to any object within DotKernel API. For example, you could inject dependencies in a -> a service and so on, just need to register it in the `ConfigProvider` +> service and so on, just need to register it in the `ConfigProvider` From 984b3782bb4816e58092f7fef937c8373612ff5d Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Fri, 21 Jun 2024 10:03:09 +0300 Subject: [PATCH 22/27] Update docs/book/v5/core-features/dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 9190c3d..855f00a 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -58,4 +58,4 @@ That's it. By registering this, when your object will be instantiated from the c dependencies resolved. > Dependencies injection applies to any object within DotKernel API. For example, you could inject dependencies in a -> service and so on, just need to register it in the `ConfigProvider` +> service, a handler and so on, just need to register it in the `ConfigProvider` From cfdb6053986ced97e96dbc60ac755feb016bb74b Mon Sep 17 00:00:00 2001 From: arhimede Date: Fri, 21 Jun 2024 12:20:26 +0300 Subject: [PATCH 23/27] Update dependency-injection.md Signed-off-by: arhimede --- docs/book/v5/core-features/dependency-injection.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index 855f00a..feb4efd 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -3,7 +3,7 @@ Dependency injection is a design pattern used in software development to implement inversion of control or in simple terms is the act of providing dependencies for an object during instantiation. -In PHP, dependency injection can be implemented in various ways, including through constructor injection, setter +In PHP, dependency injection can be implemented in various ways, including through **constructor** injection, setter injection, and property injection. DotKernel API, through it's [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package @@ -11,8 +11,8 @@ focuses only on constructor injection. ## Usage -DotKernel API comes out of the box with -[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provide all we need for +**DotKernel API** comes out of the box with +[dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provides all we need for injecting dependencies in any object you want. `dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, added to the constructor From 7f0a47afedd0ab4d2922da5a9ae2b3a85028b2a2 Mon Sep 17 00:00:00 2001 From: bidi47 Date: Fri, 21 Jun 2024 12:25:38 +0300 Subject: [PATCH 24/27] Update dependency-injection.md Signed-off-by: arhimede --- .../v5/core-features/dependency-injection.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/book/v5/core-features/dependency-injection.md b/docs/book/v5/core-features/dependency-injection.md index feb4efd..b599a88 100644 --- a/docs/book/v5/core-features/dependency-injection.md +++ b/docs/book/v5/core-features/dependency-injection.md @@ -1,24 +1,24 @@ # Dependency Injection -Dependency injection is a design pattern used in software development to implement inversion of control or in simple -terms is the act of providing dependencies for an object during instantiation. +Dependency injection is a design pattern used in software development to implement inversion of control. In simpler +terms, it's the act of providing dependencies for an object during instantiation. -In PHP, dependency injection can be implemented in various ways, including through **constructor** injection, setter -injection, and property injection. +In PHP, dependency injection can be implemented in various ways, including through constructor injection, setter +injection and property injection. -DotKernel API, through it's [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package +DotKernel API, through its [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package focuses only on constructor injection. ## Usage -**DotKernel API** comes out of the box with +**DotKernel API** comes out of the box with the [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provides all we need for -injecting dependencies in any object you want. +injecting dependencies into any object you want. `dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, added to the constructor of a class. Dependencies are specified as separate parameters of the `#[Inject]` attribute. -For our example we will inject a `UserService` and `config` dependencies in a `UseHandler`. +For our example we will inject `UserService` and `config` dependencies into a `UseHandler`. ```php use Dot\DependencyInjection\Attribute\Inject; @@ -40,7 +40,7 @@ class UserHandler implements RequestHandlerInterface > If your class needs the value of a specific configuration key, you can specify the path using dot notation: > `config.example` -After, register the class in the `ConfigProvider`, under `factories`, using +The next step is to register the class in the `ConfigProvider` under `factories` using `Dot\DependencyInjection\Factory\AttributedServiceFactory::class` ```php @@ -54,8 +54,8 @@ public function getDependencies(): array } ``` -That's it. By registering this, when your object will be instantiated from the container, it will automatically have its +That's it. When your object is instantiated from the container, it will automatically have its dependencies resolved. -> Dependencies injection applies to any object within DotKernel API. For example, you could inject dependencies in a -> service, a handler and so on, just need to register it in the `ConfigProvider` +> Dependencies injection is available to any object within DotKernel API. For example, you can inject dependencies in a +> service, a handler and so on, simply by registering it in the `ConfigProvider`. From f4d76c660eb9bc4c3eb8b8482e22d0fbd6aac8ed Mon Sep 17 00:00:00 2001 From: MarioRadu Date: Sat, 22 Jun 2024 16:17:31 +0300 Subject: [PATCH 25/27] updated create book module tutorial to support dependency injection Signed-off-by: arhimede --- docs/book/v5/tutorials/create-book-module.md | 40 +++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/docs/book/v5/tutorials/create-book-module.md b/docs/book/v5/tutorials/create-book-module.md index 4dbc9d3..c54b310 100644 --- a/docs/book/v5/tutorials/create-book-module.md +++ b/docs/book/v5/tutorials/create-book-module.md @@ -157,12 +157,12 @@ use Api\App\Helper\PaginationHelper; use Api\Book\Collection\BookCollection; use Api\Book\Entity\Book; use Doctrine\ORM\EntityRepository; -use Dot\AnnotatedServices\Annotation\Entity; +use Dot\DependencyInjection\Attribute\Entity; /** - * @Entity(name="Api\Book\Entity\Book") * @extends EntityRepository */ + #[Entity(name: Book::class)] class BookRepository extends EntityRepository { public function saveBook(Book $book): Book @@ -204,16 +204,12 @@ namespace Api\Book\Service; use Api\Book\Entity\Book; use Api\Book\Repository\BookRepository; -use Dot\AnnotatedServices\Annotation\Inject; +use Dot\DependencyInjection\Attribute\Inject; use DateTimeImmutable; class BookService implements BookServiceInterface { - /** - * @Inject({ - * BookRepository::class, - * }) - */ + #[Inject(BookRepository::class)] public function __construct(protected BookRepository $bookRepository) { } @@ -265,8 +261,8 @@ use Api\Book\Handler\BookHandler; use Api\Book\Repository\BookRepository; use Api\Book\Service\BookService; use Api\Book\Service\BookServiceInterface; -use Dot\AnnotatedServices\Factory\AnnotatedRepositoryFactory; -use Dot\AnnotatedServices\Factory\AnnotatedServiceFactory; +use Dot\DependencyInjection\Factory\AttributedRepositoryFactory; +use Dot\DependencyInjection\Factory\AttributedServiceFactory; use Mezzio\Hal\Metadata\MetadataMap; use Api\App\ConfigProvider as AppConfigProvider; @@ -284,9 +280,9 @@ class ConfigProvider { return [ 'factories' => [ - BookHandler::class => AnnotatedServiceFactory::class, - BookService::class => AnnotatedServiceFactory::class, - BookRepository::class => AnnotatedRepositoryFactory::class, + BookHandler::class => AttributedServiceFactory::class, + BookService::class => AttributedServiceFactory::class, + BookRepository::class => AttributedRepositoryFactory::class, ], 'aliases' => [ BookServiceInterface::class => BookService::class, @@ -487,19 +483,17 @@ use Mezzio\Hal\ResourceGenerator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Dot\AnnotatedServices\Annotation\Inject; +use Dot\DependencyInjection\Attribute\Inject; class BookHandler implements RequestHandlerInterface { - use ResponseTrait; - - /** - * @Inject({ - * HalResponseFactory::class, - * ResourceGenerator::class, - * BookServiceInterface::class - * }) - */ + use HandlerTrait; + + #[Inject( + HalResponseFactory::class, + ResourceGenerator::class, + BookServiceInterface::class + )] public function __construct( protected HalResponseFactory $responseFactory, protected ResourceGenerator $resourceGenerator, From 5721eb2888d0c73e2ae82eff588e05f619d07438 Mon Sep 17 00:00:00 2001 From: arhimede Date: Mon, 24 Jun 2024 12:28:20 +0300 Subject: [PATCH 26/27] add doctrine 3 for v5 api Signed-off-by: arhimede --- docs/book/v5/introduction/introduction.md | 59 ++++++++++++------- docs/book/v5/introduction/packages.md | 2 +- .../v5/introduction/server-requirements.md | 3 +- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/docs/book/v5/introduction/introduction.md b/docs/book/v5/introduction/introduction.md index b91a68e..7103303 100644 --- a/docs/book/v5/introduction/introduction.md +++ b/docs/book/v5/introduction/introduction.md @@ -1,6 +1,7 @@ # Introduction -Based on Enrico Zimuel’s Zend Expressive API – Skeleton example, DotKernel API runs on Laminas and Mezzio components and implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15. +Based on Enrico Zimuel’s Zend Expressive API – Skeleton example, DotKernel API runs on Laminas and Mezzio components and +implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15. Here is a list of the core components: @@ -13,9 +14,9 @@ Here is a list of the core components: * Authorization (mezzio/mezzio-authorization) * Config Aggregator (laminas/laminas-config-aggregator) * Container (roave/psr-container-doctrine) -* Annotations (dotkernel/dot-annotated-services) +* Dependency Injection (dotkernel/dot-dependency-injection) * Input Filter (laminas/laminas-inputfilter) -* Doctrine 2 ORM (doctrine/orm) +* Doctrine 3 ORM (doctrine/orm) * Serializer/Deserializer (laminas/laminas-hydrator) * Paginator (laminas/laminas-paginator) * HAL (mezzio/mezzio-hal) @@ -24,72 +25,90 @@ Here is a list of the core components: * Fixtures (dotkernel/dot-data-fixtures) * UUID (ramsey/uuid-doctrine) -## Doctrine 2 ORM +## Doctrine 3 ORM For the persistence in a relational database management system we chose Doctrine ORM (object-relational mapper). -The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary priority. +The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about +persistence only as a secondary priority. ## Documentation -Our documentation is Postman based. We use the following files in which we store information about every available endpoint ready to be tested: +Our documentation is Postman based. We use the following files in which we store information about every available +endpoint ready to be tested: * documentation/DotKernel_API.postman_collection.json * documentation/DotKernel_API.postman_environment.json ## Hypertext Application Language -For our API payloads (a value object for describing the API resource, its relational links and any embedded/child resources related to it) we chose mezzio-hal. +For our API payloads (a value object for describing the API resource, its relational links and any embedded/child +resources related to it) we chose mezzio-hal. ## CORS -By using `MezzioCorsMiddlewareCorsMiddleware`, the CORS preflight will be recognized and the middleware will start to detect the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods. Therefore, for every preflight request, there is at least one Router request. +By using `MezzioCorsMiddlewareCorsMiddleware`, the CORS preflight will be recognized and the middleware will start to +detect the proper CORS configuration. The Router is used to detect every allowed request method by executing a route +match with all possible request methods. Therefore, for every preflight request, there is at least one Router request. ## OAuth 2.0 -OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on your DotKernel API. We are using mezzio/mezzio-authentication-oauth2 which provides OAuth 2.0 authentication for Mezzio and PSR-7/PSR-15 applications by using league/oauth2-server package. +OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on your +DotKernel API. We are using mezzio/mezzio-authentication-oauth2 which provides OAuth 2.0 authentication for Mezzio and +PSR-7/PSR-15 applications by using league/oauth2-server package. ## Email -It is not unlikely for an API to send emails depending on the use case. Here is another area where DotKernel API shines. Using `DotMailServiceMailService` provided by dotkernel/dot-mail you can easily send custom email templates. +It is not unlikely for an API to send emails depending on the use case. Here is another area where DotKernel API shines. +Using `DotMailServiceMailService` provided by dotkernel/dot-mail you can easily send custom email templates. ## Configuration -From authorization at request route level to API keys for your application, you can find every configuration variable in the config directory. +From authorization at request route level to API keys for your application, you can find every configuration variable in +the config directory. Registering a new module can be done by including its ConfigProvider.php in config.php. -Brand new middlewares should go into pipeline.php. Here you can edit the order in which they run and find more info about the currently included ones. +Brand new middlewares should go into pipeline.php. Here you can edit the order in which they run and find more info +about the currently included ones. You can further customize your api within the autoload directory where each configuration category has its own file. ## Routing -Each module has a `RoutesDelegator.php` file for managing existing routes inside that specific module. It also allows a quick way of adding new routes by providing the route path, Middlewares that the route will use and the route name. +Each module has a `RoutesDelegator.php` file for managing existing routes inside that specific module. It also allows a +quick way of adding new routes by providing the route path, Middlewares that the route will use and the route name. -You can allocate permissions per route name in order to restrict access for a user role to a specific route in `config/autoload/authorization.global.php`. +You can allocate permissions per route name in order to restrict access for a user role to a specific route +in `config/autoload/authorization.global.php`. ## Commands -For registering new commands first make sure your command class extends `SymfonyComponentConsoleCommandCommand`. Then you can enable it by registering it in `config/autoload/cli.global.php`. +For registering new commands first make sure your command class extends `Symfony\Component\Console\Command\Command`. +Then you can enable it by registering it in `config/autoload/cli.global.php`. ## File locker -Here you will also find our brand-new file locker configuration, so you can easily turn it on or off (by default: `'enabled' => true`). +Here you will also find our brand-new file locker configuration, so you can easily turn it on or off (by +default: `'enabled' => true`). -Note: The File Locker System will create a `command-{command-default-name}.lock` file which will not let another instance of the same command to run until the previous one has finished. +Note: The File Locker System will create a `command-{command-default-name}.lock` file which will not let another +instance of the same command to run until the previous one has finished. ## PSR Standards -* [PSR-3](https://www.php-fig.org/psr/psr-3/): Logger Interface – the application uses `LoggerInterface` for error logging +* [PSR-3](https://www.php-fig.org/psr/psr-3/): Logger Interface – the application uses `LoggerInterface` for error + logging * [PSR-4](https://www.php-fig.org/psr/psr-4): Autoloader – the application locates classes using an autoloader * [PSR-7](https://www.php-fig.org/psr/psr-7): HTTP message interfaces – the handlers return `ResponseInterface` * [PSR-11](https://www.php-fig.org/psr/psr-11): Container interface – the application is container-based -* [PSR-15](https://www.php-fig.org/psr/psr-15): HTTP Server Request Handlers – the handlers implement `RequestHandlerInterface` +* [PSR-15](https://www.php-fig.org/psr/psr-15): HTTP Server Request Handlers – the handlers + implement `RequestHandlerInterface` ## Tests -One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find factory-made tests in the tests/AppTest/ folder, and you can also register your own. +One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find +factory-made tests in the `tests/AppTest/` folder, and you can also register your own. We have 2 types of tests: functional and unit tests, you can run both types at the same type by executing this command: diff --git a/docs/book/v5/introduction/packages.md b/docs/book/v5/introduction/packages.md index d64ca86..fe04427 100644 --- a/docs/book/v5/introduction/packages.md +++ b/docs/book/v5/introduction/packages.md @@ -1,6 +1,6 @@ # Packages -* `dotkernel/dot-annotated-services` - Dependency injection component using class attributes. +* `dotkernel/dot-dependency-injection` - Dependency injection component using class attributes. * `dotkernel/dot-cache` - Cache component extending symfony-cache * `dotkernel/dot-cli` - Component for creating console applications based on laminas-cli * `dotkernel/dot-data-fixtures` - Provides a CLI interface for listing & executing doctrine data fixtures diff --git a/docs/book/v5/introduction/server-requirements.md b/docs/book/v5/introduction/server-requirements.md index 789238b..e490022 100644 --- a/docs/book/v5/introduction/server-requirements.md +++ b/docs/book/v5/introduction/server-requirements.md @@ -22,7 +22,7 @@ Both mod_php and FCGI (FPM) are supported. ## RDBMS -* MySQL / MariaDB >= 5.5.3 +* MariaDB >= 10.11 LTS ## Recommended extensions @@ -33,3 +33,4 @@ Both mod_php and FCGI (FPM) are supported. * gd, exif - if working with images * zlib, zip, bz2 - if compessing files * curl (required if APIs are used) +* sqlite3 - for tests From 263b716d90be793400866d1816f0b91710749df6 Mon Sep 17 00:00:00 2001 From: arhimede Date: Wed, 26 Jun 2024 13:03:34 +0300 Subject: [PATCH 27/27] add WSL instruction Signed-off-by: arhimede --- docs/book/v4/installation/getting-started.md | 8 +++++++- docs/book/v5/installation/getting-started.md | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/book/v4/installation/getting-started.md b/docs/book/v4/installation/getting-started.md index fc13998..6302249 100644 --- a/docs/book/v4/installation/getting-started.md +++ b/docs/book/v4/installation/getting-started.md @@ -1,6 +1,12 @@ # Clone the project -Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the directory is empty before proceeding to the download process. Once there, run the following command: +## Recommended development environment + +> If you are using Windows as OS on your machine, you can use WSL2 as development environment. +> Read more here: [PHP-Mariadb-on-WLS2](https://www.dotkernel.com/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin/) + +Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the +directory is empty before proceeding to the download process. Once there, run the following command: ```shell git clone https://github.com/dotkernel/api.git . diff --git a/docs/book/v5/installation/getting-started.md b/docs/book/v5/installation/getting-started.md index fc13998..6302249 100644 --- a/docs/book/v5/installation/getting-started.md +++ b/docs/book/v5/installation/getting-started.md @@ -1,6 +1,12 @@ # Clone the project -Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the directory is empty before proceeding to the download process. Once there, run the following command: +## Recommended development environment + +> If you are using Windows as OS on your machine, you can use WSL2 as development environment. +> Read more here: [PHP-Mariadb-on-WLS2](https://www.dotkernel.com/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin/) + +Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the +directory is empty before proceeding to the download process. Once there, run the following command: ```shell git clone https://github.com/dotkernel/api.git .