internal/server/middleware.go (view raw)
1package server
2
3import (
4 "context"
5 "log/slog"
6 "net/http"
7 "strings"
8
9 "codeberg.org/maxwelljensen/weimar/internal/api"
10 "codeberg.org/maxwelljensen/weimar/internal/auth"
11)
12
13// requireAuth wraps a handler with session cookie authentication.
14// Unauthenticated requests receive a 401 JSON error.
15func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc {
16 return func(w http.ResponseWriter, r *http.Request) {
17 cookie, err := r.Cookie(auth.SessionCookieName)
18 if err != nil {
19 writeJSONError(w, http.StatusUnauthorized, "unauthorized")
20 return
21 }
22
23 user, err := s.auth.Authenticate(r.Context(), cookie.Value)
24 if err != nil {
25 slog.Debug("auth failed", "reason", err)
26 writeJSONError(w, http.StatusUnauthorized, "unauthorized")
27 return
28 }
29
30 ctx := context.WithValue(r.Context(), api.CtxKeyUser, user)
31 next(w, r.WithContext(ctx))
32 }
33}
34
35// requireAuthOrPublic wraps a handler with optional auth.
36// If the user has a valid session, it sets the user in context.
37// If not, the request passes through without a user context.
38// This is controlled by the require_auth_for_browse config setting.
39func (s *Server) requireAuthOrPublic(next http.HandlerFunc) http.HandlerFunc {
40 return func(w http.ResponseWriter, r *http.Request) {
41 if s.cfg.Auth.RequireAuthForBrowse {
42 // Act like requireAuth — reject unauthenticated.
43 s.requireAuth(next).ServeHTTP(w, r)
44 return
45 }
46
47 // Try to authenticate, but don't reject if it fails.
48 cookie, err := r.Cookie(auth.SessionCookieName)
49 if err != nil {
50 next(w, r)
51 return
52 }
53
54 user, err := s.auth.Authenticate(r.Context(), cookie.Value)
55 if err != nil {
56 next(w, r)
57 return
58 }
59
60 ctx := context.WithValue(r.Context(), api.CtxKeyUser, user)
61 next(w, r.WithContext(ctx))
62 }
63}
64
65// writeJSONError writes a JSON error response.
66func writeJSONError(w http.ResponseWriter, status int, msg string) {
67 w.Header().Set("Content-Type", "application/json")
68 w.WriteHeader(status)
69 w.Write([]byte(`{"error":"` + strings.ReplaceAll(msg, `"`, `\"`) + `"}`))
70}