web

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package web wires the application's HTTP surface. Routes split into three layers: every request goes through recover → log → authMiddleware (session + viewer + CSRF + must_change_password gate). Authenticated-only routes additionally pass through requireSession; the login page passes through requireAnonymous. There is no public signup route — the first user is created by the operator via scripts/operator/create-user.sql (kamal create-user) and subsequent users join via invite links rendered on /invites/:token.

/oauth/* implements a PKCE-only OAuth 2.1 server for /mcp + /api/v1/*. /api/v1/whoami exists as the bearer middleware's production smoke surface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CORS

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

CORS handles the SDK snippet's cross-origin POST to /api/v1/ingest/events. An empty allowedOrigins slice is "permissive" — the response carries Access-Control-Allow-Origin: * so any first-party page can write events. A non-empty slice is the operator-restricted form: the request Origin must match exactly or the CORS headers are omitted (the browser then blocks the response from JS, which is the only enforcement layer that matters).

Allow-Methods / Allow-Headers are static — we don't accept cookies or custom verbs on /api/v1/ingest/events.

Preflight (OPTIONS + Access-Control-Request-Method) short-circuits with 204; the wrapped handler never runs.

func DecodeJSON

func DecodeJSON[T any](w http.ResponseWriter, r *http.Request) (T, bool)

DecodeJSON reads the request body as JSON into T with the uniform error mapping the v1 endpoints expect: a body that overran MaxBody → 413, a JSON syntax/type error → 400. On either failure the response is fully written and ok=false is returned so the handler should bail. On success the decoded value is returned with ok=true.

Pair with MaxBody upstream so r.Body is already wrapped in http.MaxBytesReader; this helper just catches the *http.MaxBytesError the reader surfaces and converts it to the right status.

func Decompress added in v0.1.1

func Decompress(maxDecompressed int64) func(http.Handler) http.Handler

Decompress swaps a gzip request body for its decoded stream when the client sends Content-Encoding: gzip. maxDecompressed caps the decoded size, reusing MaxBytesReader so a zip bomb maps to 413 via the same read-error path as an oversize plain body. Mount INSIDE MaxBody (so the compressed wire bytes are capped first) and BEFORE requirePublicToken (which reads the decoded body to find the token).

func Handler

func Handler(opts Options) http.Handler

Handler builds the application's root http.Handler.

func MaxBody

func MaxBody(n int64) func(http.Handler) http.Handler

MaxBody wraps the request body in http.MaxBytesReader so reads past n bytes fail with *http.MaxBytesError. DecodeJSON below maps that error to 413; raw-body handlers can do the same with errors.As.

Mount per-route — different surfaces have different ceilings (ingest 10 MiB; future /v1/query much smaller).

func RequireBearer

func RequireBearer(svc *oauth.Service, logger *slog.Logger) func(http.Handler) http.Handler

RequireBearer enforces an OAuth bearer token on the wrapped handler. Missing, malformed, expired, or revoked tokens get a uniform 401 with a WWW-Authenticate header — we don't distinguish failure modes per RFC 6750 §3.

On success the handler downstream can pull the (user, project, scope, client) identity bag from ctx via oauth.FromContext.

Types

type Options

type Options struct {
	AuthService   *auth.Service
	OAuthService  *oauth.Service
	OAuthIssuer   string
	Logger        *slog.Logger
	SecureCookies bool

	// Version is the build-time version stamp (git describe), injected into
	// cmd/server via -ldflags and surfaced in the /healthz body so operators
	// can confirm what's deployed from `kamal logs` / a health probe. Empty in
	// most tests; "dev" for un-stamped local builds.
	Version string

	// IngestService + the three following knobs wire POST /api/v1/ingest/events.
	// Nil IngestService is supported so degenerate test scenarios that build a
	// handler without a CH pool still work — the route is just absent.
	IngestService        *ingest.Service
	AllowedOrigins       []string
	IngestMaxBodyBytes   int64
	DLQDepth503Threshold int

	QueryExecutor     *query.Executor
	QuerySchema       *query.SchemaProvider
	QueryMaxBodyBytes int64

	// MCPHandler is the Streamable HTTP transport for the /mcp endpoint
	// (internal/mcp.NewHTTPHandler). It mounts behind the same RequireBearer
	// + CORS middleware as /api/v1/*. Nil leaves /mcp unmounted — supported
	// for degenerate test scenarios built without a CH pool.
	MCPHandler http.Handler

	// RateLimiter is the extension seam consulted on the ingest and query/MCP
	// paths after the tenant is resolved (see docs/extending.md). Nil defaults to
	// the no-op extension.AllowAll, so the open-source build's behavior is
	// unchanged; a wrapper or self-hoster injects a real limiter here.
	RateLimiter extension.RateLimiter

	// Entitlement is the extension seam consulted on the analysis surfaces
	// (REST query + schema, web playground; MCP is gated in internal/mcp) after
	// the project resolves. Nil defaults to the no-op extension.Unlimited, so
	// the open-source build never gates analysis. A hosted wrapper injects a
	// real implementation to lock a project's analysis once it is over quota.
	Entitlement extension.Entitlement

	// UpgradeURL is where the web query playground redirects when Entitlement
	// denies an over-quota project — the wrapper's branded billing/upgrade page.
	// Empty falls back to a plain 402 (the API/MCP surfaces always answer 402).
	UpgradeURL string
}

Options bundles the dependencies needed to build the HTTP handler. SecureCookies enables the Secure flag on session/csrf cookies — disabled in dev (plaintext http on localhost) and enabled in production via env.

OAuthService and OAuthIssuer are required to wire the OAuth routes; they may be nil/empty in degenerate test scenarios that build a handler without a database. The auth-only path keeps working in that case.

Jump to

Keyboard shortcuts

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