package middleware import ( "context" "net/http" "strings" "github.com/rede5/gohorsejobs/backend/internal/core/ports" ) type contextKey string const ( ContextUserID contextKey = "userID" ContextTenantID contextKey = "tenantID" ContextRoles contextKey = "roles" ) type Middleware struct { authService ports.AuthService } func NewMiddleware(authService ports.AuthService) *Middleware { return &Middleware{authService: authService} } func (m *Middleware) HeaderAuthGuard(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, "Missing Authorization Header", http.StatusUnauthorized) return } parts := strings.Split(authHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { http.Error(w, "Invalid Header Format", http.StatusUnauthorized) return } token := parts[1] claims, err := m.authService.ValidateToken(token) if err != nil { http.Error(w, "Invalid Token: "+err.Error(), http.StatusUnauthorized) return } // Inject into Context ctx := context.WithValue(r.Context(), ContextUserID, claims["sub"]) ctx = context.WithValue(ctx, ContextTenantID, claims["tenant"]) ctx = context.WithValue(ctx, ContextRoles, claims["roles"]) next.ServeHTTP(w, r.WithContext(ctx)) }) } // TenantGuard ensures that the request is made by a user belonging to the prompt tenant // Note: In this architecture, the token *defines* the tenant. So HeaderAuthGuard implicitly guards the tenant. // This middleware is for extra checks if URL params conflict with Token tenant. func (m *Middleware) TenantGuard(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tenantID := r.Context().Value(ContextTenantID) if tenantID == nil || tenantID == "" { http.Error(w, "Tenant Context Missing", http.StatusForbidden) return } // Logic to compare with URL param if needed... next.ServeHTTP(w, r) }) }