- ui/ui.go: add pendingLogin struct and pendingLogins sync.Map
to UIServer; add issueTOTPNonce (generates 128-bit random nonce,
stores accountID with 90s TTL) and consumeTOTPNonce (single-use,
expiry-checked LoadAndDelete); add dummyHash() method
- ui/handlers_auth.go: split handleLoginPost into step 1
(password verify → issue nonce) and step 2 (handleTOTPStep,
consume nonce → validate TOTP) via a new finishLogin helper;
password never transmitted or stored after step 1
- ui/ui_test.go: refactor newTestMux to reuse new
newTestUIServer; add TestTOTPNonceIssuedAndConsumed,
TestTOTPNonceUnknownRejected, TestTOTPNonceExpired, and
TestLoginPostPasswordNotInTOTPForm; 11/11 tests pass
- web/templates/fragments/totp_step.html: replace
'name=password' hidden field with 'name=totp_nonce'
- db/accounts.go: add GetAccountByID for TOTP step lookup
- AUDIT.md: mark F-02 as fixed
Security: the plaintext password previously survived two HTTP
round-trips and lived in the browser DOM during the TOTP step.
The nonce approach means the password is verified once and
immediately discarded; only an opaque random token tied to an
account ID (never a credential) crosses the wire on step 2.
Nonces are single-use and expire after 90 seconds to limit
the window if one is captured.
- 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)