package api import ( "embed" "fmt" "io/fs" "net/http" "os" "time" "github.com/go-chi/chi/v5" "github.com/johanj/clavitor/lib" ) // NewRouter creates the main router with all routes registered. func NewRouter(cfg *lib.Config, webFS embed.FS) *chi.Mux { r := chi.NewRouter() h := NewHandlers(cfg) // Global middleware r.Use(LoggingMiddleware) r.Use(CORSMiddleware) r.Use(SecurityHeadersMiddleware) r.Use(RateLimitMiddleware(120)) // 120 req/min per IP r.Use(L1Middleware(cfg.DataDir)) // stateless: extract L1 from Bearer, open DB, forget // Health check (unauthenticated — no Bearer needed) r.Get("/health", h.Health) // Ping — minimal latency probe for looking glass (no DB, no auth) node, _ := os.Hostname() r.Get("/ping", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") body := fmt.Sprintf(`{"ok":true,"node":"%s","ts":%d}`, node, time.Now().Unix()) w.Write([]byte(body)) }) // Auth endpoints (unauthenticated — no Bearer, DB found by glob) r.Get("/api/auth/status", h.AuthStatus) r.Post("/api/auth/register/begin", h.AuthRegisterBegin) r.Post("/api/auth/register/complete", h.AuthRegisterComplete) r.Post("/api/auth/login/begin", h.AuthLoginBegin) r.Post("/api/auth/login/complete", h.AuthLoginComplete) // Legacy setup (only works when no credentials exist — for tests) r.Post("/api/auth/setup", h.Setup) // API routes (authenticated — L1 in Bearer, already validated by L1Middleware) r.Route("/api", func(r chi.Router) { mountAPIRoutes(r, h) }) // --- Vault App UI at /app/* --- appRoot, err := fs.Sub(webFS, "web") if err == nil { appServer := http.FileServer(http.FS(appRoot)) r.Get("/app", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/app/", http.StatusMovedPermanently) }) r.Handle("/app/*", http.StripPrefix("/app", appServer)) } return r } // mountAPIRoutes registers the authenticated API handlers on the given router. func mountAPIRoutes(r chi.Router, h *Handlers) { // Vault info (for Tokens page config snippets) r.Get("/vault-info", h.VaultInfo) // Entries CRUD r.Get("/entries", h.ListEntries) r.Post("/entries", h.CreateEntry) r.Get("/entries/{id}", h.GetEntry) r.Put("/entries/{id}", h.UpdateEntry) r.Delete("/entries/{id}", h.DeleteEntry) // Search r.Get("/search", h.SearchEntries) // Password generator r.Get("/generate", h.GeneratePassword) // Import r.Post("/import", h.ImportEntries) r.Post("/import/confirm", h.ImportConfirm) // Audit log r.Get("/audit", h.GetAuditLog) // Extension API r.Get("/ext/totp/{id}", h.GetTOTP) r.Get("/ext/match", h.MatchURL) // Backups r.Get("/backups", h.ListBackups) r.Post("/backups", h.CreateBackup) r.Post("/backups/restore", h.RestoreBackup) // Agent management r.Post("/agents", h.HandleCreateAgent) r.Get("/agents", h.HandleListAgents) r.Get("/agents/{id}", h.HandleGetAgent) r.Put("/agents/{id}/whitelist", h.HandleUpdateAgentWhitelist) r.Put("/agents/{id}/rate-limits", h.HandleUpdateAgentRateLimits) r.Post("/agents/{id}/lock", h.HandleLockAgent) r.Post("/agents/{id}/unlock", h.HandleUnlockAgent) r.Delete("/agents/{id}", h.HandleRevokeAgent) // Vault lock r.Get("/vault-lock", h.HandleVaultLockStatus) r.Post("/vault-unlock", h.HandleVaultUnlock) // WebAuthn r.Post("/webauthn/register/begin", h.HandleWebAuthnRegisterBegin) r.Post("/webauthn/register/complete", h.HandleWebAuthnRegisterComplete) r.Post("/webauthn/auth/begin", h.HandleWebAuthnAuthBegin) r.Post("/webauthn/auth/complete", h.HandleWebAuthnAuthComplete) r.Get("/webauthn/credentials", h.HandleListWebAuthnCredentials) r.Delete("/webauthn/credentials/{id}", h.HandleDeleteWebAuthnCredential) }