This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/breakpilot-compliance-sdk/services/api-gateway/internal/middleware/auth.go
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

118 lines
2.9 KiB
Go

// Package middleware provides HTTP middleware for the API Gateway
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// Claims represents JWT claims
type Claims struct {
TenantID string `json:"tenant_id"`
UserID string `json:"user_id"`
Scopes []string `json:"scopes"`
jwt.RegisteredClaims
}
// Auth middleware validates JWT tokens or API keys
func Auth(jwtSecret string) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Missing authorization header",
})
return
}
// Check for Bearer token (JWT)
if strings.HasPrefix(authHeader, "Bearer ") {
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
// Check if it's an API key (starts with pk_ or sk_)
if strings.HasPrefix(tokenString, "pk_") || strings.HasPrefix(tokenString, "sk_") {
// API Key authentication
if err := validateAPIKey(c, tokenString); err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid API key",
})
return
}
} else {
// JWT authentication
claims, err := validateJWT(tokenString, jwtSecret)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid token",
})
return
}
// Store claims in context
c.Set("tenant_id", claims.TenantID)
c.Set("user_id", claims.UserID)
c.Set("scopes", claims.Scopes)
}
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid authorization format",
})
return
}
c.Next()
}
}
func validateJWT(tokenString, secret string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, jwt.ErrTokenInvalidClaims
}
func validateAPIKey(c *gin.Context, apiKey string) error {
// In production, this would validate against a database of API keys
// For now, we extract tenant info from the X-Tenant-ID header
tenantID := c.GetHeader("X-Tenant-ID")
if tenantID == "" {
tenantID = "default"
}
c.Set("tenant_id", tenantID)
c.Set("user_id", "api-key-user")
c.Set("scopes", []string{"read", "write"})
return nil
}
// TenantIsolation ensures requests only access their own tenant's data
func TenantIsolation() gin.HandlerFunc {
return func(c *gin.Context) {
tenantID, exists := c.Get("tenant_id")
if !exists || tenantID == "" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "Tenant ID required",
})
return
}
// Add tenant ID to all database queries (handled by handlers)
c.Set("tenant_filter", tenantID)
c.Next()
}
}