Files
2026-07-13 13:39:47 +08:00

98 lines
2.7 KiB
Go

package middleware
import (
"net/http"
"strings"
"caorushizi.cn/mediago/internal/api/dto"
"caorushizi.cn/mediago/internal/api/handler"
"caorushizi.cn/mediago/internal/i18n"
"caorushizi.cn/mediago/internal/logger"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// AuthMiddleware creates a Gin middleware that validates apiKey from the
// Authorization header (Bearer token) or query parameter (for SSE).
// Whitelisted paths bypass authentication.
func AuthMiddleware(confStore handler.ConfigStore) gin.HandlerFunc {
whitelist := map[string]bool{
"/healthy": true,
"/api/auth/setup": true,
"/api/auth/signin": true,
"/api/auth/status": true,
}
return func(c *gin.Context) {
path := c.Request.URL.Path
// Whitelist check
if whitelist[path] || strings.HasPrefix(path, "/swagger/") ||
strings.HasPrefix(path, "/player") ||
strings.HasPrefix(path, "/api/v1/") ||
strings.HasPrefix(path, "/videos/") ||
strings.HasPrefix(path, "/assets/") ||
path == "/favicon.ico" ||
path == "/" {
c.Next()
return
}
// SPA frontend routes (non-API paths without dots are likely client-side routes)
if !strings.HasPrefix(path, "/api/") && !strings.Contains(path, ".") {
c.Next()
return
}
// Get stored apiKey from config
storedKey, _ := confStore.Get("apiKey").(string)
if storedKey == "" {
// No apiKey configured, auth not set up yet — allow access
c.Next()
return
}
// Extract token from Authorization header or query param
token := extractToken(c)
if token == "" {
logger.Warn("Auth: missing token", zap.String("path", path), zap.String("clientIP", c.ClientIP()))
c.AbortWithStatusJSON(http.StatusUnauthorized, dto.ErrorResponse{
Success: false,
Code: http.StatusUnauthorized,
Message: i18n.T(c, i18n.MsgUnauthorized),
})
return
}
if token != storedKey {
logger.Warn("Auth: invalid token", zap.String("path", path), zap.String("clientIP", c.ClientIP()))
c.AbortWithStatusJSON(http.StatusUnauthorized, dto.ErrorResponse{
Success: false,
Code: http.StatusUnauthorized,
Message: i18n.T(c, i18n.MsgUnauthorized),
})
return
}
c.Next()
}
}
// extractToken gets the API key from request headers or query param.
// Priority: X-API-Key header > Authorization: Bearer header > ?token= query param.
func extractToken(c *gin.Context) string {
// Try X-API-Key header first (used by frontend http client)
if key := c.GetHeader("X-API-Key"); key != "" {
return key
}
// Try Authorization header
auth := c.GetHeader("Authorization")
if strings.HasPrefix(auth, "Bearer ") {
return strings.TrimPrefix(auth, "Bearer ")
}
// Fallback to query param (for SSE EventSource which can't set headers)
return c.Query("token")
}