Add FIDO2/WebAuthn passkey authentication
Phase 14: Full WebAuthn support for passwordless passkey login and hardware security key 2FA. - go-webauthn/webauthn v0.16.1 dependency - WebAuthnConfig with RPID/RPOrigin/DisplayName validation - Migration 000009: webauthn_credentials table - DB CRUD with ownership checks and admin operations - internal/webauthn adapter: encrypt/decrypt at rest with AES-256-GCM - REST: register begin/finish, login begin/finish, list, delete - Web UI: profile enrollment, login passkey button, admin management - gRPC: ListWebAuthnCredentials, RemoveWebAuthnCredential RPCs - mciasdb: webauthn list/delete/reset subcommands - OpenAPI: 6 new endpoints, WebAuthnCredentialInfo schema - Policy: self-service enrollment rule, admin remove via wildcard - Tests: DB CRUD, adapter round-trip, interface compliance - Docs: ARCHITECTURE.md §22, PROJECT_PLAN.md Phase 14 Security: Credential IDs and public keys encrypted at rest with AES-256-GCM via vault master key. Challenge ceremonies use 128-bit nonces with 120s TTL in sync.Map. Sign counter validated on each assertion to detect cloned authenticators. Password re-auth required for registration (SEC-01 pattern). No credential material in API responses or logs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,9 @@ import (
|
||||
|
||||
// handleLoginPage renders the login form.
|
||||
func (u *UIServer) handleLoginPage(w http.ResponseWriter, r *http.Request) {
|
||||
u.render(w, "login", LoginData{})
|
||||
u.render(w, "login", LoginData{
|
||||
WebAuthnEnabled: u.cfg.WebAuthnEnabled(),
|
||||
})
|
||||
}
|
||||
|
||||
// handleLoginPost processes username+password (step 1) or TOTP code (step 2).
|
||||
@@ -290,13 +292,30 @@ func (u *UIServer) writeAudit(r *http.Request, eventType string, actorID, target
|
||||
// handleProfilePage renders the profile page for the currently logged-in user.
|
||||
func (u *UIServer) handleProfilePage(w http.ResponseWriter, r *http.Request) {
|
||||
csrfToken, _ := u.setCSRFCookies(w)
|
||||
u.render(w, "profile", ProfileData{
|
||||
claims := claimsFromContext(r.Context())
|
||||
|
||||
data := ProfileData{
|
||||
PageData: PageData{
|
||||
CSRFToken: csrfToken,
|
||||
ActorName: u.actorName(r),
|
||||
IsAdmin: isAdmin(r),
|
||||
},
|
||||
})
|
||||
WebAuthnEnabled: u.cfg.WebAuthnEnabled(),
|
||||
DeletePrefix: "/profile/webauthn",
|
||||
}
|
||||
|
||||
// Load WebAuthn credentials for the profile page.
|
||||
if u.cfg.WebAuthnEnabled() && 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u.render(w, "profile", data)
|
||||
}
|
||||
|
||||
// handleSelfChangePassword allows an authenticated human user to change their
|
||||
|
||||
Reference in New Issue
Block a user