docs(httpauth-firebase): fix rbac tier reference from 1 to 0

rbac is a Tier 0 module (no micro-lib dependencies). The dependency line
incorrectly cited it as Tier 1. The module's own tier (4) is unchanged —
it remains the auth layer above the transport infrastructure.
This commit is contained in:
2026-03-19 13:44:45 +00:00
commit d1de096c72
17 changed files with 1188 additions and 0 deletions

70
auth.go Normal file
View File

@@ -0,0 +1,70 @@
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))
})
}
}