// Package middleware provides HTTP middleware for the API package middleware import ( "context" "net/http" "strings" "github.com/omixlab/mosis-portal/internal/auth" "github.com/omixlab/mosis-portal/internal/database" ) type contextKey string const ( DeveloperContextKey contextKey = "developer" ClaimsContextKey contextKey = "claims" ) // AuthMiddleware handles JWT and API key authentication type AuthMiddleware struct { jwtManager *auth.JWTManager db *database.DB } // NewAuthMiddleware creates a new auth middleware func NewAuthMiddleware(jwtManager *auth.JWTManager, db *database.DB) *AuthMiddleware { return &AuthMiddleware{ jwtManager: jwtManager, db: db, } } // RequireAuth requires a valid JWT or API key func (m *AuthMiddleware) RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Try Bearer token first authHeader := r.Header.Get("Authorization") if strings.HasPrefix(authHeader, "Bearer ") { token := strings.TrimPrefix(authHeader, "Bearer ") claims, err := m.jwtManager.ValidateAccessToken(token) if err != nil { http.Error(w, "Invalid or expired token", http.StatusUnauthorized) return } // Add claims to context ctx := context.WithValue(r.Context(), ClaimsContextKey, claims) next.ServeHTTP(w, r.WithContext(ctx)) return } // Try API key apiKey := r.Header.Get("X-API-Key") if apiKey != "" { developer, err := m.db.ValidateAPIKey(r.Context(), apiKey) if err != nil { http.Error(w, "Invalid API key", http.StatusUnauthorized) return } // Add developer to context ctx := context.WithValue(r.Context(), DeveloperContextKey, developer) next.ServeHTTP(w, r.WithContext(ctx)) return } http.Error(w, "Authorization required", http.StatusUnauthorized) }) } // OptionalAuth adds developer info to context if authenticated, but doesn't require it func (m *AuthMiddleware) OptionalAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Try Bearer token authHeader := r.Header.Get("Authorization") if strings.HasPrefix(authHeader, "Bearer ") { token := strings.TrimPrefix(authHeader, "Bearer ") claims, err := m.jwtManager.ValidateAccessToken(token) if err == nil { ctx := context.WithValue(r.Context(), ClaimsContextKey, claims) next.ServeHTTP(w, r.WithContext(ctx)) return } } // Try API key apiKey := r.Header.Get("X-API-Key") if apiKey != "" { developer, err := m.db.ValidateAPIKey(r.Context(), apiKey) if err == nil { ctx := context.WithValue(r.Context(), DeveloperContextKey, developer) next.ServeHTTP(w, r.WithContext(ctx)) return } } // Continue without authentication next.ServeHTTP(w, r) }) } // GetClaims retrieves JWT claims from context func GetClaims(ctx context.Context) *auth.Claims { claims, ok := ctx.Value(ClaimsContextKey).(*auth.Claims) if !ok { return nil } return claims } // GetDeveloperID retrieves the developer ID from context (from JWT or API key) func GetDeveloperID(ctx context.Context) string { // First check JWT claims claims := GetClaims(ctx) if claims != nil { return claims.Subject } // Then check developer from API key developer, ok := ctx.Value(DeveloperContextKey).(*database.Developer) if ok && developer != nil { return developer.ID } return "" }