diff --git a/README.md b/README.md index afdf517..e411095 100644 --- a/README.md +++ b/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`, `user_api_tokens`, `golem15_user_organisations`, `user_groups`, `users_groups` and `golem15_user_frontend_permissions`, the JSON API under `/_user/api/v1`, and the Users, User Groups and Organisations screens of the admin. It is a core plugin shared by several applications, so its routes, payloads, table names, migration IDs, permission codes 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_oauth_identities`, `golem15_user_organisations`, `user_groups`, `users_groups` and `golem15_user_frontend_permissions`, the JSON API under `/_user/api/v1`, and the Users, User Groups and Organisations screens of the admin. It is a core plugin shared by several applications, so its routes, payloads, table names, migration IDs, permission codes and config keys are a contract: change them only in a backwards-compatible way. ## Features @@ -103,6 +103,16 @@ All routes are mounted under `/_user/api/v1` with the `throttle:user-api` middle | `GET /tokens` | `controllers.APITokenIndex` (`jwt.auth`) | | `DELETE /tokens/{id}` | `controllers.APITokenDestroy` (`jwt.auth`) | +### Host-mounted OAuth identities + +The host application mounts these constructors on a path it chooses. They are not registered under `/_user/api/v1`. `OAuthIdentitiesOptions.WriteNotFound` is the host's 404 writer so missing, foreign and unknown providers share one body. `OAuthIdentitiesOptions.Providers` defaults to `google`, `facebook` and `github` when empty. The 409 last-method phrase is `golem15.user::lang.oauth.last_method_blocked`. + +| Constructor | Role | +|-------------|------| +| `controllers.OAuthIdentitiesIndex` | Lists the caller's linked providers as `{provider, linked_at}` only. | +| `controllers.OAuthIdentitiesDestroy` | Unlinks one provider for the caller. | +| `controllers.OAuthIdentitiesOptions` | Host injects `Providers` and `WriteNotFound`. | + ### Admin The three screens are served by the framework's generic admin API under `/api/v1/golem15/user/users`, `.../usergroups` and `.../organisations`; the plugin adds no admin route of its own. @@ -133,6 +143,12 @@ Migrations added for the admin, all additive: | `202610040002_add_users_last_seen` | `users.last_seen` (timestamp, nullable) | | `202610040003_create_frontend_permissions` | the table `golem15_user_frontend_permissions` (`code` unique, `label`, `tab`, `comment`) | +Migrations added for linked OAuth identities, all additive: + +| ID | Adds | +|----|------| +| `202610050001_create_oauth_identities` | the table `golem15_user_oauth_identities` (`user_id` cascade FK to `users`, unique `(user_id, provider)` and `(provider, provider_id)`, jsonb `profile_data`) | + Packages: - `controllers`: the route handlers above, `AdminControllers` (the admin controller list), `PermissionAccessUsers` and `PermissionAccessGroups`, plus the registration steps `POST /register` runs, for other plugins: @@ -146,7 +162,7 @@ Packages: - `ActivateUsers`, `DeactivateUsers`, `RestoreUsers`, `BanUsers`, `UnbanUsers`, `UnsuspendUser`, `ThrottleStates` and `ForceDeleteCleanup`, the user actions the admin runs. Each takes the handle of the caller's transaction. - `TouchLastSeen(ctx, db, userID)`. - `middleware`: `APITokenGuard`, `Scope`, `Permission` and `BearerToken`. -- `models`: `User`, `Throttle`, `Organisation`, `UserGroup`, `UsersGroup` (the `users_groups` pivot), `APIToken`, `FrontendPermission` and `PermissionSet`. `PermissionSet` reads a stored permission column tolerantly (NULL, an empty string, `[]`, numeric strings) and writes a JSON object of integers; `ParsePermissionSet` and `PermissionSet.Text` do the same for a column that is a plain string on its model. `UserGroup.UsersCount` is filled only by a query that selects `users_count` (the admin list does). `Organisation.Members` is the relation behind the members manager, `Organisation.AttachRelations` declares its avatar (`OrganisationAvatarField`), and `Slugify` is the slug rule. `User.FilterScope` answers the admin list scope `FilterByGroup`, and `User.AttachRelations` declares the avatar (`AvatarField`). +- `models`: `User`, `Throttle`, `Organisation`, `UserGroup`, `UsersGroup` (the `users_groups` pivot), `APIToken`, `OAuthIdentity`, `FrontendPermission` and `PermissionSet`. `PermissionSet` reads a stored permission column tolerantly (NULL, an empty string, `[]`, numeric strings) and writes a JSON object of integers; `ParsePermissionSet` and `PermissionSet.Text` do the same for a column that is a plain string on its model. `UserGroup.UsersCount` is filled only by a query that selects `users_count` (the admin list does). `Organisation.Members` is the relation behind the members manager, `Organisation.AttachRelations` declares its avatar (`OrganisationAvatarField`), and `Slugify` is the slug rule. `User.FilterScope` answers the admin list scope `FilterByGroup`, and `User.AttachRelations` declares the avatar (`AvatarField`). - `updates`: the gormigrate migrations, returned by `updates.All()`. - `console`: `RequirePasswordChange` and `APITokenCommands`.