Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 52 additions & 44 deletions docs/book/v7/commands/display-available-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Summary

The `route:list` CLI command inspects the application's routes at runtime and prints every endpoint's request method, route name and path.
The `route:list` CLI command inspects the application's routes at runtime and prints a numbered table of every endpoint's request method, route name and path.
Results can be filtered by name, path or method.

## Usage
Expand All @@ -14,51 +14,54 @@ php ./bin/cli.php route:list
```

The command runs through all routes and extracts endpoint information in realtime.
Rows are sorted by path, then by request method, and UUID route parameters are shown as `{id}` rather than the full regular expression they are declared with.
The count in the table header reflects method/path pairs, so a path answering three methods contributes three rows.

The output should be similar to the following:

```text
+-------------------- 37 Routes ------+-------------------------------------+
| Request method | Route name | Route path |
+----------------+-------------------------------------+-------------------------------------+
| GET | app::view-index | / |
| GET | admin::list-admin | /admin |
| POST | admin::create-admin | /admin |
| GET | admin::view-account | /admin/account |
| PATCH | admin::update-account | /admin/account |
| GET | admin::list-role | /admin/role |
| GET | admin::view-role | /admin/role/{id} |
| DELETE | admin::delete-admin | /admin/{id} |
| GET | admin::view-admin | /admin/{id} |
| PATCH | admin::update-admin | /admin/{id} |
| POST | app::create-error-report | /error-report |
| POST | security::token | /security/token |
| GET | user::list-user | /user |
| POST | user::create-user | /user |
| DELETE | user::delete-account | /user/account |
| GET | user::view-account | /user/account |
| PATCH | user::update-account | /user/account |
| POST | user::create-account | /user/account |
| POST | user::request-activate-account | /user/account/activate |
| PATCH | user::activate-account | /user/account/activate/{hash} |
| DELETE | user::delete-account-avatar | /user/account/avatar |
| GET | user::view-account-avatar | /user/account/avatar |
| POST | user::create-account-avatar | /user/account/avatar |
| POST | user::recover-account | /user/account/recover |
| POST | user::create-account-reset-password | /user/account/reset-password |
| GET | user::check-account-reset-password | /user/account/reset-password/{hash} |
| PATCH | user::update-account-reset-password | /user/account/reset-password/{hash} |
| GET | user::list-role | /user/role |
| GET | user::view-role | /user/role/{id} |
| DELETE | user::delete-user | /user/{id} |
| GET | user::view-user | /user/{id} |
| PATCH | user::update-user | /user/{id} |
| PATCH | user::activate-user | /user/{id}/activate |
| DELETE | user::delete-user-avatar | /user/{id}/avatar |
| GET | user::view-user-avatar | /user/{id}/avatar |
| POST | user::create-user-avatar | /user/{id}/avatar |
| PATCH | user::deactivate-user | /user/{id}/deactivate |
+------+----------------+-------------------- 38 Routes ------+-------------------------------------+
| # | Request method | Route name | Route path |
+------+----------------+-------------------------------------+-------------------------------------+
| 1 | GET | app::view-index | / |
| 2 | GET | admin::list-admin | /admin |
| 3 | POST | admin::create-admin | /admin |
| 4 | GET | admin::view-account | /admin/account |
| 5 | PATCH | admin::update-account | /admin/account |
| 6 | GET | admin::list-role | /admin/role |
| 7 | GET | admin::view-role | /admin/role/{id} |
| 8 | DELETE | admin::delete-admin | /admin/{id} |
| 9 | GET | admin::view-admin | /admin/{id} |
| 10 | PATCH | admin::update-admin | /admin/{id} |
| 11 | POST | app::create-error-report | /error-report |
| 12 | POST | security::generate-token | /security/generate-token |
| 13 | POST | security::refresh-token | /security/refresh-token |
| 14 | GET | user::list-user | /user |
| 15 | POST | user::create-user | /user |
| 16 | DELETE | user::delete-account | /user/account |
| 17 | GET | user::view-account | /user/account |
| 18 | PATCH | user::update-account | /user/account |
| 19 | POST | user::create-account | /user/account |
| 20 | POST | user::request-activate-account | /user/account/activate |
| 21 | PATCH | user::activate-account | /user/account/activate/{hash} |
| 22 | DELETE | user::delete-account-avatar | /user/account/avatar |
| 23 | GET | user::view-account-avatar | /user/account/avatar |
| 24 | POST | user::create-account-avatar | /user/account/avatar |
| 25 | POST | user::recover-account | /user/account/recover |
| 26 | POST | user::create-account-reset-password | /user/account/reset-password |
| 27 | GET | user::check-account-reset-password | /user/account/reset-password/{hash} |
| 28 | PATCH | user::update-account-reset-password | /user/account/reset-password/{hash} |
| 29 | GET | user::list-role | /user/role |
| 30 | GET | user::view-role | /user/role/{id} |
| 31 | DELETE | user::delete-user | /user/{id} |
| 32 | GET | user::view-user | /user/{id} |
| 33 | PATCH | user::update-user | /user/{id} |
| 34 | PATCH | user::activate-user | /user/{id}/activate |
| 35 | DELETE | user::delete-user-avatar | /user/{id}/avatar |
| 36 | GET | user::view-user-avatar | /user/{id}/avatar |
| 37 | POST | user::create-user-avatar | /user/{id}/avatar |
| 38 | PATCH | user::deactivate-user | /user/{id}/deactivate |
+------+----------------+-------------------------------------+-------------------------------------+

```

## Filtering results
Expand All @@ -69,7 +72,11 @@ The following filters can be applied when displaying the route list:
* 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.
The filters are matched as case-sensitive substrings and can be combined.
For example, `php ./bin/cli.php route:list -i avatar` lists only the six avatar routes, and adding `-m GET` narrows that to two.

> Case matters.
> Route names and paths are lowercase and methods are uppercase, so `-i avatar` and `-m GET` match, while `-i Avatar` and `-m get` match nothing and print an empty table.

Get more help by running this command:

Expand All @@ -86,7 +93,8 @@ A: No. The command walks the application's registered routes in realtime, so it
**Q: Which filters are available?**

A: `-i|--name`, `-p|--path` and `-m|--method`.
They are case-insensitive and can be combined.
Each is a case-sensitive substring match, and they can be combined.
Use lowercase for names and paths and uppercase for methods — `-m get` matches nothing.

**Q: Why do route names matter beyond documentation?**

Expand Down
61 changes: 49 additions & 12 deletions docs/book/v7/security/oauth2-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Summary

The security steps to take before an OAuth2-protected Dotkernel API reaches production: remove or re-password the default `admin` and `frontend` OAuth clients, tune access and refresh token lifetimes, and understand how the JWT signing key pair is regenerated and where it must be kept.
The security steps to take before an OAuth2-protected Dotkernel API reaches production: remove or re-password the default `admin` and `frontend` OAuth clients, tune access and refresh token lifetimes, and understand how the JWT signing key pair is generated, why existing keys are preserved, and where they must be kept.

## Details

Expand All @@ -22,19 +22,47 @@ The configuration for OAuth2 tokens can be edited in `config/autoload/local.php`
By default, the lifetimes of the `access` and `refresh` tokens are set to one day and one month respectively.
Make sure to adjust their values in accordance with your application's needs, with lower values being generally safer.

> If your application requires it, you can revoke user OAuth tokens before their expiration by making use of the `revokeTokens` method of `UserService`.
> If your application requires it, you can revoke a user's OAuth tokens before they expire.
> `UserService::revokeTokens()` is `private`, so it cannot be called from your own code; it runs as
> part of the public `UserService::deleteUser()`, which revokes the tokens and then anonymizes the
> account.
>
> To revoke tokens on their own, use the token repositories directly: fetch the user's tokens with
> `OAuthAccessTokenRepository::findAccessTokens($identity)`, then pass each token to
> `OAuthAccessTokenRepository::revokeAccessToken()` and
> `OAuthRefreshTokenRepository::revokeRefreshToken()`.
>
> Read more about the available [configuration options](https://docs.mezzio.dev/mezzio-authentication-oauth2/v1/intro/#configuration).

## Autogeneration of Cryptographic Keys

Dotkernel API makes use of the `./vendor/bin/generate-oauth2-keys` command from `mezzio-authentication-oauth2` to automatically regenerate the
public/private key pair used to verify the transmitted JWTs.
This process is done after each `composer update` (or `composer install` with no lock file), as specified in `composer.json` under the `scripts.post-update-cmd` key.
Dotkernel API runs its own `php ./bin/generate-oauth2-keys.php` script to create the public/private
key pair and the encryption key used to sign and verify the transmitted JWTs.
It is invoked after each `composer update` (or `composer install` with no lock file), as specified in
`composer.json` under the `scripts.post-update-cmd` key:

```json
"post-update-cmd": [
"php ./bin/generate-oauth2-keys.php",
"php ./bin/composer-post-install-script.php"
]
```

**Existing keys are never overwritten.** The script checks for `data/oauth/encryption.key`,
`data/oauth/private.key` and `data/oauth/public.key`; if all three are present it prints
`OAuth2 keys already exist. Skipping...` and stops.
Only when one is missing does it delegate to
`vendor/mezzio/mezzio-authentication-oauth2/bin/generate-oauth2-keys` to generate the set.

> This guard matters in production: regenerating the keys invalidates every access token already
> issued. Preserving them across updates was added in Dotkernel API 7.2.0
> ([issue #503](https://github.com/dotkernel/api/issues/503)).
> If you deliberately want to rotate the keys, delete the three files from `data/oauth` and run
> `composer update` — accepting that existing tokens stop working.

While hidden to the VCS by default, keep in mind not to commit any local keys.

> Autogeneration of keys can be disabled by simply removing the `php ./vendor/bin/generate-oauth2-keys` command from the mentioned key.
> Key generation can be disabled by removing the `php ./bin/generate-oauth2-keys.php` entry from the mentioned key.
>
> While not related to Dotkernel API itself, do ensure that the directory containing the keys is properly secured.

Expand All @@ -52,16 +80,25 @@ Defaults are one day for access tokens and one month for refresh tokens; shorter

**Q: Can I invalidate a user's tokens before they expire?**

A: Yes, via the `revokeTokens` method of `UserService`.
A: Yes, but not via `UserService::revokeTokens()` — that method is `private`.
It runs as part of the public `UserService::deleteUser()`, which also anonymizes the account.
To revoke tokens on their own, use the repositories: `OAuthAccessTokenRepository::findAccessTokens($identity)` to list them, then `revokeAccessToken()` and `OAuthRefreshTokenRepository::revokeRefreshToken()` for each.

**Q: When are the OAuth2 keys generated?**

A: `php ./bin/generate-oauth2-keys.php` runs after every `composer update`, and after `composer install` when there is no lock file, via `scripts.post-update-cmd` in `composer.json`.
It only generates keys that are missing: if all three files in `data/oauth` exist it reports `OAuth2 keys already exist. Skipping...` and leaves them alone, so updating dependencies does not invalidate issued tokens.

**Q: When are the OAuth2 keys regenerated?**
**Q: How do I stop the keys from being generated?**

A: After every `composer update`, and after `composer install` when there is no lock file, through the `php ./vendor/bin/generate-oauth2-keys` script in `composer.json`.
A: Remove `php ./bin/generate-oauth2-keys.php` from the `scripts.post-update-cmd` key in `composer.json`.
Since 7.2.0 this is rarely necessary — the script already preserves existing keys, which is what protects issued tokens on a server.

**Q: How do I stop the keys from being regenerated?**
**Q: How do I deliberately rotate the keys?**

A: Remove `php ./vendor/bin/generate-oauth2-keys` from the `scripts.post-update-cmd` key in `composer.json`.
This matters on servers where regenerating keys would invalidate tokens already issued.
A: Delete `encryption.key`, `private.key` and `public.key` from `data/oauth`, then run `composer update`.
The script regenerates the missing set.
Every access token issued under the old keys stops working, so plan for clients to re-authenticate.

**Q: Should the key pair be committed?**

Expand Down