diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-20 11:34:33 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-20 11:34:33 -1000 |
| commit | 08bbcf18b1207153983261652b4a43a9b36f386c (patch) | |
| tree | e6665608c7c8a87d6c789cf8b4c56d466df6bb8b /internal/auth/handlers.go | |
| parent | 07ba815e8517ee2d3a5fa531361bbd09bdfcbaa7 (diff) | |
Add session-based authentication
Implement secure authentication using scs session manager with SQLite
backing store and bcrypt password hashing.
- Add users and sessions tables (migration 004)
- Create internal/auth package with Service, Middleware, and Handlers
- Protect all routes except /login, /logout, /static/*
- Add login page template and logout button to dashboard
- Default credentials: admin/changeme (configurable via env vars)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/auth/handlers.go')
| -rw-r--r-- | internal/auth/handlers.go | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/internal/auth/handlers.go b/internal/auth/handlers.go new file mode 100644 index 0000000..17bcabd --- /dev/null +++ b/internal/auth/handlers.go @@ -0,0 +1,111 @@ +package auth + +import ( + "html/template" + "log" + "net/http" + + "github.com/alexedwards/scs/v2" +) + +// Handlers provides HTTP handlers for authentication +type Handlers struct { + service *Service + sessions *scs.SessionManager + middleware *Middleware + templates *template.Template +} + +// NewHandlers creates new auth handlers +func NewHandlers(service *Service, sessions *scs.SessionManager, templates *template.Template) *Handlers { + return &Handlers{ + service: service, + sessions: sessions, + middleware: NewMiddleware(sessions), + templates: templates, + } +} + +// Middleware returns the auth middleware for use in routes +func (h *Handlers) Middleware() *Middleware { + return h.middleware +} + +// HandleLoginPage renders the login form +func (h *Handlers) HandleLoginPage(w http.ResponseWriter, r *http.Request) { + // If already logged in, redirect to dashboard + if h.middleware.IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + + data := struct { + Error string + }{ + Error: "", + } + + if err := h.templates.ExecuteTemplate(w, "login.html", data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Error rendering login template: %v", err) + } +} + +// HandleLogin processes login form submission +func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + http.Error(w, "Failed to parse form", http.StatusBadRequest) + return + } + + username := r.FormValue("username") + password := r.FormValue("password") + + if username == "" || password == "" { + h.renderLoginError(w, "Username and password are required") + return + } + + user, err := h.service.Authenticate(username, password) + if err != nil { + log.Printf("Login failed for user %s: %v", username, err) + h.renderLoginError(w, "Invalid username or password") + return + } + + // Regenerate session token to prevent session fixation + if err := h.sessions.RenewToken(r.Context()); err != nil { + http.Error(w, "Failed to create session", http.StatusInternalServerError) + log.Printf("Failed to renew session token: %v", err) + return + } + + // Set user ID in session + h.middleware.SetUserID(r, user.ID) + + log.Printf("User %s logged in successfully", username) + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +// HandleLogout processes logout +func (h *Handlers) HandleLogout(w http.ResponseWriter, r *http.Request) { + if err := h.middleware.ClearSession(r); err != nil { + log.Printf("Error clearing session: %v", err) + } + + http.Redirect(w, r, "/login", http.StatusSeeOther) +} + +func (h *Handlers) renderLoginError(w http.ResponseWriter, errorMsg string) { + data := struct { + Error string + }{ + Error: errorMsg, + } + + w.WriteHeader(http.StatusUnauthorized) + if err := h.templates.ExecuteTemplate(w, "login.html", data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Error rendering login template: %v", err) + } +} |
