Phase 16 — Admin recovery endpoints¶
Last updated: 2026-05-08
Three admin-only endpoints close the SOC 2 readiness gap where a user who loses their TOTP device or trips brute-force lockout has no recovery path. Without them, the only fix was "delete and re-create the user," which loses their finding-edit history.
Endpoints¶
| Endpoint | Effect |
|---|---|
POST /auditforge/user/{user_id}/clear-mfa |
Clears the user's TOTP enrollment so they can re-enroll on next login. Existing sessions kept. |
POST /auditforge/user/{user_id}/clear-lockout |
Clears the brute-force lockout record on the user's email so they can immediately attempt login again. |
POST /auditforge/user/{user_id}/reset-password |
Issues a new temporary password (admin-supplied) and forces must_change_password=true on next login. All sessions revoked. |
All three require the admin token (x-admin-token: <ADMIN_TOKEN>) — they are not exposed to the partner role. A 403 is returned for any session-token-authenticated caller.
Auth¶
Authorization: Bearer <session_token> is also accepted but only succeeds if the underlying user has the admin role; partner and associate roles are rejected with 403.
clear-mfa¶
Response:
was_enabled is true if the user had TOTP enrolled prior to the call (false is the idempotent no-op case). The user can immediately log in with password alone and re-enroll TOTP via POST /auth/mfa/enroll/begin.
clear-lockout¶
Response:
had_record is true if there was an active failure record (whether currently locked or just accumulating failures). The user's email is removed from the LoginAttemptStore, resetting both the failure counter and the locked_until timestamp.
reset-password¶
POST /auditforge/user/u-abc123/reset-password
Content-Type: application/json
{ "new_password": "TempIssued-2026-05-08!" }
Response:
The new password must be at least 12 characters. After this call:
- The user's existing sessions are revoked, so a stolen-device attacker cannot continue acting.
must_change_passwordis set totrue. The user is required to set a new password on next successful login (the password-change UI is mandatory).- TOTP enrollment is not cleared — if the user still has their authenticator, they continue to use it. If they have lost both, the admin should also call
clear-mfa.
Audit trail¶
Each call writes a structured warning log line via Python's standard logger. Examples:
auditforge_admin_clear_mfa | user_id=u-abc123 email=user@firm.com was_enabled=True
auditforge_admin_clear_lockout | user_id=u-abc123 email=user@firm.com had_record=True
auditforge_admin_reset_password | user_id=u-abc123 email=user@firm.com sessions_revoked=2
These appear in CloudWatch Logs for the ECS task. Ops can grep for auditforge_admin_ to enumerate all recovery actions for compliance review.
Operator workflow¶
When a user reports they cannot log in:
- Check the lockout state. If they have failed login 5 times in a 15-minute window, they are locked out for 15 minutes. Either wait, or call
clear-lockoutto lift it immediately. - Check whether they have their TOTP device. If not, call
clear-mfaso they can log in with password alone, then re-enroll TOTP after login. - If they have forgotten their password, call
reset-passwordwith an admin-issued temporary password. Communicate the temp password through a secure side channel (not email — SES is sandboxed); the user is forced to change it on next login.
If a user is fully locked out (no TOTP, forgot password, locked from brute-force):
# all three, in order:
curl -X POST -H "x-admin-token: $ADMIN" \
https://metis-demo.base2ml.com/auditforge/user/$UID/clear-lockout
curl -X POST -H "x-admin-token: $ADMIN" \
https://metis-demo.base2ml.com/auditforge/user/$UID/clear-mfa
curl -X POST -H "x-admin-token: $ADMIN" \
-H "Content-Type: application/json" \
-d "{\"new_password\":\"TempPass-$(date +%Y%m%d)!\"}" \
https://metis-demo.base2ml.com/auditforge/user/$UID/reset-password
The user logs in with the temp password, is forced to change it, then re-enrolls TOTP.
Security notes¶
- Admin-token auth means these endpoints can only be called from the founder/operator workstation. No partner-role user can perform recovery on another user — only an admin can. This is the right floor for SOC 2: the recovery path itself is privileged.
- Session revocation on password reset prevents the obvious "stolen-cookie" continuation attack: even if an attacker has captured a session token, that token stops working the moment the user (or admin) resets the password.
- TOTP-clear does not revoke sessions. Reasoning: the admin clearing TOTP is responding to the user's request — the user's existing sessions remain valid. If the admin wants to invalidate sessions too, they pair
clear-mfawithreset-password. - The new-password length floor (12 characters) prevents trivially-weak temp passwords. No complexity rules — argon2id makes the brute-force economics around even moderate-length passwords prohibitive.
Files¶
app/auditforge/users.py—UserStore.admin_reset_passwordandLoginAttemptStore.clearapp/auditforge_endpoints.py— three POST handlers below the role-update endpointtests/test_auditforge_endpoints.py— 13 endpoint tests covering auth, 404 paths, and behavior