| package middleware |
|
|
| import ( |
| "errors" |
| "strings" |
|
|
| "github.com/Wei-Shaw/sub2api/internal/config" |
| "github.com/Wei-Shaw/sub2api/internal/pkg/googleapi" |
| "github.com/Wei-Shaw/sub2api/internal/service" |
|
|
| "github.com/gin-gonic/gin" |
| ) |
|
|
| |
| func APIKeyAuthGoogle(apiKeyService *service.APIKeyService, cfg *config.Config) gin.HandlerFunc { |
| return APIKeyAuthWithSubscriptionGoogle(apiKeyService, nil, cfg) |
| } |
|
|
| |
| |
| |
| |
| func APIKeyAuthWithSubscriptionGoogle(apiKeyService *service.APIKeyService, subscriptionService *service.SubscriptionService, cfg *config.Config) gin.HandlerFunc { |
| return func(c *gin.Context) { |
| if v := strings.TrimSpace(c.Query("api_key")); v != "" { |
| abortWithGoogleError(c, 400, "Query parameter api_key is deprecated. Use Authorization header or key instead.") |
| return |
| } |
| apiKeyString := extractAPIKeyForGoogle(c) |
| if apiKeyString == "" { |
| abortWithGoogleError(c, 401, "API key is required") |
| return |
| } |
|
|
| apiKey, err := apiKeyService.GetByKey(c.Request.Context(), apiKeyString) |
| if err != nil { |
| if errors.Is(err, service.ErrAPIKeyNotFound) { |
| abortWithGoogleError(c, 401, "Invalid API key") |
| return |
| } |
| abortWithGoogleError(c, 500, "Failed to validate API key") |
| return |
| } |
|
|
| if !apiKey.IsActive() { |
| abortWithGoogleError(c, 401, "API key is disabled") |
| return |
| } |
| if apiKey.User == nil { |
| abortWithGoogleError(c, 401, "User associated with API key not found") |
| return |
| } |
| if !apiKey.User.IsActive() { |
| abortWithGoogleError(c, 401, "User account is not active") |
| return |
| } |
|
|
| |
| if cfg.RunMode == config.RunModeSimple { |
| c.Set(string(ContextKeyAPIKey), apiKey) |
| c.Set(string(ContextKeyUser), AuthSubject{ |
| UserID: apiKey.User.ID, |
| Concurrency: apiKey.User.Concurrency, |
| }) |
| c.Set(string(ContextKeyUserRole), apiKey.User.Role) |
| setGroupContext(c, apiKey.Group) |
| _ = apiKeyService.TouchLastUsed(c.Request.Context(), apiKey.ID) |
| c.Next() |
| return |
| } |
|
|
| isSubscriptionType := apiKey.Group != nil && apiKey.Group.IsSubscriptionType() |
| if isSubscriptionType && subscriptionService != nil { |
| subscription, err := subscriptionService.GetActiveSubscription( |
| c.Request.Context(), |
| apiKey.User.ID, |
| apiKey.Group.ID, |
| ) |
| if err != nil { |
| abortWithGoogleError(c, 403, "No active subscription found for this group") |
| return |
| } |
|
|
| needsMaintenance, err := subscriptionService.ValidateAndCheckLimits(subscription, apiKey.Group) |
| if err != nil { |
| status := 403 |
| if errors.Is(err, service.ErrDailyLimitExceeded) || |
| errors.Is(err, service.ErrWeeklyLimitExceeded) || |
| errors.Is(err, service.ErrMonthlyLimitExceeded) { |
| status = 429 |
| } |
| abortWithGoogleError(c, status, err.Error()) |
| return |
| } |
|
|
| c.Set(string(ContextKeySubscription), subscription) |
|
|
| if needsMaintenance { |
| maintenanceCopy := *subscription |
| subscriptionService.DoWindowMaintenance(&maintenanceCopy) |
| } |
| } else { |
| if apiKey.User.Balance <= 0 { |
| abortWithGoogleError(c, 403, "Insufficient account balance") |
| return |
| } |
| } |
|
|
| c.Set(string(ContextKeyAPIKey), apiKey) |
| c.Set(string(ContextKeyUser), AuthSubject{ |
| UserID: apiKey.User.ID, |
| Concurrency: apiKey.User.Concurrency, |
| }) |
| c.Set(string(ContextKeyUserRole), apiKey.User.Role) |
| setGroupContext(c, apiKey.Group) |
| _ = apiKeyService.TouchLastUsed(c.Request.Context(), apiKey.ID) |
| c.Next() |
| } |
| } |
|
|
| |
| |
| |
| func extractAPIKeyForGoogle(c *gin.Context) string { |
| |
| if k := strings.TrimSpace(c.GetHeader("x-goog-api-key")); k != "" { |
| return k |
| } |
|
|
| |
| auth := strings.TrimSpace(c.GetHeader("Authorization")) |
| if auth != "" { |
| parts := strings.SplitN(auth, " ", 2) |
| if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") { |
| if k := strings.TrimSpace(parts[1]); k != "" { |
| return k |
| } |
| } |
| } |
|
|
| |
| if k := strings.TrimSpace(c.GetHeader("x-api-key")); k != "" { |
| return k |
| } |
|
|
| |
| if allowGoogleQueryKey(c.Request.URL.Path) { |
| if v := strings.TrimSpace(c.Query("key")); v != "" { |
| return v |
| } |
| } |
|
|
| return "" |
| } |
|
|
| func allowGoogleQueryKey(path string) bool { |
| return strings.HasPrefix(path, "/v1beta") || strings.HasPrefix(path, "/antigravity/v1beta") |
| } |
|
|
| func abortWithGoogleError(c *gin.Context, status int, message string) { |
| c.JSON(status, gin.H{ |
| "error": gin.H{ |
| "code": status, |
| "message": message, |
| "status": googleapi.HTTPStatusToGoogleStatus(status), |
| }, |
| }) |
| c.Abort() |
| } |
|
|