mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
Add the TOTP enroll/confirm/disable surface and Argon2id-hashed recovery codes as the v1.23 second factor. - totp.ts wraps otpauth (zero-dep) pinned to the interop floor (SHA-1, 6 digits, 30 s) with a +/-1 drift window. Replay protection persists the last accepted time-step per account (User.totpLastStep, migration 0202_v123_totp_laststep) and rejects any step that is not strictly newer, so a captured code dies the instant it is accepted once. - recovery-codes.ts issues ten single-use, high-entropy codes, Argon2id- hashed at rest, shown once; regeneration replaces the entire prior set in a transaction and verification burns the matched code with a guarded update. - The TOTP secret is stored AES-256-GCM encrypted (already registered in the encrypted-columns registry + rotation script); decrypt is fail-closed. - Routes: setup (pending secret), confirm (activate + issue recovery codes), disable (step-up + current factor), recovery-codes/regenerate (step-up). Setup/confirm are cookie-only; disable/regenerate add requireFreshMfa. - scripts/disable-mfa.ts is the operator escape hatch for a locked-out self-hoster, audited, run via pnpm dlx tsx.
12 lines
616 B
SQL
12 lines
616 B
SQL
-- v1.23 — TOTP replay guard.
|
|
--
|
|
-- Persists the last accepted time-step counter per account so a verified
|
|
-- code cannot be replayed within its 30-second validity window. The verify
|
|
-- path resolves the submitted code to its RFC 6238 step (`floor(now /
|
|
-- period)` adjusted by the ±1 drift delta) and rejects any step that is not
|
|
-- strictly greater than this column. Additive, nullable — existing rows
|
|
-- (no TOTP enrolled) need no backfill, and a freshly confirmed factor starts
|
|
-- with NULL (the first verify simply records its step).
|
|
ALTER TABLE "users"
|
|
ADD COLUMN IF NOT EXISTS "totp_last_step" BIGINT;
|