middleware

package
v0.1.132 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package middleware holds the request pipeline every Līdza app runs: panic recovery, request ids, request logging, per-request deadlines, body limits, CORS and secure headers. Each middleware is a plain func(http.Handler) http.Handler with no state beyond its options.

Index

Constants

View Source
const (
	// DefaultPermissionsPolicy turns off the camera, the microphone and
	// location for the page and every frame in it.
	DefaultPermissionsPolicy = "camera=(), microphone=(), geolocation=()"
	// AllowGeolocation lets the app's own pages ask for the visitor's
	// location (navigator.geolocation); frames from other origins still
	// cannot. Camera and microphone stay off.
	AllowGeolocation = "camera=(), microphone=(), geolocation=(self)"
)

Permissions-Policy values for SecureHeadersOptions.PermissionsPolicy and lidza.App.PermissionsPolicy.

View Source
const (
	// DefaultCSP is the policy an app sends outside dev mode when it sets
	// none: scripts, styles, fonts and connections from the app's own
	// origin only (WebSockets to it included), images also from data: and
	// blob: URLs, no plugins, no <base> elsewhere, forms posting only to
	// the app, and no framing. The built templates hold under it: their
	// scripts and styles are files, and the JSON <script> blocks a page
	// carries (JSON-LD, the i18n catalog) are data the policy does not
	// govern. An app that loads from another origin extends it with
	// AddCSP.
	DefaultCSP = "" /* 200-byte string literal not displayed */
	// NoCSP sends no Content-Security-Policy at all.
	NoCSP = "off"
)

Content-Security-Policy values for SecureHeadersOptions.CSP and lidza.App.CSP.

View Source
const (
	// DefaultHSTS is what an app whose APP_URL is https:// sends outside
	// dev mode: browsers use HTTPS for its host for a year. Subdomains
	// are left out: they may be other services, so including them is the
	// domain owner's choice (HSTSSubdomains).
	DefaultHSTS = "max-age=31536000"
	// HSTSSubdomains extends the year to every subdomain of the host.
	HSTSSubdomains = "max-age=31536000; includeSubDomains"
	// NoHSTS sends no Strict-Transport-Security header.
	NoHSTS = "off"
)

Strict-Transport-Security values for lidza.App.HSTS.

View Source
const EnvTrustedProxies = "LIDZA_TRUSTED_PROXIES"

EnvTrustedProxies names the proxies whose forwarded headers count: IPs and CIDRs, comma-separated, plus "loopback" (127.0.0.0/8, ::1) and "private" (10/8, 172.16/12, 192.168/16, fc00::/7). Empty trusts none.

Variables

This section is empty.

Functions

func AddCSP

func AddCSP(policy, directive string, sources ...string) string

AddCSP returns policy with sources added to one directive. A fetch directive (script-src, media-src, ...) that policy lacks is created with default-src's sources first, so adding to it keeps what it fell back to. Browsers keep only the first of a repeated directive, so a policy is extended here rather than by appending "; img-src ...":

CSP: middleware.AddCSP(middleware.DefaultCSP, "script-src", "https://js.example.com")

func Chain

func Chain(h http.Handler, mw ...Middleware) http.Handler

Chain applies middlewares so that the first one listed is the outermost.

func ClientIP

func ClientIP(r *http.Request) string

ClientIP is the request's client address: the one ClientIdentity resolved (through the trusted proxies), else the connection's. It is the default rate-limit key; never read X-Forwarded-For by hand.

func GetRequestID

func GetRequestID(ctx context.Context) string

GetRequestID returns the request id set by RequestID, or "".

Types

type CORSOptions

type CORSOptions struct {
	// Origins are the allowed origins; "*" allows any (not with credentials).
	Origins []string
	Methods []string // default GET, POST, PUT, PATCH, DELETE
	Headers []string // default Content-Type, Authorization, X-Request-ID
	// Credentials allows cookies and Authorization on cross-origin calls.
	Credentials bool
	// MaxAge is how long browsers cache the preflight, default 10 minutes.
	MaxAge time.Duration
}

CORSOptions configures CORS. Same-origin apps (the default: the binary serves both the frontend and /api) need none.

type Middleware

type Middleware func(http.Handler) http.Handler

Middleware wraps a handler.

func CORS

func CORS(o CORSOptions) Middleware

CORS answers preflights and sets the allow headers for the listed origins. Requests from other origins pass through without CORS headers, which the browser then blocks.

