Documentation
¶
Index ¶
- Constants
- Variables
- func AdminMiddleware(jwtManager *jwt.Manager, adminEmail string, ...) gin.HandlerFunc
- func AuthMiddleware(jwtManager *jwt.Manager, publicPaths []string) gin.HandlerFunc
- func AuthMiddlewareWithState(jwtManager *jwt.Manager, publicPaths []string, state SessionState) gin.HandlerFunc
- func DeviceContext() gin.HandlerFunc
- func IdempotencyMiddleware(store IdempotencyStore) gin.HandlerFunc
- func LoggerMiddleware(logger *slog.Logger) gin.HandlerFunc
- func MaxBodyBytesMiddleware(defaultLimit, multipartLimit int64, overrides map[string]int64) gin.HandlerFunc
- func MetricsMiddleware() gin.HandlerFunc
- func NewRateLimitMiddleware(name string, rate int64, period time.Duration) gin.HandlerFunc
- func NewUserRateLimitMiddleware(name string, rate int64, period time.Duration) gin.HandlerFunc
- func RateLimitByPath(rl gin.HandlerFunc, paths ...string) gin.HandlerFunc
- func RateLimitByPathPrefix(rl gin.HandlerFunc, prefixes ...string) gin.HandlerFunc
- func RateLimitByPathPrefixExcept(rl gin.HandlerFunc, exempt []string, prefixes ...string) gin.HandlerFunc
- func RateLimitByPathSegment(rl gin.HandlerFunc, segments ...string) gin.HandlerFunc
- func RateLimitByPathSegmentForMethods(rl gin.HandlerFunc, methods []string, segments ...string) gin.HandlerFunc
- func RateLimitByPathWithQueryParam(rl gin.HandlerFunc, path, queryParam string) gin.HandlerFunc
- func RecoveryWithMetrics() gin.HandlerFunc
- func RegisterAppMetrics(version string, startedAt time.Time)
- func RequestTimeoutMiddleware(timeout time.Duration) gin.HandlerFunc
- func SecurityHeadersMiddleware() gin.HandlerFunc
- type DBStatsCollector
- type IdempotencyStore
- type SessionState
Constants ¶
const ( // RequestIDKey is the context key for request ID RequestIDKey = "request_id" // RequestIDHeader is the header key for request ID RequestIDHeader = "X-Request-ID" // UserIDKey is the context key AuthMiddleware uses to store the // authenticated user's ID. UserIDKey = "userID" )
const HeaderIdempotencyKey = "Idempotency-Key"
HeaderIdempotencyKey is the request header a client sets to make a mutating request safe to retry (spelled as in the IETF "Idempotency-Key Header Field" draft).
const HeaderIdempotencyReplayed = "Idempotency-Replayed"
HeaderIdempotencyReplayed is set on a response served from a stored record rather than by executing the request.
Variables ¶
var ( // AppInfo is a constant gauge (value 1) with version and go_version labels. AppInfo = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "app_info", Help: "Application build information.", }, []string{"version", "go_version"}, ) // AppUptimeSeconds reports seconds since server start. AppUptimeSeconds = prometheus.NewGaugeFunc( prometheus.GaugeOpts{ Name: "app_uptime_seconds", Help: "Seconds since server start.", }, func() float64 { return 0 }, ) // HealthCheckStatus is 1 when the service is healthy, 0 when unhealthy. HealthCheckStatus = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "health_check_status", Help: "Health check status: 1 = healthy, 0 = unhealthy.", }, ) )
var ( // HTTPRequestsTotal counts total HTTP requests by method, path, and status. HTTPRequestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests.", }, []string{"method", "path", "status"}, ) // HTTPRequestDurationSeconds tracks HTTP request latency. HTTPRequestDurationSeconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request latency in seconds.", Buckets: prometheus.DefBuckets, }, []string{"method", "path"}, ) // HTTPResponseSizeBytes tracks HTTP response sizes. HTTPResponseSizeBytes = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_response_size_bytes", Help: "HTTP response size in bytes.", Buckets: prometheus.ExponentialBuckets(100, 10, 7), }, []string{"method", "path"}, ) // HTTPRequestsInFlight tracks the number of in-flight HTTP requests. HTTPRequestsInFlight = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "http_requests_in_flight", Help: "Number of HTTP requests currently being processed.", }, ) // APIPanicsRecoveredTotal counts panics recovered by the recovery middleware. APIPanicsRecoveredTotal = prometheus.NewCounter( prometheus.CounterOpts{ Name: "api_panics_recovered_total", Help: "Total number of panics recovered.", }, ) )
var ( // RateLimitHitsTotal counts requests rejected by a rate limiter. The // "limiter" label names the bucket that did the rejecting (see the names // passed in cmd/api/main.go). RateLimitHitsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "rate_limit_hits_total", Help: "Total number of requests rejected by a rate limiter, by limiter and route.", }, []string{"limiter", "path"}, ) // RateLimitRequestsTotal counts every request evaluated by a rate limiter, // allowed or rejected. RateLimitRequestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "rate_limit_requests_total", Help: "Total number of requests evaluated by a rate limiter (allowed and rejected), by limiter and route.", }, []string{"limiter", "path"}, ) )
var AccessTokensRejectedTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "auth_access_tokens_rejected_total", Help: "Total number of requests rejected because the access token's session is no longer usable.", }, []string{"reason"}, )
AccessTokensRejectedTotal counts authenticated requests refused by the session-state check, by reason: session_revoked, account_disabled or lookup_failed.
var IdempotencyRequestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "idempotency_requests_total", Help: "Requests carrying an Idempotency-Key header, by outcome.", }, []string{"outcome"}, )
IdempotencyRequestsTotal counts requests that carried an Idempotency-Key, by outcome: executed (claimed and run), replayed, in_progress, mismatch, not_replayable, invalid_key, unavailable.
Functions ¶
func AdminMiddleware ¶
func AdminMiddleware(jwtManager *jwt.Manager, adminEmail string, getUserEmail func(userID uuid.UUID) (string, error)) gin.HandlerFunc
AdminMiddleware creates a middleware that restricts access to admin users only. It requires the auth middleware to have already set "userID" in the context. The admin user is determined by matching the user's email against the ADMIN_EMAIL env var.
func AuthMiddleware ¶
func AuthMiddleware(jwtManager *jwt.Manager, publicPaths []string) gin.HandlerFunc
AuthMiddleware enforces JWT authentication without checking session state. See AuthMiddlewareWithState.
func AuthMiddlewareWithState ¶ added in v1.3.7
func AuthMiddlewareWithState(jwtManager *jwt.Manager, publicPaths []string, state SessionState) gin.HandlerFunc
AuthMiddlewareWithState enforces JWT authentication on all routes except explicitly allowed public paths. It extracts the user ID from the token and sets it in the Gin context as "userID".
When state is non-nil it additionally rejects tokens whose session has been revoked and tokens belonging to a disabled or deleted account, with a 401, and answers 503 when the state cannot be read. Tokens carrying no session ID are checked for the disabled flag only.
func DeviceContext ¶ added in v1.3.6
func DeviceContext() gin.HandlerFunc
DeviceContext attaches the calling client's User-Agent and address to the request context, where the auth service reads them when creating or renewing a session.
func IdempotencyMiddleware ¶ added in v1.3.2
func IdempotencyMiddleware(store IdempotencyStore) gin.HandlerFunc
IdempotencyMiddleware makes mutating requests safe to retry when the client opts in with an `Idempotency-Key` header; a request without the header passes through untouched.
Shape of the guarantee:
- Scoped per authenticated user; unauthenticated endpoints are not covered.
- The first request with a key executes; concurrent duplicates get 409; later duplicates get the stored response verbatim, plus `Idempotency-Replayed: true`.
- Reusing one key for a different request body gets 422.
- Server errors (5xx) and panics release the key.
It must be registered after AuthMiddleware (it reads "userID" from the context) and after the rate limiters.
func LoggerMiddleware ¶
func LoggerMiddleware(logger *slog.Logger) gin.HandlerFunc
LoggerMiddleware creates a middleware that emits a structured access-log line for every request: who (user ID, client IP), what (method, path), when (timestamp via the logger), and the outcome (status, latency). It also assigns each request a unique ID, exposed both in the Gin context and the X-Request-ID response header for correlation.
The line is written through the provided *slog.Logger; pass nil to use slog.Default(). The log level scales with the response status: 5xx logs at Error, 4xx at Warn, everything else at Info.
func MaxBodyBytesMiddleware ¶ added in v1.3.0
func MaxBodyBytesMiddleware(defaultLimit, multipartLimit int64, overrides map[string]int64) gin.HandlerFunc
MaxBodyBytesMiddleware caps request body size: defaultLimit for non-multipart requests, multipartLimit for multipart ones. overrides maps a path suffix (matched the same way as RateLimitByPath) to a larger limit; the first matching suffix wins.
func MetricsMiddleware ¶
func MetricsMiddleware() gin.HandlerFunc
MetricsMiddleware returns a Gin middleware that records Prometheus metrics for every HTTP request: counter, duration histogram, response size histogram, and in-flight gauge.
func NewRateLimitMiddleware ¶
NewRateLimitMiddleware creates a Gin middleware that rate-limits requests, keyed by c.ClientIP(). rate is the number of requests allowed per period (e.g., 10 requests per 1 minute).
func NewUserRateLimitMiddleware ¶ added in v1.3.0
NewUserRateLimitMiddleware is like NewRateLimitMiddleware, but keys by the authenticated user's ID (set by AuthMiddleware as "userID") when present, falling back to client IP.
func RateLimitByPath ¶
func RateLimitByPath(rl gin.HandlerFunc, paths ...string) gin.HandlerFunc
RateLimitByPath applies a rate-limit middleware only to requests whose path (relative to the router group) matches one of the given suffixes.
func RateLimitByPathPrefix ¶ added in v1.2.9
func RateLimitByPathPrefix(rl gin.HandlerFunc, prefixes ...string) gin.HandlerFunc
RateLimitByPathPrefix applies a rate-limit middleware only to requests whose path (relative to the router group) starts with one of the given prefixes.
func RateLimitByPathPrefixExcept ¶ added in v1.3.5
func RateLimitByPathPrefixExcept(rl gin.HandlerFunc, exempt []string, prefixes ...string) gin.HandlerFunc
RateLimitByPathPrefixExcept is RateLimitByPathPrefix with an exact-path escape hatch, for the cheap read that happens to live under an expensive prefix.
"/imports" is budgeted for one-shot heavy work — parsing and inserting a logbook. "/imports/templates" only serves a static catalogue, and the import screen reads it on entry: sharing the expensive bucket would let opening that screen a dozen times exhaust the budget for the import the pilot came to do.
func RateLimitByPathSegment ¶ added in v1.3.2
func RateLimitByPathSegment(rl gin.HandlerFunc, segments ...string) gin.HandlerFunc
RateLimitByPathSegment applies a rate-limit middleware to every request whose path contains one of the given segments, wherever it appears.
func RateLimitByPathSegmentForMethods ¶ added in v1.3.2
func RateLimitByPathSegmentForMethods(rl gin.HandlerFunc, methods []string, segments ...string) gin.HandlerFunc
RateLimitByPathSegmentForMethods is RateLimitByPathSegment narrowed to a set of HTTP methods.
func RateLimitByPathWithQueryParam ¶ added in v1.3.0
func RateLimitByPathWithQueryParam(rl gin.HandlerFunc, path, queryParam string) gin.HandlerFunc
RateLimitByPathWithQueryParam applies a rate-limit middleware only to requests whose path (relative to the router group) ends with the given suffix AND which carry a non-empty queryParam.
func RecoveryWithMetrics ¶
func RecoveryWithMetrics() gin.HandlerFunc
RecoveryWithMetrics returns a Gin middleware that recovers from panics, increments the api_panics_recovered_total counter, and returns a 500 response.
func RegisterAppMetrics ¶
RegisterAppMetrics registers app_info and app_uptime_seconds gauges. version is the application version string (e.g. "1.0.0" or a git SHA).
func RequestTimeoutMiddleware ¶ added in v1.3.0
func RequestTimeoutMiddleware(timeout time.Duration) gin.HandlerFunc
RequestTimeoutMiddleware bounds every request's context to the given duration.
func SecurityHeadersMiddleware ¶
func SecurityHeadersMiddleware() gin.HandlerFunc
SecurityHeadersMiddleware adds standard security headers to all API responses.
Types ¶
type DBStatsCollector ¶
type DBStatsCollector struct {
// contains filtered or unexported fields
}
DBStatsCollector implements prometheus.Collector and exposes sql.DBStats as Prometheus gauges on every scrape.
func NewDBStatsCollector ¶
func NewDBStatsCollector(db *sql.DB) *DBStatsCollector
NewDBStatsCollector creates a collector that reads stats from db on every scrape.
func (*DBStatsCollector) Collect ¶
func (c *DBStatsCollector) Collect(ch chan<- prometheus.Metric)
Collect reads the current sql.DBStats and sends each metric value.
func (*DBStatsCollector) Describe ¶
func (c *DBStatsCollector) Describe(ch chan<- *prometheus.Desc)
Describe sends the descriptors of each metric to the channel.
type IdempotencyStore ¶ added in v1.3.2
type IdempotencyStore interface {
Begin(ctx context.Context, userID uuid.UUID, key string, requestHash []byte) (service.IdempotencyClaim, error)
Finish(ctx context.Context, userID uuid.UUID, key string, claimedAt time.Time, resp service.IdempotentResponse) error
Abandon(ctx context.Context, userID uuid.UUID, key string, claimedAt time.Time) error
MaxResponseBytes() int
}
IdempotencyStore is the slice of the idempotency service this middleware needs.
type SessionState ¶ added in v1.3.7
type SessionState func(ctx context.Context, userID, sessionID uuid.UUID) (disabled bool, live bool, err error)
SessionState reports whether the account behind an access token is disabled and whether the token's session still holds a live refresh token. A deleted account reports live=false; a non-nil error means the state could not be read. Nil disables the check.