feat: own shared user API tokens
This commit is contained in:
19
README.md
19
README.md
@@ -12,7 +12,7 @@ Module `git.golem15.com/golem15/sm-user-plugin`, package `user`, plugin ID `gole
|
||||
|
||||
The plugin is a compiled SummerCMS plugin. Its `init` registers it with the framework, so an application loads it by listing it in `summer.yaml`; `summer build` then emits the blank import above into the application's `plugins.gen.go`.
|
||||
|
||||
It owns the `users` table extensions, `user_throttle`, `jwt_blacklist`, `golem15_user_organisations`, `user_groups` and `users_groups`, and the JSON API under `/_user/api/v1`. It is a core plugin shared by several applications, so its routes, payloads, table names, migration IDs and config keys are a contract: change them only in a backwards-compatible way.
|
||||
It owns the `users` table extensions, `user_throttle`, `jwt_blacklist`, `user_api_tokens`, `golem15_user_organisations`, `user_groups` and `users_groups`, and the JSON API under `/_user/api/v1`. It is a core plugin shared by several applications, so its routes, payloads, table names, migration IDs and config keys are a contract: change them only in a backwards-compatible way.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -25,6 +25,7 @@ It owns the `users` table extensions, `user_throttle`, `jwt_blacklist`, `golem15
|
||||
- **Profile.** `POST /update`, `POST /marketing-consent`, `POST /avatar` and `POST /avatar/remove` (the avatar is stored through the framework's attachment storage).
|
||||
- **Organisations.** The `Organisation` model and the `organisation_id` and `organisation_role` user columns, as a foreign-key target for other plugins.
|
||||
- **User groups.** The `user_groups` table (seeded with the `guest` and `registered` groups, as in PHP) and the `users_groups` membership pivot, the `UserGroup` model and the `User.Groups` relation, which is never serialized. `UserGroupCodes` and `HasGroupCode` let other plugins check membership server-side, for example a site-admin check on the group code `admin`. No route of this plugin reads or writes groups, and the user payload still answers `"groups":[]`.
|
||||
- **Shared API tokens.** `user_api_tokens` stores one hashed personal/OAuth credential per user for the whole application. The `user.api_token` guard authenticates it for any plugin; `user.scope:<scope>` limits delegated access and `user.permission:<code>` checks the user's current group permissions. `POST/GET/DELETE /tokens` manage personal tokens, and `user:token:create`, `user:token:list` and `user:token:revoke` provide the CLI equivalent. A raw secret is shown only when created.
|
||||
- **Mail templates.** Activation, reset and reactivation mails in Polish and English (`golem15.user::mail.*`, layout `golem15.user::mail.layouts.user`); the locale follows the user (`ResolveMailLocale`).
|
||||
- **Extension events.** `RegisterEvent` fires after a user row is created; its `Payload` is a copy of the register input (extra keys a client sent included, `password` and `password_confirmation` never). `GetApiArrayEvent` lets other plugins merge keys into the user payload.
|
||||
- **Registration for other plugins.** `RegisterUser`, `FireRegisterEvent`, `IssueToken` and `APIArray` are the steps `POST /register` runs, exported so another plugin (a first-run setup endpoint, for example) creates a user with the same validation, hashing, event, token and payload.
|
||||
@@ -67,7 +68,7 @@ plugins:
|
||||
|
||||
The plugin's own `go.mod` replaces the framework with `../../../../summercms.go`, so it expects the framework checkout next to the application checkout (`<dir>/summercms.go` and `<dir>/acme/plugins/golem15/user`).
|
||||
|
||||
Another plugin that needs the signed-in user puts its routes behind the `jwt.auth` middleware and imports `git.golem15.com/golem15/sm-user-plugin/models` or `.../classes` for the user model and helpers.
|
||||
Another plugin that needs the signed-in user puts its routes behind `jwt.auth`. A machine API uses `user.api_token`, optionally followed by `user.scope:read`, `user.scope:write`, or `user.permission:vendor.plugin.code`. Both guards resolve the same user and current group permissions. Import `git.golem15.com/golem15/sm-user-plugin/models`, `.../classes`, or `.../middleware` for the shared types and helpers.
|
||||
|
||||
## API reference
|
||||
|
||||
@@ -90,6 +91,9 @@ All routes are mounted under `/_user/api/v1` with the `throttle:user-api` middle
|
||||
| `POST /marketing-consent` | `controllers.MarketingConsent` |
|
||||
| `POST /avatar` | `controllers.UploadAvatar` |
|
||||
| `POST /avatar/remove` | `controllers.RemoveAvatar` |
|
||||
| `POST /tokens` | `controllers.APITokenStore` (`jwt.auth`) |
|
||||
| `GET /tokens` | `controllers.APITokenIndex` (`jwt.auth`) |
|
||||
| `DELETE /tokens/{id}` | `controllers.APITokenDestroy` (`jwt.auth`) |
|
||||
|
||||
Packages:
|
||||
|
||||
@@ -98,10 +102,11 @@ Packages:
|
||||
- `FireRegisterEvent(ctx, app, user, fields)` fires `RegisterEvent` with `Payload` set to a copy of `fields` without `password` and `password_confirmation`, and returns the listeners' error (`/register` ignores it). Fire it after the user row is committed.
|
||||
- `IssueToken(app, user, r)` mints the session JWT as `/register` and `/login` do.
|
||||
- `APIArray(ctx, app, user)` builds the user payload, extended by `GetApiArrayEvent` listeners.
|
||||
- `classes`: `JWTSecret`, `GormUsers` (the `bouncer.UserProvider`), `LookupByEmail`, `UserGroupCodes` and `HasGroupCode`, the activation and reset code helpers, the throttle helpers, mail locale helpers and the two events. `RegisterEvent` carries `User` and `Payload map[string]any`, a read-only copy of the register input without the password keys.
|
||||
- `models`: `User`, `Throttle`, `Organisation`, `UserGroup`.
|
||||
- `classes`: `JWTSecret`, `GormUsers` (the `bouncer.UserProvider`), `LookupByEmail`, `UserPermissionGrants`, `UserGroupCodes`, `HasGroupCode`, `APITokenPrefix`, `MintAPIToken`, `HashAPIToken` and `RevokeAPIToken`, plus the activation/reset, throttle, mail locale and event helpers. `RegisterEvent` carries `User` and `Payload map[string]any`, a read-only copy of the register input without the password keys.
|
||||
- `middleware`: `APITokenGuard`, `Scope`, `Permission` and `BearerToken`.
|
||||
- `models`: `User`, `Throttle`, `Organisation`, `UserGroup`, `APIToken`.
|
||||
- `updates`: the gormigrate migrations, returned by `updates.All()`.
|
||||
- `console`: `RequirePasswordChange`, the `user:require-password-change` command.
|
||||
- `console`: `RequirePasswordChange` and `APITokenCommands`.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -126,12 +131,16 @@ Defaults live in `config/config.yaml` and are merged under `golem15.user.*`. Any
|
||||
| `golem15.user.throttle.use_throttle` | `true` | Whether login throttling is on. |
|
||||
| `golem15.user.password.bcrypt_cost` | `10` | bcrypt cost for new password hashes. |
|
||||
| `golem15.user.password.min_length` | `8` | Minimum password length. |
|
||||
| `golem15.user.api_tokens.prefix` | `usr_` | Prefix for newly minted and accepted shared API-token secrets. Override an existing application's legacy prefix without creating a plugin-specific token table. |
|
||||
|
||||
## CLI commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `user:require-password-change <email>` | Flag an account so its next session must set a new password. |
|
||||
| `user:token:create <email> [--name api] [--scopes read,write] [--expires RFC3339]` | Create an application-wide API token and print its secret once. |
|
||||
| `user:token:list` | List token metadata without secrets or hashes. |
|
||||
| `user:token:revoke <id>` | Revoke a shared API token immediately. |
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
Reference in New Issue
Block a user