package middleware import ( "net/http" "strings" "git.nakama.town/fmartingr/hako/internal/auth" "git.nakama.town/fmartingr/hako/internal/auth/store" "git.nakama.town/fmartingr/hako/internal/server/webcontext" "github.com/google/uuid" ) // AuthMiddleware is a global middleware that validates JWT tokens if present // and adds user info to context. It does not block requests if no token is provided. // This allows routes to optionally use authentication information. // The middleware also verifies that the user exists in the database to prevent // authentication with tokens for deleted users. func AuthMiddleware(jwtService *auth.JWTService, userStore *store.UserStore) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Create webcontext for this request c := webcontext.NewWebContext(w, r) var tokenString string // Extract token from Authorization header if present authHeader := r.Header.Get("Authorization") if authHeader != "" { // Check Bearer prefix parts := strings.Split(authHeader, " ") if len(parts) == 2 && parts[0] == "Bearer" { tokenString = parts[1] } } // Fallback: extract token from query parameter if not in header // This allows embedding authenticated URLs (e.g., for images) if tokenString == "" { tokenString = r.URL.Query().Get("token") } // Validate token if present if tokenString != "" { // Validate token - if valid, verify user exists; if invalid, continue without context claims, err := jwtService.ValidateToken(tokenString) if err == nil { // Token is valid, verify user exists in database userID, parseErr := uuid.Parse(claims.UserID) if parseErr == nil { user, userErr := userStore.FindByID(userID) if userErr == nil && user != nil { // User exists, add user info to context // Use role from database (source of truth) role := user.Role if role == "" { role = "user" } c.SetUserWithRole(claims.UserID, claims.Email, role) // Update request with new context r = c.Request() } // If user doesn't exist, continue without setting context (don't block) } } // If token is invalid, continue without setting context (don't block) } // Continue with request (with or without user context) // Use the updated request if context was modified next.ServeHTTP(w, r) }) } } // RequireAuthMiddleware is a middleware that requires authentication. // It checks if user info is in context and returns 401 if not authenticated. func RequireAuthMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Create webcontext for this request c := webcontext.NewWebContext(w, r) // Check if user is authenticated if !c.UserIsLogged() { http.Error(w, "Authorization required", http.StatusUnauthorized) return } // User is authenticated, continue next.ServeHTTP(w, r) }) } } // RequireAdminMiddleware is a middleware that requires admin role. // It checks if user is authenticated and has admin role, returns 403 if not admin. func RequireAdminMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Create webcontext for this request c := webcontext.NewWebContext(w, r) // Check if user is authenticated if !c.UserIsLogged() { http.Error(w, "Authorization required", http.StatusUnauthorized) return } // Check if user has admin role role := c.GetUserRole() if role != "admin" { http.Error(w, "Admin access required", http.StatusForbidden) return } // User is authenticated and is admin, continue next.ServeHTTP(w, r) }) } }