package middleware import ( "log" "net/http" "strings" "gophergate/internal/db" "gophergate/internal/models" "github.com/gin-gonic/gin" ) func AuthMiddleware(database *db.DB, requireAuth bool) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { // Fallback to checking "Authentication" header in case the client library used the wrong name authHeader = c.GetHeader("Authentication") } if authHeader == "" { if requireAuth { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": gin.H{ "message": "Missing Authorization or Authentication header.", "type": "invalid_request_error", "param": nil, "code": "401", }, }) return } c.Next() return } token := strings.TrimPrefix(authHeader, "Bearer ") if token == authHeader { // No "Bearer " prefix if requireAuth { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": gin.H{ "message": "Invalid authorization header format. Bearer token required.", "type": "invalid_request_error", "param": nil, "code": "401", }, }) return } c.Next() return } // Try to resolve client from database with a read-only SELECT var clientID string err := database.Get(&clientID, "SELECT client_id FROM client_tokens WHERE token = ? AND is_active = 1", token) if err == nil { c.Set("auth", models.AuthInfo{ Token: token, ClientID: clientID, }) // Update last_used_at asynchronously so that database locks or write delays // do not block or fail the client's request authentication. go func(t string) { if _, updateErr := database.Exec("UPDATE client_tokens SET last_used_at = CURRENT_TIMESTAMP WHERE token = ?", t); updateErr != nil { log.Printf("Warning: failed to update client token last_used_at: %v", updateErr) } }(token) c.Next() } else { log.Printf("Token not found, inactive or error in DB: %s (err: %v)", token, err) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": gin.H{ "message": "Invalid or inactive client token.", "type": "invalid_request_error", "param": nil, "code": "401", }, }) } } }