Skip to content

SCRUM-262 — admin-auth admin reset of another user's MFA

Found while writing the admin access guide (SCRUM-243).

What exists

POST /api/admin/users/{id}/mfa/reset (admin, via requireAdmin), no body:

Case Answer
unknown user 404 not_found
root target 403 root_protected (root has no second factor)
own account 403 self_modification
target is an admin, caller is not root 403 insufficient_role (D3)
nothing enrolled (no factor, no pending secret, no recovery codes) 409 mfa_not_enrolled
otherwise 200 with the user; in one transaction under FOR UPDATE: factor, pending secret and step cleared, recovery codes deleted, open MFA tickets burned, refresh sessions revoked, audit mfa.reset with {had_factor, recovery_codes_removed, sessions_revoked}

The caller's root flag is read from the database in the same transaction. An admin whose MFA was reset is sent through enrollment at the next login (SCRUM-208).

How to verify

cd services/admin_auth
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/admin_mfa_test.go: the rule table (self, root, admin→admin 403, root→admin 200, admin→live_ops 200, non-admin callers 403, unknown 404, nothing enrolled 409); the effect (login then gives tokens with no MFA challenge, the old recovery code, ticket and refresh cookie no longer work, one audit row with details); an admin reset by root must enroll again; two concurrent resets → one 200 and one 409, one audit row.

Results at time of writing

  • gofmt, go vet, go test -race with the DB (9 packages): pass, on the branch rebased onto the merged staging.

How it was built

DeepSeek run scoped (Landlock) to services/admin_auth; interrupted twice by WSL crashes and finished by continuation rounds. Claude review: transaction scope, rule order and revocation checked; no changes needed.