-- AZ-534 (Epic AZ-529): TOTP-based 2FA at credential login. -- -- mfa_secret holds the base32 TOTP shared secret encrypted at rest via the -- ASP.NET Core IDataProtector ("Azaion.Mfa.Secret" purpose). The column type -- stays text because the protected payload is base64. -- -- mfa_recovery_codes is a JSONB array of objects { hash: , used_at: }. -- One-time-use is enforced by setting used_at on consumption; the row is rewritten -- transactionally with the rest of the login. -- -- mfa_last_used_window prevents replay of a TOTP code within its 30-second -- step. We persist the last accepted RFC 6238 time-step counter (long); a -- re-presented code matches that counter and is rejected. ALTER TABLE public.users ADD COLUMN IF NOT EXISTS mfa_enabled boolean NOT NULL DEFAULT false, ADD COLUMN IF NOT EXISTS mfa_secret text NULL, ADD COLUMN IF NOT EXISTS mfa_recovery_codes jsonb NULL, ADD COLUMN IF NOT EXISTS mfa_enrolled_at timestamp NULL, ADD COLUMN IF NOT EXISTS mfa_last_used_window bigint NULL; -- AZ-534 — pin the authentication-methods-reference (RFC 8176) to the session -- so refresh-token rotations (AZ-531) inherit the original auth strength. A user -- who logs in with MFA stays "amr=[pwd,mfa]" for the entire refresh chain even if -- they later disable MFA; conversely, enabling MFA mid-session does NOT silently -- upgrade the in-flight session. ALTER TABLE public.sessions ADD COLUMN IF NOT EXISTS mfa_authenticated boolean NOT NULL DEFAULT false;