summaryrefslogtreecommitdiff
path: root/cmd/dashboard
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-02-05 15:35:01 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-02-05 15:35:01 -1000
commit0a1001eb0bd2d1f7c0624ae1ef8ae7ccdb3447d4 (patch)
treecaf04d4f505bb12751579e2f0f1730ead7a9e2e2 /cmd/dashboard
parent1eab4d59454fa5999675d51b99e77ac6580aba95 (diff)
Add passkey (WebAuthn) authentication support
Enable passwordless login via passkeys as an alternative to password auth. Users register passkeys from Settings; the login page offers both options. WebAuthn is optional — only active when WEBAUTHN_RP_ID and WEBAUTHN_ORIGIN env vars are set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'cmd/dashboard')
-rw-r--r--cmd/dashboard/main.go29
1 files changed, 28 insertions, 1 deletions
diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go
index db1f66d..0553041 100644
--- a/cmd/dashboard/main.go
+++ b/cmd/dashboard/main.go
@@ -23,6 +23,8 @@ import (
"task-dashboard/internal/handlers"
appmiddleware "task-dashboard/internal/middleware"
"task-dashboard/internal/store"
+
+ "github.com/go-webauthn/webauthn/webauthn"
)
func main() {
@@ -78,8 +80,23 @@ func main() {
log.Printf("Warning: failed to parse auth templates: %v", err)
}
+ // Initialize WebAuthn (optional - only if configured)
+ var wa *webauthn.WebAuthn
+ if cfg.WebAuthnRPID != "" && cfg.WebAuthnOrigin != "" {
+ var err error
+ wa, err = webauthn.New(&webauthn.Config{
+ RPDisplayName: "Task Dashboard",
+ RPID: cfg.WebAuthnRPID,
+ RPOrigins: []string{cfg.WebAuthnOrigin},
+ })
+ if err != nil {
+ log.Fatalf("Failed to initialize WebAuthn: %v", err)
+ }
+ log.Printf("WebAuthn initialized (RP ID: %s, Origin: %s)", cfg.WebAuthnRPID, cfg.WebAuthnOrigin)
+ }
+
// Initialize auth handlers
- authHandlers := auth.NewHandlers(authService, sessionManager, authTemplates)
+ authHandlers := auth.NewHandlers(authService, sessionManager, authTemplates, wa)
// Initialize API clients
todoistClient := api.NewTodoistClient(cfg.TodoistAPIKey)
@@ -146,6 +163,10 @@ func main() {
r.With(authRateLimiter.Limit).Post("/login", authHandlers.HandleLogin)
r.Post("/logout", authHandlers.HandleLogout)
+ // WebAuthn public routes (rate-limited)
+ r.With(authRateLimiter.Limit).Post("/passkeys/login/begin", authHandlers.HandlePasskeyLoginBegin)
+ r.With(authRateLimiter.Limit).Post("/passkeys/login/finish", authHandlers.HandlePasskeyLoginFinish)
+
// Serve static files (public)
fileServer := http.FileServer(http.Dir(cfg.StaticDir))
r.Handle("/static/*", http.StripPrefix("/static/", fileServer))
@@ -235,6 +256,12 @@ func main() {
r.Post("/shopping/mode/{store}/toggle", h.HandleShoppingModeToggle)
r.Post("/shopping/mode/{store}/complete", h.HandleShoppingModeComplete)
+ // Passkey management (WebAuthn)
+ r.Get("/settings/passkeys", authHandlers.HandleListPasskeys)
+ r.Post("/passkeys/register/begin", authHandlers.HandlePasskeyRegisterBegin)
+ r.Post("/passkeys/register/finish", authHandlers.HandlePasskeyRegisterFinish)
+ r.Delete("/passkeys/{id}", authHandlers.HandleDeletePasskey)
+
// Settings
r.Get("/settings", h.HandleSettingsPage)
r.Post("/settings/sync", h.HandleSyncSources)