package middleware import ( "context" "net/http" "codeberg.org/nextgo/dbots/internal/db" "codeberg.org/nextgo/dbots/internal/errorutil" "codeberg.org/nextgo/dbots/internal/token" "github.com/go-chi/render" ) type contextKey string var UserContextKey contextKey = "user" // AuthMiddleware reads the PASETO session cookie, verifies it, checks it // hasn't been revoked in the db, then sets the *db.User on the context. // does not block unauthenticated requests func AuthMiddleware(q *db.Queries, pasetoKeyHex string) func(http.Handler) http.Handler { key, err := token.KeyFromHex(pasetoKeyHex) if err != nil { panic("middleware: invalid PASETO key: " + err.Error()) } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie("session") if err != nil { next.ServeHTTP(w, r) return } claims, err := token.VerifyToken(key, c.Value) if err != nil { http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1, Path: "/"}) next.ServeHTTP(w, r) return } if _, err := q.GetSession(r.Context(), claims.JTI); err != nil { http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1, Path: "/"}) next.ServeHTTP(w, r) return } user, err := q.GetUser(r.Context(), claims.UserID) if err != nil { next.ServeHTTP(w, r) return } ctx := context.WithValue(r.Context(), UserContextKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } } // AuthGuardMiddleware blocks requests where no authenticated user was set. func AuthGuardMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, ok := r.Context().Value(UserContextKey).(*db.User); !ok { render.Render(w, r, errorutil.ErrUnauthorized) return } next.ServeHTTP(w, r) }) } // GetUser returns the authenticated user from context. // Only safe to call inside a route guarded by AuthGuardMiddleware. func GetUser(ctx context.Context) *db.User { return ctx.Value(UserContextKey).(*db.User) }