package api import ( "context" "net/http" "strings" "git.jnss.me/joakim/opal/internal/auth" "git.jnss.me/joakim/opal/internal/engine" ) type contextKey string const userIDKey contextKey = "userID" // AuthMiddleware validates JWT tokens or API keys and adds userID to context func AuthMiddleware() func(http.Handler) http.Handler { authCfg := auth.LoadConfig() return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get Authorization header authHeader := r.Header.Get("Authorization") if authHeader == "" { Error(w, http.StatusUnauthorized, "missing authorization header") return } // Parse "Bearer " parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { Error(w, http.StatusUnauthorized, "invalid authorization header format") return } token := parts[1] // Try JWT first if OAuth is enabled if authCfg.OAuthEnabled { if claims, err := auth.ValidateJWT(token, authCfg); err == nil { // Valid JWT - add userID to context ctx := context.WithValue(r.Context(), userIDKey, claims.UserID) next.ServeHTTP(w, r.WithContext(ctx)) return } } // Fall back to API key validation (for CLI compatibility) valid, userID, err := engine.ValidateAPIKey(token) if err != nil { Error(w, http.StatusInternalServerError, "failed to validate credentials") return } if !valid { Error(w, http.StatusUnauthorized, "invalid credentials") return } // Add userID to context ctx := context.WithValue(r.Context(), userIDKey, userID) next.ServeHTTP(w, r.WithContext(ctx)) }) } } // GetUserID extracts userID from request context func GetUserID(r *http.Request) int { userID, ok := r.Context().Value(userIDKey).(int) if !ok { return 0 } return userID } // DevAuthMiddleware always injects userID=1 into context — for local dev only func DevAuthMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), userIDKey, 1) next.ServeHTTP(w, r.WithContext(ctx)) }) } } // CORSMiddleware adds CORS headers for future web frontend func CORSMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } }