Add TOTP enrollment to web UI

- Profile page TOTP section with enrollment flow:
  password re-auth → QR code + manual entry → 6-digit confirm
- Server-side QR code generation (go-qrcode, data: URI PNG)
- Admin "Remove TOTP" button on account detail page
- Enrollment nonces: sync.Map with 5-minute TTL, single-use
- Template fragments: totp_section.html, totp_enroll_qr.html
- Handler: handlers_totp.go (enroll start, confirm, admin remove)

Security: Password re-auth before secret generation (SEC-01).
Lockout checked before Argon2. CSRF on all endpoints. Single-use
enrollment nonces with expiry. TOTP counter replay prevention
(CRIT-01). Self-removal not permitted (admin only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 17:39:45 -07:00
parent 25417b24f4
commit 37afc68287
10 changed files with 477 additions and 14 deletions

View File

@@ -304,13 +304,16 @@ func (u *UIServer) handleProfilePage(w http.ResponseWriter, r *http.Request) {
DeletePrefix: "/profile/webauthn",
}
// Load WebAuthn credentials for the profile page.
if u.cfg.WebAuthnEnabled() && claims != nil {
if claims != nil {
acct, err := u.db.GetAccountByUUID(claims.Subject)
if err == nil {
creds, err := u.db.GetWebAuthnCredentials(acct.ID)
if err == nil {
data.WebAuthnCreds = creds
data.TOTPEnabled = acct.TOTPRequired
// Load WebAuthn credentials for the profile page.
if u.cfg.WebAuthnEnabled() {
creds, err := u.db.GetWebAuthnCredentials(acct.ID)
if err == nil {
data.WebAuthnCreds = creds
}
}
}
}