package mw import ( "context" "log" "net/http" "slices" "strings" "crussell/auth" ) type contextKey string const ( UserIDKey contextKey = "user_id" UserRoleKey contextKey = "user_role" JTIKey contextKey = "jti" ) // RequireAuth middleware - validates JWT and adds user info to context func RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { RespondJSON(w, http.StatusUnauthorized, map[string]string{"error": "missing or invalid authorization header"}) return } tokenString := strings.TrimPrefix(authHeader, "Bearer ") userID, role, jti, err := auth.VerifyToken(tokenString, r.Context()) if err != nil { RespondJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid token"}) return } // Add user info and JTI to context ctx := context.WithValue(r.Context(), UserIDKey, userID) ctx = context.WithValue(ctx, UserRoleKey, role) ctx = context.WithValue(ctx, JTIKey, jti) next.ServeHTTP(w, r.WithContext(ctx)) }) } // OptionalAuth middleware - extracts user info if token present, otherwise passes through func OptionalAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { tokenString := strings.TrimPrefix(authHeader, "Bearer ") userID, role, jti, err := auth.VerifyToken(tokenString, r.Context()) if err != nil { log.Printf("OptionalAuth: invalid token ignored") } else { ctx := context.WithValue(r.Context(), UserIDKey, userID) ctx = context.WithValue(ctx, UserRoleKey, role) ctx = context.WithValue(ctx, JTIKey, jti) next.ServeHTTP(w, r.WithContext(ctx)) return } } next.ServeHTTP(w, r) }) } // RequireRole middleware - checks if user has required role(s) func RequireRole(allowedRoles ...string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role, ok := r.Context().Value(UserRoleKey).(string) if !ok { RespondJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"}) return } // Check if user has one of the allowed roles hasRole := slices.Contains(allowedRoles, role) if !hasRole { RespondJSON(w, http.StatusForbidden, map[string]string{"error": "forbidden"}) return } next.ServeHTTP(w, r) }) } } // RequireVerified middleware - only allows verified_email and admin func RequireVerified(next http.Handler) http.Handler { return RequireRole("verified_email", "admin")(next) } // RequireNonGuest middleware - blocks guest-role tokens from money routes. // Guests are created with account_role='guest' (account_type is NOT checked — // real guests are seeded with account_type='email'), and they have no login // flow, so a legitimate guest never holds a JWT. This is a defense-in-depth // guard: any token whose role claim is 'guest' (forged/minted guest tokens or // future changes) is refused. A missing role is treated as not-guest and passes // through — RequireAuth guarantees the role is present when this runs, matching // the trust model of the in-handler isVerifiedRole checks. func RequireNonGuest(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role, ok := r.Context().Value(UserRoleKey).(string) if ok && role == "guest" { RespondJSON(w, http.StatusForbidden, map[string]string{"error": "forbidden"}) return } next.ServeHTTP(w, r) }) } // RequireAdmin middleware - only allows admin and validates the user ID is present // in context, so handlers don't need to re-check. The user ID is always set by // RequireAuth before this runs, but this adds a defensive safety net. func RequireAdmin(next http.Handler) http.Handler { return RequireRole("admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { uid, ok := r.Context().Value(UserIDKey).(string) if !ok || uid == "" { RespondJSON(w, http.StatusUnauthorized, map[string]string{"error": "Authentication required"}) return } next.ServeHTTP(w, r) })) } // Helper functions to get user info from context func GetUserID(ctx context.Context) (string, bool) { userID, ok := ctx.Value(UserIDKey).(string) return userID, ok } func GetUserRole(ctx context.Context) (string, bool) { role, ok := ctx.Value(UserRoleKey).(string) return role, ok } func GetJTI(ctx context.Context) (string, bool) { jti, ok := ctx.Value(JTIKey).(string) return jti, ok }