package auth import ( "context" "net/http" "aether/pkg/response" ) type contextKey int const keyUser contextKey = iota // RequireAuth loads the session user and injects it into context. // Returns 401 if not authenticated. func RequireAuth(ss *SessionStore, store *Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id, ok := ss.GetUserID(r) if !ok { response.Error(w, http.StatusUnauthorized, "authentication required") return } user, err := store.GetByID(r.Context(), id) if err != nil { response.Error(w, http.StatusUnauthorized, "authentication required") return } ctx := context.WithValue(r.Context(), keyUser, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } } // RequireAdmin additionally checks that the user is an admin. func RequireAdmin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := UserFromContext(r.Context()) if user == nil || !user.IsAdmin { response.Error(w, http.StatusForbidden, "admin access required") return } next.ServeHTTP(w, r) }) } // CSRF validates the double-submit CSRF token for state-changing requests. func CSRF(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet, http.MethodHead, http.MethodOptions: next.ServeHTTP(w, r) return } if !ValidateCSRF(r) { response.Error(w, http.StatusForbidden, "invalid CSRF token") return } next.ServeHTTP(w, r) }) } // UserFromContext retrieves the authenticated user from context (may be nil). func UserFromContext(ctx context.Context) *User { u, _ := ctx.Value(keyUser).(*User) return u }