middleware

package
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
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

View Source
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.

View Source
var ErrAllowedOriginsEmpty = errors.New("cors: no allowed origins configured")

ErrAllowedOriginsEmpty is returned by ParseAllowedOrigins when the resolved list contains no origins.

View Source
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

func CORSMiddleware(allowedOrigins []string) func(http.Handler) http.Handler

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

func ClientIPFromContext(ctx context.Context) string

ClientIPFromContext returns the resolved client IP stored by ClientIPMiddleware. Returns "" if the middleware did not run.

func ClientIPMiddleware added in v0.6.0

func ClientIPMiddleware(trusted []*net.IPNet) func(http.Handler) http.Handler

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

func JWKSMiddleware(keyRing *jwtpkg.KeyRing) func(http.Handler) http.Handler

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

func LoggingMiddleware(logger *zap.Logger) func(http.Handler) http.Handler

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

func ParseAllowedOrigins(raw string, allowCredentials bool) ([]string, error)

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

func ParseTrustedProxies(raw string) ([]*net.IPNet, error)

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

func RateLimitMiddleware(limits []PathLimit, logger *zap.Logger) func(http.Handler) http.Handler

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

func RecoverMiddleware(logger *zap.Logger) func(http.Handler) http.Handler

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.

func (*FixedWindowLimiter) Allow added in v0.6.0

func (l *FixedWindowLimiter) Allow(key string, now time.Time) bool

Allow returns true if the key has remaining quota in the current window.

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

type RateLimiter interface {
	Allow(key string, now time.Time) bool
}

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

type ReadinessProbe interface {
	Ready(ctx context.Context) error
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL