Skip to content

feat(audit): record the OAuth superadmin sign-in in the audit trail (#932) - #933

Merged
ngjunsiang merged 1 commit into
weeklyfrom
feat/audit-oauth-signin-events
Oct 20, 2026
Merged

ngjunsiang merged 1 commit into
weeklyfrom
feat/audit-oauth-signin-events

Conversation

@ngjunsiang

Copy link
Copy Markdown
Contributor

Closes #932

Summary

The API-key sign-in has been audited since #931; a superadmin signing in through Campus Auth was not, so the widest-reaching principal in the audit service left no trace of entering it. This closes that gap.

Outcome Event
Sign-in completed audit.ui.auth.success — the campus.auth account, and whether it is a superadmin
Sign-in refused audit.ui.auth.failed — reason: invalid_state, missing_code, auth_service_error, auth_service_unavailable

The event types are deliberately not audit.apikeys.*: those mean "an API key authenticated", and a Campus account is not an API key. The trail should say which credential authenticated.

What is recorded, and what is not

  • The account is recorded as a label in the span's tags — campus.audit has no user store, and its spans carry user_id=None by design. current_actor()'s ("superadmin", user id) is the same value.
  • The superadmin flag distinguishes an account on the AUDIT_ADMINS list from one that is not. A non-superadmin can complete the OAuth flow and be refused by the pages, so the trail answers "who tried to get in", not just "who got in".
  • The access token is never part of an event: it is exchanged server-side and kept in the session, never in a request or a response body. A test asserts it appears nowhere in the emitted span.

Testing

New TestAuditWebOAuthSignInEvents class (7 tests): success event content, state mismatch, missing code, an auth-service error, an auth service that is down at /audit/login/start, a non-superadmin sign-in marked as such, and no event at all when AUDIT_EVENTS_ENABLED=0. As in #931, the new tests clean up the spans they add so the shared traces table stays as the other unit tests expect it.

Unit suite passing, contract suite passing, ruff check . clean, pyright clean on campus/.

Stack

This is the bottom link of a three-PR stack (see the stack description on the top PR): #932 (this PR) → #927 (the key-management page) → #918 (the role catalogue). Each PR is based on the one below it; this one is based on weekly.

Related

…932)

The API-key sign-in is audited since #931; a superadmin signing in
through Campus Auth was not, so the widest-reaching principal in the
service left no trace of entering it.

The OAuth flow now records every outcome, under its own event types:
audit.ui.auth.success carries the campus.auth account and whether it
is a superadmin, and audit.ui.auth.failed carries a reason
(invalid_state, missing_code, auth_service_error,
auth_service_unavailable). They are deliberately distinct from the
audit.apikeys.* events of the API-key path: a Campus account is not an
API key, and the trail should say which credential authenticated.

The account is recorded as a label in the span's tags — campus.audit
has no user store and its spans carry user_id=None by design — and the
access token is never part of an event: it is exchanged server-side
and kept in the session, never in a request or a response body.

The new tests clean up the spans they add, so the shared traces table
stays as the other unit tests expect it.
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