feat(pact): add HasFrontendTokenScopes and the shared ValidTokenScopeName rule (E-2)

- pact.FrontendTokenScope and pact.HasFrontendTokenScopes declare member
  (usr_) token scopes; the admin token catalog never reads them
- pact.ValidTokenScopeName carries cabana's scope-name pattern; cabana's
  g15_ catalog switches to it with unchanged behaviour and messages
This commit is contained in:
Jakub Zych
2026-10-23 16:15:25 +02:00
parent c14238b94b
commit 9eedfbbb29
5 changed files with 43 additions and 5 deletions

View File

@@ -116,6 +116,8 @@ func (p *Plugin) TokenScopes() []pact.TokenScope {
A scoped request passes only when one method-allowed scope matches every required permission code and the owner holds a required code inside that scope. A superuser with a scoped token is limited to that scope. An empty requirement list is refused unless the scope is full (`*` or `admin:read` on GET/HEAD). Routes that never call `cabana.Allows` (`GET /field-types`, `POST /markdown/preview`) apply the same empty-list rule. Account profile, avatar, token management, refresh and logout refuse every personal token (`details.reason` `interactive_only`), including `*`. `GET /auth/me` accepts any valid token.
Member personal API tokens, the ones the user plugin issues to frontend users, have their own catalog. A plugin declares scopes for them with `pact.HasFrontendTokenScopes` (`pact.FrontendTokenScope`: name, label and description, no permissions or methods, because the route's scope middleware decides). Those scopes appear only in the member token list and never in this admin catalog, and `pact.HasTokenScopes` scopes never appear in the member list. Both catalogs check names with `pact.ValidTokenScopeName`.
### Expiry and revoke
Expiry is `30d`, `90d` (default), `365d` or `never`. Soft revoke sets `revoked_at`; revoked tokens are hidden from the default list. A password reset does not invalidate personal API tokens.