- db/accounts.go: add StorePendingTOTP() which writes
totp_secret_enc and totp_secret_nonce but leaves
totp_required=0; add comment explaining two-phase flow
- server.go (handleTOTPEnroll): switch from SetTOTP() to
StorePendingTOTP() so the required flag is only set after
the user confirms a valid TOTP code via handleTOTPConfirm,
which still calls SetTOTP()
- server_test.go: TestTOTPEnrollDoesNotRequireTOTP verifies
that after POST /v1/auth/totp/enroll, TOTPRequired is false
and the encrypted secret is present; confirms that a
subsequent login without a TOTP code still succeeds (no
lockout)
- AUDIT.md: mark F-01 and F-11 as fixed
Security: without this fix an admin who enrolls TOTP but
abandons before confirmation is permanently locked out
because totp_required=1 but no confirmed secret exists.
StorePendingTOTP() keeps the secret pending until the user
proves possession by confirming a valid code.
- AUDIT.md: security audit report with 16 findings (F-01..F-16)
- F-04 (server.go): wire loginRateLimit (10 req/s, burst 10) to
POST /v1/auth/login and POST /v1/token/validate; no limit on
/v1/health or public-key endpoints
- F-04 (server_test.go): TestLoginRateLimited uses concurrent
goroutines (sync.WaitGroup) to fire burst+1 requests before
Argon2id completes, sidestepping token-bucket refill timing;
TestTokenValidateRateLimited; TestHealthNotRateLimited
- F-11 (ui.go): refactor Register() so all UI routes are mounted
on a child mux wrapped with securityHeaders middleware; five
headers set on every response: Content-Security-Policy,
X-Content-Type-Options, X-Frame-Options, HSTS, Referrer-Policy
- F-11 (ui_test.go): 7 new tests covering login page, dashboard
redirect, root redirect, static assets, CSP directives,
HSTS min-age, and middleware unit behaviour
Security: rate limiter on login prevents brute-force credential
stuffing; security headers mitigate clickjacking (X-Frame-Options
DENY), MIME sniffing (nosniff), and protocol downgrade (HSTS)