func ClientIdentity

func ClientIdentity(p *Proxies) Middleware

ClientIdentity resolves each request's client address once (Resolve) and puts it in the context, where ClientIP, the rate limiter, the auth throttles and the request log read it. lidza.App installs it with LIDZA_TRUSTED_PROXIES; with none trusted it is the connection's address.

func Logger

func Logger(log *slog.Logger) Middleware

Logger writes one structured line per request: method, path, status, bytes, duration, request id and client_ip (ClientIP: the resolved client, never a raw forwarded header). The path is the URL path only, cut at maxLogPath bytes; the query, headers, cookies and body are never logged.

func MaxBody

func MaxBody(n int64) Middleware

MaxBody caps the request body; reading past n fails with a *http.MaxBytesError, which the typed router reports as 400.

func RateLimit

func RateLimit(o RateLimitOptions) Middleware

RateLimit is a token bucket per key. Over the limit replies 429 with Retry-After. The bucket table is bounded, so a flood of new clients cannot grow memory without limit.

func Recover

func Recover(log *slog.Logger) Middleware

Recover turns a panic into a 500 JSON error and logs the stack, so one bad request never takes the process down.

func RequestID

func RequestID() Middleware

RequestID reads X-Request-ID from the request or generates one, stores it in the context and echoes it in the response.

func SecureHeaders

func SecureHeaders(o SecureHeadersOptions) Middleware

SecureHeaders sets the response headers every app should send.

func StreamTimeout

func StreamTimeout(d, stream time.Duration) Middleware

StreamTimeout is Timeout with a second deadline, stream, for requests that accept server-sent events (Accept: text/event-stream, as the generated clients send for a router.Stream operation) or upgrade to a WebSocket.

func Timeout

func Timeout(d time.Duration) Middleware

Timeout gives every request a deadline through its context. Handlers and the calls they make (database, HTTP, Rust pool) stop at the deadline; the response is not buffered, so streaming still works.

type Proxies

type Proxies struct {
	// contains filtered or unexported fields
}

Proxies is the set of trusted proxies ClientIdentity resolves through.

func ParseProxies

func ParseProxies(s string) (*Proxies, error)

ParseProxies reads LIDZA_TRUSTED_PROXIES' format. An entry that is neither an address, a CIDR nor a shorthand is an error, so a typo is not silently ignored.

func (*Proxies) Empty

func (p *Proxies) Empty() bool

Empty reports whether no proxy is trusted.

func (*Proxies) Resolve

func (p *Proxies) Resolve(r *http.Request) string

Resolve returns the client's address. A peer that is not a trusted proxy is the client, whatever its headers say. From a trusted peer the forwarded addresses are read nearest first: trusted proxies are skipped, and the first address that is not one is the client. A malformed entry stops the walk at the trusted hop before it; when every hop is trusted, the farthest is the client.

type RateLimitOptions

type RateLimitOptions struct {
	// RPS is the sustained rate per key; Burst the bucket size.
	RPS   float64
	Burst int
	// Key groups requests; default is ClientIP, the client's address
	// through the trusted proxies (ClientIdentity, LIDZA_TRUSTED_PROXIES).
	Key func(r *http.Request) string
	// MaxKeys bounds the table of buckets; when reached, buckets idle for
	// the longest are evicted. Default 100000.
	MaxKeys int
}

RateLimitOptions configures RateLimit.

type SecureHeadersOptions

type SecureHeadersOptions struct {
	// CSP is the Content-Security-Policy value; empty or NoCSP sends
	// none. Vite's dev server injects inline scripts, so a strict policy
	// is for production builds; lidza.App sends DefaultCSP there unless
	// it sets its own.
	CSP string
	// HSTS enables Strict-Transport-Security: HSTSPolicy, or a year with
	// the subdomains when that is empty. Only for an app served over
	// HTTPS (browsers ignore it over HTTP).
	HSTS bool
	// HSTSPolicy is the Strict-Transport-Security value when HSTS is set.
	HSTSPolicy string
	// PermissionsPolicy is the Permissions-Policy value; empty sends
	// DefaultPermissionsPolicy, which turns off the camera, the
	// microphone and location. An app that asks for one of them names it
	// for its own origin: AllowGeolocation for location.
	PermissionsPolicy string
}

SecureHeadersOptions configures SecureHeaders.

Jump to

Keyboard shortcuts

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