Skip to content

feat(audit): API-key sign-in for the audit web UI (#915) - #916

Merged
ngjunsiang merged 2 commits into
weeklyfrom
feat/audit-web-apikey-signin
Oct 20, 2026
Merged

ngjunsiang merged 2 commits into
weeklyfrom
feat/audit-web-apikey-signin

Conversation

@ngjunsiang

Copy link
Copy Markdown
Contributor

Closes #915

Summary

Adds a development-only "Sign in with API key" path to the audit
web UI, so holders of audit API keys (e.g. dev-environment students
given a minted key) can use the trace viewer in the browser without
being on the AUDIT_ADMINS allowlist. campus.audit stays
self-contained and user-blind: no campus.auth dependency, and the
key's owner_id is a display label, never resolved against any user
store.

Behavior

  • GET/POST /audit/login/key — CSRF-protected paste form. The key is
    verified in-process via resources.apikeys.verify() (the same
    check /audit/v1 uses; rejects revoked/expired keys, bumps
    last_used). Unknown/revoked/expired keys all get one generic 401
    (no existence leak); keys lacking traces:read (wildcard-aware:
    traces:*/* qualify) get 403.
  • The Flask session stores key id + owner label + expiry only —
    never the plaintext key (cookie sessions are signed, not encrypted).
    TTL is 12h, capped by the key's own expires_at.
  • Every gated request re-resolves the key record by id, so
    revocation, expiry, and scope changes end access on the next
    request, mid-session.
  • API-key and OAuth sessions are mutually exclusive (newest login
    wins). Logout for a key session clears the local session only — the
    key stays valid for API/CLI use.
  • Development-only by construction: both methods 404 unless
    ENV=development; the login page shows the second button only in
    development. Dev DBs are per-deployment, so minted keys don't exist
    elsewhere.
  • The OAuth admin path (AUDIT_ADMINS) is unchanged.

Docs

campus/audit/docs/web-ui-requirements.md gains §5.3 (API-key
sign-in).

Testing

  • New tests/unit/audit/test_web_apikey_auth.py (15 tests): route
    404s outside development, CSRF enforcement, login accept/reject
    paths, wildcard scopes, session-ttl cap, plaintext-never-stored,
    mid-session revocation/scope-downgrade/expiry, mutual exclusivity
    via the real callback flow, local-only logout.
  • Full suites locally: unit 767 OK, contract 487 OK, ruff check .
    clean, pyright clean on changed files.

Related

Add a development-only /audit/login/key path so holders of audit API
keys (e.g. dev-environment students given a scoped key) can use the
trace viewer in the browser, keeping campus.audit self-contained and
user-blind — the key's owner_id is a display label, never resolved.

- GET/POST paste-key form, CSRF-protected with a single-use token;
  the key is verified in-process via resources.apikeys.verify().
- Only keys whose scopes cover traces:read (wildcard-aware) may sign
  in; the session stores key id + owner label + expiry only (12h,
  capped by the key's expires_at), never the plaintext key.
- Every gated request re-resolves the key record: revocation, expiry,
  and scope changes end access mid-session.
- API-key and OAuth sessions are mutually exclusive; key logout
  clears the local session only (the key stays valid for API use).
- Both route methods 404 unless ENV=development; the login page shows
  the second button only in development.
- Docs: web-ui-requirements.md gains §5.3.

CLI counterpart for minting/managing keys: nyjc-computing/campus-cli#54
hmac.compare_digest() raises TypeError when either argument contains
non-ASCII characters, and the submitted token on /audit/login/key is
raw form data. A POST with a non-ASCII csrf_token therefore crashed
the view with a 500 instead of the intended 400.

Encode both sides to UTF-8 before comparing, and cover the case with a
regression test.
@ngjunsiang
ngjunsiang merged commit 9b71969 into weekly Oct 20, 2026
22 checks passed
@ngjunsiang
ngjunsiang deleted the feat/audit-web-apikey-signin branch October 20, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant