Fix F-04 + F-11; add AUDIT.md
- 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)
This commit is contained in:
@@ -127,7 +127,30 @@ func (db *DB) UpdatePasswordHash(accountID int64, hash string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorePendingTOTP stores the encrypted TOTP secret without enabling the
|
||||
// totp_required flag. This is called during enrollment (POST /v1/auth/totp/enroll)
|
||||
// so the secret is available for confirmation without yet enforcing TOTP on login.
|
||||
// The flag is set to 1 only after the user successfully confirms the code via
|
||||
// handleTOTPConfirm, which calls SetTOTP.
|
||||
//
|
||||
// Security: keeping totp_required=0 during enrollment prevents the user from
|
||||
// being locked out if they abandon the enrollment flow after the secret is
|
||||
// generated but before they have set up their authenticator app.
|
||||
func (db *DB) StorePendingTOTP(accountID int64, secretEnc, secretNonce []byte) error {
|
||||
_, err := db.sql.Exec(`
|
||||
UPDATE accounts
|
||||
SET totp_secret_enc = ?, totp_secret_nonce = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`, secretEnc, secretNonce, now(), accountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: store pending TOTP: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTOTP stores the encrypted TOTP secret and marks TOTP as required.
|
||||
// Call this only after the user has confirmed the TOTP code; for the initial
|
||||
// enrollment step use StorePendingTOTP instead.
|
||||
func (db *DB) SetTOTP(accountID int64, secretEnc, secretNonce []byte) error {
|
||||
_, err := db.sql.Exec(`
|
||||
UPDATE accounts
|
||||
|
||||
Reference in New Issue
Block a user