diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc41d7..3db0a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,25 @@ ADR-007 and ADR-002. with a create modal that reveals the secret once (copy button) and an admin All Tokens section. Docs: `docs/IMPORT-API.md` and `docs/CONFIG.md` updated to recommend a PAT for imports. Core feature; no plugin-contract change. +- Optional permission scopes on personal API tokens. A token MAY carry a scopes + list (permission names, migration `7d22_apitokens_scopes` adds the nullable + `apitokens.scopes` JSON column); NULL keeps the original behavior (acts as its + owner). A scoped token grants ONLY the listed permissions, intersected with + what the owner actually holds at use time, and SUSPENDS the admin-role bypass, + so a scoped token minted by an admin is genuinely limited: it is denied on + role-gated (`require_role`) endpoints and gets no import mode. The shim mints + the request JWT with a `patscopes` claim that `require_permission`, + `require_role`, and `import_mode_active` read; normal login JWTs carry no such + claim and are unaffected (zero regression). Scopes are validated at write time + against the token OWNER's permissions (the scope ceiling - a token can never + grant more than its owner holds; when an admin edits another user's token the + ceiling is that owner's permissions), rejecting unknown or unheld names 400. + Minting/managing tokens now requires the new `apitokens.create` permission + (category `apitokens`; admins hold it by default, grantable via the roles UI) + rather than being open to any authenticated user. The Settings > API Tokens + create/edit modals gain a "Restrict permissions" section (a category-grouped + checkbox grid limited to the permissions the creator holds) and the token + lists show a full-access / N-permissions access chip. - Vendor-model photos on asset detail heroes: computers and printers now surface the linked model's `imageurl` in their extension payloads (the field machines already exposed), and the machine, PC, printer, network diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 5e51785..a1e0761 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -231,6 +231,15 @@ is stored; the secret is shown once at creation. This is the recommended credential for long-running imports (see `docs/IMPORT-API.md`). There is no env var to configure; PATs are managed entirely through the API/UI. +Creating or managing a PAT requires the `apitokens.create` permission (admins +hold it by default; grant it to other roles from Settings > Users & Roles). By +default a PAT is unscoped and acts with the full authority of its owner. A PAT +may optionally carry a scopes list (a subset of the owner's permissions, capped +at what the owner actually holds): a scoped token grants ONLY those permissions, +intersected with the owner's live permissions at use time, and suspends the +admin bypass, so it is denied on role-gated (admin-only) endpoints and on import +mode. Use an unscoped token for admin-only work and imports. + ### identifiers (dynamic) One boolean key per asset identifier per asset type, keyed diff --git a/docs/IMPORT-API.md b/docs/IMPORT-API.md index 1a99b6e..b6c6d50 100644 --- a/docs/IMPORT-API.md +++ b/docs/IMPORT-API.md @@ -55,6 +55,13 @@ mode) as its owning admin, exactly as a login JWT would, but without the hourly expiry. Revoke it from the same Settings page (or `DELETE /api/apitokens/`) when the import is done. +Use an **unscoped** token for imports. A token may optionally carry a scopes +list that limits it to specific permissions; a scoped token suspends the admin +bypass and is denied on role-gated endpoints AND on import mode, so it cannot +run an import. Leave the "Restrict permissions" option off (the default) so the +token acts with the full authority of its admin owner. Minting a token itself +requires the `apitokens.create` permission (admins have it by default). + A short-lived login JWT still works for quick one-off calls if you prefer. ### Import mode: the `X-Import-Mode` header diff --git a/frontend/src/views/settings/ApiTokensList.vue b/frontend/src/views/settings/ApiTokensList.vue index 67eec6d..1227987 100644 --- a/frontend/src/views/settings/ApiTokensList.vue +++ b/frontend/src/views/settings/ApiTokensList.vue @@ -10,7 +10,9 @@ Personal access tokens let scripts and integrations authenticate as you without an hourly-expiring login session. Send the token as Authorization: Bearer shopdb_pat_.... Ideal for long-running - imports that would otherwise die when the login JWT expires. + imports that would otherwise die when the login JWT expires. A token may + be restricted to a subset of your permissions; a restricted token cannot + reach admin-only (role-gated) endpoints or import mode.

Loading...
@@ -22,6 +24,7 @@ Name Token + Access Created Expires Last Used @@ -33,6 +36,7 @@ {{ token.name }} {{ token.displayprefix }}... + {{ scopeSummary(token) }} {{ formatDate(token.createddate) }} {{ token.expiresat ? formatDate(token.expiresat) : 'Never' }} {{ token.lastusedat ? formatDate(token.lastusedat) : 'Never' }} @@ -42,12 +46,14 @@ Active + - + No tokens yet @@ -67,6 +73,7 @@ Owner Name Token + Access Expires Last Used Status @@ -78,6 +85,7 @@ {{ token.username || '-' }} {{ token.name }} {{ token.displayprefix }}... + {{ scopeSummary(token) }} {{ token.expiresat ? formatDate(token.expiresat) : 'Never' }} {{ token.lastusedat ? formatDate(token.lastusedat) : 'Never' }} @@ -91,7 +99,7 @@ - + No tokens @@ -102,7 +110,7 @@