docs(19.7): ban and password reset revoke OIDC grants as well as MCP grants (user decision)
This commit is contained in:
@@ -128,7 +128,7 @@ Close lane C of the phase code review and both sm-servers-app escalations, on th
|
||||
- Why a second is added: the stored cutoff is truncated to whole seconds while `created_at` is not. A token created at 12:00:05.300, before a reset at 12:00:05.700 that stores 12:00:05, is therefore refused by the verifier even without the revoke listener.
|
||||
- Why `!CreatedAt.After(cutoff)` alone is not enough: it lets that token through.
|
||||
- The cost is a token legitimately minted in the same second right after a reset. An OAuth consent round trip cannot complete that fast, so the cost is accepted.
|
||||
- The revocation sits behind one oauthserver function, `revokeUserGrants(ctx, tx, userIDs []uint, before time.Time, kinds []string) error`. `OnAccessRevoked` calls it with `kinds = []string{models.ClientKindMCP}`. If the user decides that a ban or reset also ends OIDC grants (an open question to the user), the change is one line: add the OIDC kind at that call site. Plan 09 records the question in deferred-items.
|
||||
- The revocation sits behind one oauthserver function, `revokeUserGrants(ctx, tx, userIDs []uint, before time.Time, kinds []string) error`. `OnAccessRevoked` calls it with both the MCP and the OIDC client kinds: the user decided on 2026-10-23 that a ban or a password reset ends every OAuth grant of that user, MCP agents and OIDC sign-ins alike. Link changes (C-WR-02) still revoke only `backend`-scoped MCP grants.
|
||||
- **P-36 (backend grants follow the link, C-WR-02):** oauthserver listens to `BackendLinkedEvent` and `BackendUnlinkedEvent`. In the event's transaction it revokes the frontend user's unrevoked MCP access and refresh tokens whose scopes contain `backend`; after commit it fires `MCPSubjectRevoked` with each affected `ClientID`.
|
||||
- `BackendUnlinkedEvent` already fires on every unlink the plugin performs: My account, the Administrators screen, and the deletion or deactivation of the frontend user (`dropUserLink` → `RemoveLink`, documented in `classes/events.go`).
|
||||
- The link event is still handled for one silent path: deleting the backend administrator in cabana removes the link row through the foreign key's `ON DELETE CASCADE`, and no event fires. Revoking on every new link means a grant left over from that path cannot run with the reach of a later admin, so a backend grant only ever runs with the reach of the admin shown at its consent.
|
||||
@@ -318,7 +318,7 @@ Output: local commits in sm-user-plugin, sm-oauthserver-plugin, sm-journal-plugi
|
||||
- classes/mcp_store.go `mcpTx.refresh`: apply the same three refusals (user gone through `PrincipalByID`, banned, `issuedBeforeCutoff` on the refresh token) by answering the store's existing not-found result, which wristband maps to `invalid_grant`.
|
||||
- classes/cascade.go:
|
||||
- Add the single revocation function `revokeUserGrants(ctx context.Context, tx *gorm.DB, userIDs []uint, before time.Time, kinds []string) error`. It sets `revoked_at` on the users' unrevoked access and refresh tokens created before `before`, whose client kind is in `kinds`, and returns the distinct client ids it touched.
|
||||
- `OnAccessRevoked(ctx, *userclasses.AccessRevokedEvent)` calls it in `ev.Tx` with `ev.Before` and `kinds = []string{models.ClientKindMCP}`. A comment there says that extending the revocation to OIDC grants, if the user decides so, is adding the OIDC kind to this list.
|
||||
- `OnAccessRevoked(ctx, *userclasses.AccessRevokedEvent)` calls it in `ev.Tx` with `ev.Before` and the MCP and OIDC client kinds (user decision 2026-10-23: a ban or reset ends every OAuth grant). Before revoking, check how OIDC grants are stored (client kind constant, consent rows, access and refresh tokens) and revoke the tokens and the consent row so the OIDC client must ask again; record the exact rows touched in the SUMMARY.
|
||||
- `OnBackendLinkChanged(ctx, userID uint, tx *gorm.DB)` sets `revoked_at` on the user's unrevoked MCP access and refresh tokens whose scopes contain `backend`, then fires `MCPSubjectRevoked` with each distinct `ClientID` after commit.
|
||||
- Two adapters: `OnBackendLinked(ctx, *userclasses.BackendLinkedEvent)` and `OnBackendUnlinked(ctx, *userclasses.BackendUnlinkedEvent)`.
|
||||
- plugin.go: register the three listeners next to the existing two.
|
||||
@@ -523,7 +523,7 @@ replace git.golem15.com/golem15/summercms => %s
|
||||
|
||||
**sm-oauthserver-plugin (local commits, untagged):**
|
||||
- The verifier and MCP refresh refuse banned and pre-cutoff tokens.
|
||||
- `Cascade.OnAccessRevoked`, `OnBackendLinked`, `OnBackendUnlinked` and `OnBackendLinkChanged`, all built on the one revocation function `revokeUserGrants` (MCP kind only; adding the OIDC kind is the one-line change if the user decides so).
|
||||
- `Cascade.OnAccessRevoked`, `OnBackendLinked`, `OnBackendUnlinked` and `OnBackendLinkChanged`, all built on the one revocation function `revokeUserGrants` (`OnAccessRevoked` passes the MCP and OIDC kinds; the link handlers pass MCP with the `backend` scope only).
|
||||
- `issuedBeforeCutoff`, the whole-second-aware `tokens_valid_after` rule.
|
||||
- The client-specific revoke event.
|
||||
- Subject-event helpers delegate to the user plugin.
|
||||
@@ -592,7 +592,7 @@ Create `.planning/phases/19.7-generic-mcp-module/19.7-08-SUMMARY.md` when done.
|
||||
- the previous and new go.work target in sm-oauthserver-plugin;
|
||||
- the detached submodule HEADs in sm-demo-app;
|
||||
- a "Delivered differently" note for E-1. The plugin serves no frontend organisation payload, so the escalation's "frontend organisation payloads gain the logo" is delivered as `OrganisationLogoURL` and `OrganisationLogoFields`. They give applications the `has_logo`, `logo` and `logo_url` keys for their own organisation payloads. Name this for the sm-servers-app hand-off;
|
||||
- the open OIDC question, with the one-line change that would answer it (the kinds list in `OnAccessRevoked`).
|
||||
- the OIDC decision (ban and reset revoke OIDC grants too) and the rows it touches.
|
||||
|
||||
Release rule: this plan does not tag, push or deploy. Every repository works against the local framework (go.work, or `replace ../summercms.go`), and every plugin's go.mod `require` line stays at its current version. The plans may commit locally in every repository. The releases happen only after phase verification, in this order:
|
||||
1. framework v0.5.0;
|
||||
|
||||
@@ -357,7 +357,7 @@ Output: named tests in six repositories (framework, user plugin, oauthserver, Jo
|
||||
- a banned user → ErrMCPTokenInvalid, and valid again after unban for a token created after the cut-off;
|
||||
- the MCP refresh rotation refuses a banned user, a pre-cutoff refresh token and a deleted user.
|
||||
- TestMCPGrantRevocation (oauthserver, Postgres, C-CR-01, C-WR-02, C-IN-02):
|
||||
- AccessRevokedEvent revokes MCP access and refresh tokens created before Before, leaves later ones and OIDC-client tokens;
|
||||
- AccessRevokedEvent revokes MCP access and refresh tokens created before Before, and OIDC access and refresh tokens (and the OIDC consent row) created before Before, and leaves later ones of both kinds;
|
||||
- RemoveLink and a new SetLink revoke only `backend`-scoped MCP tokens of that user and fire revoked events with their ClientIDs after commit;
|
||||
- the authorization revoke route fires with ClientID.
|
||||
- TestJournalPromptWrites (Journal, -short): `draft_post` declares `Writes`; through pier's catalog (the TestJournalMCP harness) a read-only identity's prompts/list omits `journal_draft_post` and a read+write one lists it.
|
||||
@@ -461,7 +461,7 @@ Output: named tests in six repositories (framework, user plugin, oauthserver, Jo
|
||||
- New entries with reasons:
|
||||
- B-IN-05: the prompt dialog closes on a 422. The contract text was corrected in plan 07; keeping the dialog open changes the shared confirm primitive used by every action.
|
||||
- B-IN-06: no fix required; the visible link is the popup-blocker fallback.
|
||||
- OIDC grants on ban or password reset: an open question to the user; plan 08 revokes MCP grants only. Record whether OIDC refresh already refuses banned users (evidence). Also record that the extension is one line: add the OIDC client kind to the `kinds` list `Cascade.OnAccessRevoked` passes to `revokeUserGrants`, with a pinning test to add in TestMCPGrantRevocation.
|
||||
- OIDC grants on ban or password reset: decided by the user on 2026-10-23 (both MCP and OIDC grants are revoked); record it as resolved, with the test that pins it.
|
||||
- The local oauthserver go.work retarget.
|
||||
- The demo's oauthserver and journal submodules detached at unreleased commits, plus the user submodule pointer, all waiting for the release.
|
||||
- The release order:
|
||||
|
||||
Reference in New Issue
Block a user