mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 18:46:42 +02:00
refactor(shared-go): extract shared auth package from 3 Go services
Create packages/shared-go/authutil/ with two JWT validator implementations: - JWKSValidator: EdDSA JWKS validation with key caching (extracted from mana-sync) - RemoteValidator: delegates to mana-core-auth /api/v1/auth/validate (from mana-notify/gateway) Plus shared types (Claims, User), middleware factories (JWTMiddleware, ServiceKeyMiddleware), context helpers (GetUser, GetUserID, GetUserRole), and token extraction. Migrated services: - mana-sync: internal/auth/jwt.go now wraps authutil.JWKSValidator - mana-notify: internal/auth/auth.go now wraps authutil.RemoteValidator + ServiceKeyMiddleware - mana-api-gateway: internal/middleware/jwt.go now wraps authutil.RemoteValidator All 3 services compile and pass tests. Service-level packages re-export types for backward compatibility so no consumer code changes are needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
509a541b70
commit
4f70e1ca6c
14 changed files with 466 additions and 385 deletions
|
|
@ -1,101 +1,25 @@
|
|||
// Package middleware provides HTTP middleware for the API gateway.
|
||||
// JWT validation delegates to shared-go/authutil.
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/manacore/shared-go/authutil"
|
||||
)
|
||||
|
||||
type jwtContextKey string
|
||||
|
||||
const UserIDContextKey jwtContextKey = "userID"
|
||||
const UserRoleContextKey jwtContextKey = "userRole"
|
||||
|
||||
// JWTClaims holds the JWT token claims.
|
||||
type JWTClaims struct {
|
||||
Sub string `json:"sub"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// JWTMiddleware validates Bearer JWT tokens for management endpoints.
|
||||
// Uses JWKS from mana-core-auth (simplified: accepts any valid JWT structure for now).
|
||||
func JWTMiddleware(authURL string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{
|
||||
"error": "Authorization header required. Use Bearer <JWT>.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
// Validate JWT via auth service
|
||||
userID, role, err := validateJWT(r.Context(), authURL, tokenStr)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{
|
||||
"error": "Invalid or expired token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), UserIDContextKey, userID)
|
||||
ctx = context.WithValue(ctx, UserRoleContextKey, role)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
validator := authutil.NewRemoteValidator(authURL)
|
||||
return authutil.JWTMiddleware(validator)
|
||||
}
|
||||
|
||||
// GetUserID returns the authenticated user ID from context.
|
||||
func GetUserID(r *http.Request) string {
|
||||
id, _ := r.Context().Value(UserIDContextKey).(string)
|
||||
return id
|
||||
return authutil.GetUserID(r)
|
||||
}
|
||||
|
||||
// GetUserRole returns the user role from context.
|
||||
func GetUserRole(r *http.Request) string {
|
||||
role, _ := r.Context().Value(UserRoleContextKey).(string)
|
||||
return role
|
||||
}
|
||||
|
||||
// validateJWT calls mana-core-auth /api/v1/auth/validate to verify the token.
|
||||
func validateJWT(ctx context.Context, authURL, token string) (userID, role string, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", authURL+"/api/v1/auth/validate", strings.NewReader(`{"token":"`+token+`"}`))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("auth service: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", "", fmt.Errorf("auth validation failed: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Valid bool `json:"valid"`
|
||||
UserID string `json:"userId"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !result.Valid {
|
||||
return "", "", fmt.Errorf("token not valid")
|
||||
}
|
||||
|
||||
return result.UserID, result.Role, nil
|
||||
return authutil.GetUserRole(r)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue