package httpauth import ( "context" "net/http" "path" "strings" "firebase.google.com/go/v4/auth" ) // TokenVerifier abstracts Firebase JWT verification. // *auth.Client satisfies this interface directly — no adapter needed in production. // Retained solely for unit-test mockability. type TokenVerifier interface { VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken string) (*auth.Token, error) } // ctxUIDKey and ctxClaimsKey are unexported typed context keys. // Using distinct types prevents collisions with keys from other packages. type ctxUIDKey struct{} type ctxClaimsKey struct{} func setTokenData(ctx context.Context, uid string, claims map[string]any) context.Context { ctx = context.WithValue(ctx, ctxUIDKey{}, uid) ctx = context.WithValue(ctx, ctxClaimsKey{}, claims) return ctx } func getUID(ctx context.Context) (string, bool) { uid, ok := ctx.Value(ctxUIDKey{}).(string) return uid, ok && uid != "" } func getClaims(ctx context.Context) (map[string]any, bool) { claims, ok := ctx.Value(ctxClaimsKey{}).(map[string]any) return claims, ok } // AuthMiddleware verifies the Bearer token and injects uid + claims into the // request context. Requests to publicPaths are skipped without token verification // (wildcards supported via path.Match). Returns 401 on missing or invalid tokens. func AuthMiddleware(verifier TokenVerifier, 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 ") { http.Error(w, "unauthorized", http.StatusUnauthorized) return } token := strings.TrimPrefix(authHeader, "Bearer ") decoded, err := verifier.VerifyIDTokenAndCheckRevoked(r.Context(), token) if err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } ctx := setTokenData(r.Context(), decoded.UID, decoded.Claims) next.ServeHTTP(w, r.WithContext(ctx)) }) } }