package authjwt import ( "encoding/json" "net/http" "path" "strings" "github.com/golang-jwt/jwt/v5" "code.nochebuena.dev/einherjar/auth/authmw" "code.nochebuena.dev/einherjar/contracts/logging" "code.nochebuena.dev/einherjar/core/xerrors" ) // AuthMiddleware verifies the Bearer access token and injects uid + claims into context // via authmw.SetTokenData. Downstream authmw.EnrichmentMiddleware reads them transparently. // // Accepts a Verifier — pass a Signer when the service issues tokens, or a // NewRSAPublicKeyVerifier/NewECPublicKeyVerifier when it only verifies. // // Requests to publicPaths are skipped without verification (path.Match wildcards supported). // Returns 401 on missing, invalid, or expired tokens. func AuthMiddleware(logger logging.Logger, verifier Verifier, publicPaths []string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, pattern := range publicPaths { if matched, _ := path.Match(pattern, r.URL.Path); matched { next.ServeHTTP(w, r) return } } authHeader := r.Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Bearer ") { writeUnauthorized(logger, w, r) return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") token, err := verifier.Verify(tokenStr) if err != nil { writeUnauthorized(logger, w, r) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { writeUnauthorized(logger, w, r) return } uid, _ := claims["sub"].(string) if uid == "" { writeUnauthorized(logger, w, r) return } ctx := authmw.SetTokenData(r.Context(), uid, map[string]any(claims)) next.ServeHTTP(w, r.WithContext(ctx)) }) } } func writeUnauthorized(logger logging.Logger, w http.ResponseWriter, r *http.Request) { logger.WithContext(r.Context()).Warn("auth-jwt: unauthorized", "error_code", string(xerrors.ErrUnauthorized), "status", http.StatusUnauthorized, ) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) _ = json.NewEncoder(w).Encode(map[string]any{ "code": string(xerrors.ErrUnauthorized), "message": "unauthorized", }) }