package handler import ( "net/http" "strings" "aiaa-notification-service/internal/cache" "aiaa-notification-service/internal/model" "aiaa-notification-service/internal/store" "github.com/gin-gonic/gin" ) // AdminAuth checks the admin key for management API endpoints. func AdminAuth(adminKey string) gin.HandlerFunc { return func(c *gin.Context) { key := extractBearer(c) if key == "" || key != adminKey { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) return } c.Next() } } // SourceAuth identifies the source by its API key and sets it in context. func SourceAuth(s *store.Store) gin.HandlerFunc { return func(c *gin.Context) { key := extractBearer(c) if key == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing api key"}) return } source, err := s.GetSourceByAPIKey(c.Request.Context(), key) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid api key"}) return } c.Set("source", source) c.Next() } } // RateLimit applies per-source rate limiting. func RateLimit(cache *cache.Cache, defaultLimit int) gin.HandlerFunc { return func(c *gin.Context) { source, exists := c.Get("source") if !exists { c.Next() return } src := source.(*model.Source) allowed, retryAfter, err := cache.CheckRateLimit(c.Request.Context(), src.ID, defaultLimit) if err != nil { // Redis error — allow pass through c.Next() return } if !allowed { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "error": "rate_limit_exceeded", "message": "too many requests", "retry_after": retryAfter, }) return } c.Next() } } func extractBearer(c *gin.Context) string { auth := c.GetHeader("Authorization") if auth == "" { return "" } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") { return "" } return parts[1] }