Backend for #738. Real TOTP 2FA for admin accounts, all roles incl.
super_admin (closes#735).
- mfaService: otplib TOTP; AES-256-GCM encryption of the secret at rest
(key derived from MFA_ENCRYPTION_KEY or JWT_SECRET); bcrypt-hashed,
single-use recovery codes; otpauth URI + QR.
- Migration 151: adds two_factor_recovery_codes + two_factor_enrolled_at
(secret/enabled columns already existed from legacy 016).
- Enrollment endpoints (behind adminAuth, per-user): GET /mfa/status,
POST /mfa/{setup,enable,disable,recovery-codes}. Disable/regenerate
require a current code so a hijacked session can't strip 2FA.
- Login challenge: /admin/login returns {mfaRequired, mfaToken} (no
session) when 2FA is on; /admin/login/mfa exchanges a TOTP or recovery
code for the session. Lockout counter is NOT reset until the second
factor passes, so MFA brute-force is rate-limited too.
- CLI break-glass: scripts/reset-admin-mfa.js --email <e> | --all --yes,
audit-logged, matches reset-admin-password.js convention.
- Docs + optional MFA_ENCRYPTION_KEY env.
Verified end-to-end on a live backend: enroll (super_admin), challenge,
TOTP + single-use recovery login, disable, and CLI reset.