Skip to content

SCRUM-209 — admin-auth audit endpoint and login/refresh metrics

Plan ref: AA-10 (docs/11-admin-plane-plan.md). Closes gap 7 in doc 10.

What exists

Piece Notes
GET /admin-auth/audit same wire shape as Config's /api/admin/config/audit: {"entries":[{id, at, actor_id, actor_name, source:"admin-auth", action, target, details}], "next_cursor"}; newest first; limit 1..200 (default 50), opaque cursor (base64url {"before":id}), action, actor, from/to (RFC3339). Malformed → 400 validation_failed
Auth requireRole("viewer"): token verified, roles read from the DB row, disabled/missing account → 401 invalid_token, below minimum → 403 insufficient_role. requireAdmin is now requireRole("admin")
staff_login_total{result} success, invalid_credentials (wrong password, unknown email, disabled), locked (already locked or this failure trips the lock), bad_request, error; all at 0 from start-up
staff_refresh_total{result} success (incl. the 30 s grace path), invalid, reuse_detected, error

Audit actions already written by earlier tickets: login.success, login.locked, logout, session.reuse_detected, user.invite, user.reset_link, invite.revoke, user.update, user.onboard, user.password_reset, root.bootstrap, root.rotate. gateway_dev already forwards /admin-auth/*.

How to verify

cd services/admin_auth
gofmt -l . && go vet ./...
ADMIN_AUTH_TEST_DATABASE_URL=postgres://auth_rw:pw@127.0.0.1:5433/admin_auth_test?sslmode=disable \
  go test -race -count=1 ./...

internal/server/audit_test.go: login + invite appear as login.success and user.invite in the shared shape; limit=1 paging walks every row once and ends with next_cursor:null; each filter; malformed cursor/limit 400; no token 401; disabled user 401; viewer allowed. metrics_test.go: good login, wrong password, malformed body → 1 each, other series 0; refresh success and a replay → reuse_detected.

Results at time of writing

  • gofmt, go vet, go test -race with the DB (7 packages): pass.

How it was built

DeepSeek run scoped (Landlock) to services/admin_auth (213 s, ~29k output tokens, reasoning effort low). Claude review: store query and wire format diffed against Config's audit; no changes needed.