feat(#10): role-based access control with named API tokens - #58
Merged
Conversation
Adds viewer/operator/admin roles. Every /api/v1 request resolves to an Identity(actor, role); reads need any identity, writes need >= operator, and token administration needs admin. Fully backwards-compatible. Auth (api/auth.py): - resolve_identity: env tokens win (ACTIONSPLANE_API_TOKEN -> admin, ACTIONSPLANE_API_READ_TOKEN -> viewer, constant-time), then DB tokens (SHA-256 hash lookup -> row role), then open mode (nothing configured) -> viewer so reads work and writes still fail closed. - require_identity/require_token, require_role factory, require_operator (= require_configured_operate, so the ~20 existing write endpoints are unchanged), require_admin. Env-only helpers (classify_actor/token_ok) kept. - The audit-log actor now names the credential (operate | read | token:<name>). Tokens: - ApiToken model + migration 0015 (only the hash + a non-secret prefix stored, never the secret). repository create/list/get-by-hash/count/delete. - Admin-gated endpoints: GET/POST/DELETE /access/tokens (create returns the secret exactly once, audit-logged), GET /access/me. Frontend: - Settings "API access" tab = real token management (admin-gated create/list/ revoke, role select, secret shown once with copy); "Single sign-on" stays an honest OIDC-later scaffold. useIdentity hook + types + api methods. Tests: rewrote test_api_auth.py (12) for env/DB/open resolution, role gating, and hermetic end-to-end token-admin flow (sqlite get_session override). Migration round-trips on Postgres. Suite 364 -> 365. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap #10 — RBAC
Adds viewer / operator / admin roles. Every
/api/v1request now resolves to anIdentity(actor, role): reads need any identity, writes need ≥ operator, and token administration needs admin. Fully backwards-compatible — nothing that worked before breaks.Auth (
api/auth.py)resolve_identitychecks credentials in order:ACTIONSPLANE_API_TOKEN→ admin,ACTIONSPLANE_API_READ_TOKEN→ viewer.Dependencies:
require_identity/require_token, arequire_rolefactory,require_operator(kept as the aliasrequire_configured_operateso the ~20 existing write endpoints need no change), andrequire_admin. The env-only helpers (classify_actor/token_ok) are retained. The write-auditactornow names the credential —operate|read|token:<name>.Tokens
ApiTokenmodel + migration 0015 — only the hash and a non-secret prefix are stored, never the secret.GET/POST/DELETE /access/tokens(create returns the plaintext exactly once, audit-logged astoken.create/token.revoke), plusGET /access/me.Frontend
useIdentityhook (/access/me), types, and API methods.Tests
Rewrote
test_api_auth.py(12 tests): env→DB→open resolution, DB-token role mapping, role gating (viewer blocked from writes, operator blocked from token admin), and a hermetic end-to-end token lifecycle (create → use →/access/me→ revoke → 401) using a sqliteget_sessionoverride — no external DB needed. Migration round-tripped on Postgres. Suite 364 → 365, ruff + format + tsc + build clean.🤖 Generated with Claude Code