package server import ( "context" "log/slog" "net/http" "strings" "github.com/mjensen/weimar/internal/api" "github.com/mjensen/weimar/internal/auth" ) // requireAuth wraps a handler with session cookie authentication. // Unauthenticated requests receive a 401 JSON error. func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(auth.SessionCookieName) if err != nil { writeJSONError(w, http.StatusUnauthorized, "unauthorized") return } user, err := s.auth.Authenticate(r.Context(), cookie.Value) if err != nil { slog.Debug("auth failed", "reason", err) writeJSONError(w, http.StatusUnauthorized, "unauthorized") return } ctx := context.WithValue(r.Context(), api.CtxKeyUser, user) next(w, r.WithContext(ctx)) } } // requireAuthOrPublic wraps a handler with optional auth. // If the user has a valid session, it sets the user in context. // If not, the request passes through without a user context. // This is controlled by the require_auth_for_browse config setting. func (s *Server) requireAuthOrPublic(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if s.cfg.Auth.RequireAuthForBrowse { // Act like requireAuth — reject unauthenticated. s.requireAuth(next).ServeHTTP(w, r) return } // Try to authenticate, but don't reject if it fails. cookie, err := r.Cookie(auth.SessionCookieName) if err != nil { next(w, r) return } user, err := s.auth.Authenticate(r.Context(), cookie.Value) if err != nil { next(w, r) return } ctx := context.WithValue(r.Context(), api.CtxKeyUser, user) next(w, r.WithContext(ctx)) } } // writeJSONError writes a JSON error response. func writeJSONError(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) w.Write([]byte(`{"error":"` + strings.ReplaceAll(msg, `"`, `\"`) + `"}`)) }