Documentation
¶
Index ¶
- Constants
- Variables
- func AuthMiddleware(keyRing *jwtpkg.KeyRing, expectedTenant, expectedAudience string, ...) func(http.Handler) http.Handler
- func CORSMiddleware(allowedOrigins []string) func(http.Handler) http.Handler
- func ClientIPFromContext(ctx context.Context) string
- func ClientIPMiddleware(trusted []*net.IPNet) func(http.Handler) http.Handler
- func HealthMiddleware(probe ReadinessProbe, next http.Handler) http.Handler
- func JWKSMiddleware(keyRing *jwtpkg.KeyRing) func(http.Handler) http.Handler
- func LoggingMiddleware(logger *zap.Logger) func(http.Handler) http.Handler
- func ParseAllowedOrigins(raw string, allowCredentials bool) ([]string, error)
- func ParseTrustedProxies(raw string) ([]*net.IPNet, error)
- func RateLimitMiddleware(limits []PathLimit, logger *zap.Logger) func(http.Handler) http.Handler
- func RecoverMiddleware(logger *zap.Logger) func(http.Handler) http.Handler
- type FixedWindowLimiter
- type PathLimit
- type RateLimiter
- type ReadinessProbe
Constants ¶
const ClientIPHeader = "X-Client-IP"
ClientIPHeader is set by ClientIPMiddleware to the resolved client IP. Downstream handlers (audit log, rate limiter) read this header instead of X-Forwarded-For so they cannot be spoofed.
Variables ¶
var AuthExemptPaths = map[string]bool{ "/identity.IdentityService/BeginOAuthLogin": true, "/identity.IdentityService/OAuthLogin": true, "/identity.IdentityService/PasswordLogin": true, "/identity.IdentityService/PasswordSignup": true, "/identity.IdentityService/RefreshToken": true, "/identity.IdentityService/Logout": true, "/identity.IdentityService/GetCurrentUser": true, "/identity.IdentityService/BeginPasskeyLogin": true, "/identity.IdentityService/CompletePasskeyLogin": true, "/identity.IdentityService/InitiateQrLogin": true, "/identity.IdentityService/PollQrLogin": true, "/identity.IdentityService/AcceptInvitation": true, "/identity.IdentityService/RequestAdminHelp": true, "/identity.IdentityService/VerifyTotp": true, "/identity.IdentityService/RequestPasswordReset": true, "/identity.IdentityService/ConfirmPasswordReset": true, "/identity.IdentityService/VerifyEmail": true, "/identity.IdentityService/ConfirmEmailChange": true, "/.well-known/jwks.json": true, "/health": true, "/healthz": true, }
AuthExemptPaths lists URL paths that do not require a valid JWT. Connect-Go uses the proto service/method as the URL path.
var ErrAllowedOriginsEmpty = errors.New("cors: no allowed origins configured")
ErrAllowedOriginsEmpty is returned by ParseAllowedOrigins when the resolved list contains no origins.
var ErrInvalidTrustedProxy = errors.New("invalid trusted proxy entry")
ErrInvalidTrustedProxy is returned by ParseTrustedProxies for entries the parser cannot interpret.
Functions ¶
func AuthMiddleware ¶
func AuthMiddleware(keyRing *jwtpkg.KeyRing, expectedTenant, expectedAudience string, requireAudience bool) func(http.Handler) http.Handler
AuthMiddleware verifies JWT Bearer tokens on non-exempt paths and injects the authenticated user ID into the X-Authenticated-User-Id request header so downstream Connect handlers can read it.
expectedTenant, when non-empty, is enforced on every verified token: tokens whose "tenant" claim does not match are rejected. Pass an empty string to disable the cross-tenant check.
expectedAudience and requireAudience are passed through to jwtpkg.VerifyAccessToken — see that function for the audience policy.
For auth-exempt paths the middleware still attempts to parse and verify a token when one is present (e.g. GetCurrentUser may optionally read the caller identity) but never rejects the request.
func CORSMiddleware ¶
CORSMiddleware handles CORS preflight requests and injects response headers for allowed origins. allowedOrigins must be the validated output of ParseAllowedOrigins. Match is exact case-sensitive on scheme+host+port.
func ClientIPFromContext ¶ added in v0.6.0
ClientIPFromContext returns the resolved client IP stored by ClientIPMiddleware. Returns "" if the middleware did not run.
func ClientIPMiddleware ¶ added in v0.6.0
ClientIPMiddleware resolves the client IP by walking X-Forwarded-For right-to-left, skipping any addresses that themselves come from a trusted proxy CIDR. The first untrusted address is the real client. If no XFF is present or no trusted proxies are configured, falls back to the TCP peer address.
The resolved IP is set on both the X-Client-IP header (for downstream handlers that read headers) and on the request context.
func HealthMiddleware ¶
func HealthMiddleware(probe ReadinessProbe, next http.Handler) http.Handler
HealthMiddleware serves /livez (always 200 if the process is alive) and /readyz (200 only when probe.Ready returns nil). The legacy paths /health, /healthz, and / map to /livez for backwards compatibility.
Pass a nil probe to disable readiness checks (the endpoint then always returns 200, useful for tests).
func JWKSMiddleware ¶
JWKSMiddleware serves the /.well-known/jwks.json endpoint from the key ring. The response contains the RSA public keys for all keys in the ring so that third-party services can verify tokens without sharing a secret.
func LoggingMiddleware ¶
LoggingMiddleware logs every request's method, path, response status code, duration, and remote address using the provided zap logger.
func ParseAllowedOrigins ¶ added in v0.6.0
ParseAllowedOrigins splits a comma-separated origin list and validates each entry. When allowCredentials is true the function refuses dangerous values: the wildcard "*", literal "null", empty entries, and malformed URLs. The returned slice preserves input order and case.
Why: this middleware unconditionally sets Access-Control-Allow-Credentials, so a wildcard origin in the allowlist would expose authenticated state to any origin. Failing fast at startup is the only safe behaviour.
func ParseTrustedProxies ¶ added in v0.6.0
ParseTrustedProxies parses a comma-separated list of CIDRs. Whitespace around entries is ignored. The empty string returns an empty slice, meaning "trust no proxies" — X-Forwarded-For is ignored entirely and only the TCP peer address is honoured.
func RateLimitMiddleware ¶ added in v0.6.0
RateLimitMiddleware enforces per-IP+path quotas using the configured PathLimit entries. Requests whose path matches a PathLimit are checked against its limiter; everything else passes through.
The client IP comes from ClientIPHeader (set by ClientIPMiddleware), so this middleware must be installed after it. Rate-limited responses return 429 with a Retry-After header.
func RecoverMiddleware ¶ added in v0.6.0
RecoverMiddleware catches panics in any downstream handler, logs them with the stack trace, and returns a generic 500 to the client.
Connect-Go does not recover panics by itself — a nil deref in any RPC handler would otherwise crash the goroutine and propagate to the HTTP server. At a million requests/day even a 0.001 % panic rate hits real users; we prefer a logged 500 to an unexplained TCP reset.
Types ¶
type FixedWindowLimiter ¶ added in v0.6.0
type FixedWindowLimiter struct {
// contains filtered or unexported fields
}
FixedWindowLimiter is a bounded, fixed-window in-memory rate limiter. Each key gets `limit` permits per `window`. Window boundaries are aligned to wall-clock seconds for simplicity; that's fine for human-scale abuse.
func NewFixedWindowLimiter ¶ added in v0.6.0
func NewFixedWindowLimiter(window time.Duration, limit, maxSize int) *FixedWindowLimiter
NewFixedWindowLimiter returns a limiter with the given per-key limit per window. limit <= 0 disables the limiter — Allow always returns true.
type PathLimit ¶ added in v0.6.0
type PathLimit struct {
PathPrefix string
Limiter RateLimiter
Tag string // metric label / log field
}
PathLimit binds a path prefix to a RateLimiter. The middleware below gates each request by the first matching PathLimit entry.
type RateLimiter ¶ added in v0.6.0
RateLimiter gates requests by a string key (typically a client IP plus a path bucket). The in-memory implementation is per-replica; a Redis-backed variant can replace it without changing call sites.
type ReadinessProbe ¶ added in v0.6.0
ReadinessProbe checks that the dependencies needed to serve traffic are reachable. Implementations should be cheap and bounded — `/readyz` is hit from load balancers on every health interval.