Help us shape the future of Go! Take the 2026 Go Developer Survey

identityhttp

package module
v0.0.0-...-789096f Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 42 Imported by: 9

README

Identity HTTP transport

API-key create/rotate/delete/update may explicitly select CSRFNone only for strict bearer handlers that ignore ambient cookies. This selected profile requires the original Idempotency-Key for each selected operation, matching the native management API rather than the draft catalog's rotate/repeat labels. The same KeyedSecretRateLimiter admits these actions with shared IP/subject budgets and stable original debit custody. Cookie profiles and all other operation contracts remain unchanged. This metadata selection grants no authority and does not enable routes; the optional feature must enforce the native workflow.

Organization creation/metadata updates, member admission/role updates/removal and invitation sending/acceptance/resend/cancellation/rejection may select CSRFNone only for strict-bearer feature handlers that ignore ambient cookies. This is not a cookie fallback or a general organization exemption: the canonical cookie profile and all other operation policies remain unchanged. NewCanonicalOperation validates metadata, not handler authentication. The selected organization integration must enforce trusted ingress, one strict bearer header, current native authority and original idempotency/recovery. No handler or route is enabled by this base constructor alone.

Explicit identity.mfa.disable selection accepts only original stored-session CSRF (X-CSRF-Token, exact Origin, no Referer fallback). It grants no general mutation exemption, bearer fallback or renewable freshness. The optional http/mfa module owns the actual disable handler and selected closed wire shape.

Development checkpoint, not a complete identity HTTP product. Go 1.27. This independently versioned module currently owns server lifecycle, separate health/readiness probe handlers, typed opt-in operation selection and bounded ordinary request parsing, credential binding and trusted-network/tenant admission. Dependencies are identity kernel, authentication and stable identifier contracts, not feature implementations or the reference server. Selected secure routing, CORS, real owner phase hooks and OpenAPI composition are available. Complete domain/protocol handlers and their authentication, authorization, cookie/CSRF and rate profiles remain required feature work. This checkpoint is not a release-qualified complete identity endpoint platform.

Selected secure router

Passkey registration may explicitly select CSRFVariantOriginSessionState: the owning feature must dispatch a closed signup variant using original pre-auth cookie/flow and exact Origin, versus enrollment using the original session synchronizer and Origin. Failed enrollment must never fall back to signup. The base selects neither handler. AdmitPasskeySignupChallenge accepts native-authenticated original anonymous-flow/create-ceremony metadata only, after the same attempt's AdmitIP; AdmitPasskeySignup performs all three dimensions for a direct custody-authenticated caller. Neither proves authority. Use the same namespace/key/backend as other verification profiles: IP and anonymous-flow budgets remain shared; ceremony keys include the operation.

Use NewOperationDescriptors(FeatureServices{Features: selected}), then NewRouter(TransportPolicy{Network: networkConfig, CORS: routePolicies, HandlerTimeout: timeout, Document: DocumentConfig{Title: title, Version: version}}, operations, middleware). The returned handler and OpenAPI document share one validated immutable operation snapshot. Empty selection exposes nothing; metadata and probes are never implicit. The network policy includes all limits, exact tenant/public host bindings, proxy prefixes and forwarding-hop bounds. HTTPS is required; unknown hosts return 421. There is no route-prefix rewrite: canonical, protocol and probe paths remain exact and root-mounted.

The router composes envelope/network admission, raw-body bounds, route-specific CORS, exact dispatch and real feature-owned stages. It does not authenticate, debit a rate bucket, serialize cookies or consume CSRF proofs a second time. Use the same network configuration and shared domain/rate owners in selected feature factories. Those factories must enforce their advertised controls; metadata cannot certify an arbitrary supplied handler. CORS policy for an unselected method/path is rejected. Preflight does no owner or hook work.

Nonempty middleware requires each selected handler to implement OperationMiddlewareBinder; opaque handlers are rejected rather than receiving invented authentication phases. All policy, descriptor and wire validation precedes wrapper factories. Individual frozen-owner binding conflicts can still fail construction; no partial router/document is returned. Owned probes support global hooks at explicit public/no-auth boundaries without a principal or ambient credential headers. Probe hook writers discard output: short circuits produce fixed unavailable responses, never invented health/readiness. The real readiness check runs at most once and original standalone handlers stay usable. Bound hooks share max(ProbeConfig.Timeout, 1ms) as a whole-call budget while readiness retains its original 1ns..2s bound; cancellation and drain are checked again after hook unwind. No provider result or response is cached.

Responses retain Cache-Control: no-store, X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer, including streaming and hook short circuits. An unknown-length body overflow denies before a later response can claim success; feature decoders must still validate body results before authentication/mutation. The 1ms..30s whole-handler context never extends an inbound deadline. Use NewHTTPServer for raw IO/write deadlines and connection/body ownership; cooperative cancellation cannot terminate arbitrary caller code or prove rollback. Precommit panics produce redacted 503; postcommit failures abort the response. This selected-router checkpoint does not complete all feature handlers, protected metadata, protocol/provider/browser journeys or release qualification.

Explicit public metadata

NewDocumentedHandler never exposes a metadata endpoint. To explicitly expose public metadata, use NewDocumentedMetadataHandler with MetadataConfig: Public: true, an exact NetworkConfig, the shared SafeRateLimiter and a positive 1ms..30s request budget. It serves only HTTPS GET /openapi.json on mapped hosts, charges the canonical safe IP budget once and returns exact validated document bytes. The endpoint describes itself in that same snapshot. It rejects bodies, queries, retry keys and Authorization; cookies confer no authority. No implicit HEAD, CORS or cache validation is enabled. Protected/admin metadata remains full-router work: leave the endpoint off for that profile. Construction never invokes handlers or readiness callbacks. Caller-owned wire examples/descriptions must already be public; this is not a secret detector.

OpenAPIDocument.OpenapiDocument provides the typed side-effect-free direct snapshot export, with its exact digest. Coverage() and CoverageJSON() export a separate deterministic diagnostic inventory: all canonical obligations plus selected extensions, honestly classified as selected, absent, direct-only, middleware or unavailable shared-route variants. These do not certify runtime implementation or full parity. The report contains disabled baseline names and is not automatically embedded in OpenAPI or published over HTTP. Returned storage is detached; snapshot exports and dispatch may run concurrently.

Keyed secret-route admission

NewKeyedSecretRateLimiter(KeyedSecretRateConfig{...}) selects canonical keyed rate.secret operations only. Supply a borrowed native atomic rate-limit/v2.StrictAtomicBatchBackend, immutable deployment namespace/key, trusted clock and 1ms..2s bound. PostgreSQL requires atomic schema migration 2. No per-item or emergency fallback is available. Pass trusted network facts, the actual authenticated/capability subject and exactly the protected workflow's original identity.IdempotencyKey in KeyedSecretRateRequest. If present, challenge ID and immutable original lifetime must come from the challenge owner. Never use client-claimed subjects, replacement retry keys or remaining TTL.

Every call debits IP10/10m, tenant/subject10/10m and optional challenge5/lifetime as one command. Same key/tenant/subject/operation preserves original correlation; changed IP/challenge conflicts with retained native intent rather than resetting the command. Matching replay returns the original admission, not a new debit. Proceed only on nil error and Allowed; domain authentication, authorization, cumulative risk and transaction-safe proof consumption remain separately required. Cancellation can deny continuation after a debit and does not prove refund. Known denial uses the longest exhausted delay, ceiling seconds clamped1..3600. Close after draining handlers; borrowed storage is never closed. Non-keyed secret flows, real password endpoint composition and the remaining rate profiles are still pending. Construction selects no routes or advertised capabilities.

Password-verification admission

NewPasswordVerificationRateLimiter(PasswordVerificationRateConfig{...}) owns only the non-keyed identity.password.verify profile, not other verification operations. Supply the borrowed native atomic rate backend (PostgreSQL atomic schema 2), immutable deployment namespace/key, prompt trusted clock and 1ms..2s bound. Admit accepts trusted tenant network facts and the actual current primary subject in PasswordVerificationRateRequest. Never pass a request-claimed user. No HTTP retry key or challenge exists for current-password verification.

Each attempt debits trusted IP20/10m and tenant/subject10/10m atomically. A fresh server-native command counts repeated attempts; it does not reuse a workflow command or permit caller-selected correlation. Unknown/outage/cancellation denies without retry or local fallback. Continue only on nil error and Allowed; the workflow still owns primary-session checks, cumulative risk, signing and proof issuance. Cancellation may follow a debit and does not prove refund. Known rejection returns the longest delay rounded to seconds, clamped1..3600. Close after draining; the borrowed backend is not closed. Construction exposes no endpoint or metadata.

Issued-challenge verification admission

NewChallengeVerificationRateLimiter(ChallengeVerificationRateConfig{...}) uses the same atomic backend, namespace and key as password verification, so IP and tenant/subject budgets are shared rather than reset by switching flows. It selects canonical issued-challenge rate.verify operations only; it does not install routes or authenticate bearer proofs.

For an HTTP attempt, call AdmitIP before proof processing. After the owning workflow authenticates the original issuance, call AdmitChallenge with its actual subject, original challenge ID and immutable policy lifetime (integral microseconds, 1s..24h). Never derive these from JSON claims, a replacement challenge or remaining expiry. The first phase debits IP20/10m; the second atomically debits subject10/10m and original challenge10/lifetime. An IP debit is not refunded when authentication or the second phase fails. Already-authenticated direct callers may instead use Admit for all three dimensions in one batch; do not combine it with the split phases for the same attempt.

Proceed only on nil error and Allowed. Native expiry, current authority and stricter transaction-owned attempt limits still apply. Each phase uses fresh server debit correlation, never HTTP retry keys or workflow command IDs. Ambiguity, outage, cancellation and closure deny without retry or fallback. Close after draining handlers; borrowed storage remains open.

For identity.passkey.signin-verify, use the separate PasskeyVerificationRateRequest with exactly one original Identifier or AnonymousFlowID, then AdmitPasskeyChallenge after the IP phase (or AdmitPasskey for a custody-authenticated direct caller). It has no user or operation selector and leaves ChallengeVerificationRateRequest unchanged. The native passkey owner must first authenticate the exact original browser/ceremony and bind the identifier when present. Discovery cannot know a user before cryptographic verification: its original anonymous flow is a separate rate source, not a user ID or authenticated principal. The selected source receives10/10m under its own domain-separated key, while original IP and challenge buckets remain unchanged. Missing/ambiguous source, an alternate operation or renewed challenge lifetime is not admissible input. Existing user-subject profiles are unchanged. These are admission ports, not delivered passkey handlers or original-custody verification; those must be explicitly composed with their native owner.

Safe-route admission

NewSafeRateLimiter(SafeRateConfig{...}) selects the fixed rate.safe catalog policy, not arbitrary caller-supplied numeric overrides. Supply a borrowed strong rate-limit/v2.StrictBackend, deployment key namespace, trusted clock, bounded degradation recorder, positive 1ms..2s duration and bounded emergency cardinality. Use one limiter per process/deployment namespace across all safe routes. Close it after draining handlers; it closes only its local store, never the borrowed backend. Copies share that store and terminal closure state.

Call Admit(ctx, facts, operationID, authenticatedUserID) with facts from NetworkResolver and the user only from current session-owner admission. Empty user means unauthenticated. Client input cannot select tenant, IP, user or policy. The normalized full IP is limited to 100/minute in shared storage; authenticated tenant/user is additionally limited to 300/minute. Atomic token buckets refill continuously, without wall-clock window resets. Counters span canonical safe operations. An IP debit is not refunded when a later subject decision denies or becomes unknown.

Only known shared unavailability enables the process-local 20/minute IP budget. Unknown dispatch never becomes fallback permission. Every decided fallback requires a bounded degradation event attributed to the operation; recorder failure denies admission. The event's Allowed flag describes the counter decision, not successful domain execution. Saturation fails closed rather than evicting live budgets; on saturation, only counters idle for a full minute may be reclaimed through the primitive. Earlier request deadlines remain effective. Callbacks are synchronous and must honor their context; none run detached.

Proceed only on nil error and Allowed. ErrRateLimited carries a positive RetryAfter; ErrRateUnavailable discloses no backend detail. An admission is not authentication, authorization or a reusable lease. Exact HTTP status/header mapping and session-handler integration remain pending. This checkpoint uses committed primitive security revision 72dfa13dd0a6, not released v2.0.0, whose memory store can evict live counters. Published dependency resolution and full release qualification are not established by local archive-backed tests.

Original-flow, signup, sign-in and delivery policies

NewAuthRateLimiter(AuthRateConfig{...}) selects canonical rate.signup, rate.signin and rate.delivery operations from the same catalog. Supply a strong borrowed backend, stable deployment namespace, independent nonzero 32-byte identifier HMAC key, trusted clock and a 1ms..2s maximum duration. No emergency store or degradation callback is used: outages and unknown outcomes fail closed.

Before starting a new tenant-bound browser flow, call AdmitOriginalFlow(ctx, facts): this applies the IP-only rate.auth bucket of 20/10m, shared across tenant hosts and owner instances in the same namespace. There is no identifier or challenge yet. Do not use this method for an existing flow or any operation with those dimensions; their applicable buckets remain required. HTTPS/origin admission belongs to the handler, and rate admission must precede flow entropy or durable writes. Original-flow, signup, signin and delivery budgets are independent. This method does not register or advertise a route.

Passkey register-options and signin-options instead use AdmitPasskeyOptionsIP, sharing the same rate.auth IP20/10m budget before native proof/flow processing. Identified signin then calls AdmitPasskeyOptionsIdentifier once with its owner-canonical identifier; enrollment and identifier-free signin have no such dimension. These options have no issued ceremony yet: never invent a challenge from a session, proof, flow or retry key. Verification uses the original issued ceremony through the separate verification owner above. Neither phase grants reusable clearance, and the generic Admit API retains its original signin/signup/delivery contract.

Call Admit(ctx, facts, AuthRateRequest{OperationID: operationID, Identifier: canonicalIdentifier, UserID: authenticatedUserID}). Identifiers must come from the owning workflow's canonical input or authenticated original challenge custody. A client-claimed identifier unrelated to the challenge is not suitable. The rate owner neither normalizes identifiers nor authenticates subjects. HMAC key derivation length-frames profile, dimension, tenant and value; raw identifiers and subjects never become backend keys or diagnostics.

Signup uses IP5/hour and tenant/identifier3/day, with no subject dimension; signup requires mapped tenant facts and rejects a supplied UserID. For bounded native normalization after IP admission, call AdmitSignupIP then AdmitSignupIdentifier with the same owning workflow's canonical input. Do not also call Admit for that request: full and split admission share the same bucket identities. A rejected source lookup retains its earlier IP debit. The one-hour retry ceiling never shortens the daily identifier refill period. Neither admission method validates a flow, reads an account or permits signup.

Sign-in uses shared IP20/10m and tenant/identifier10/10m. Delivery uses shared IP10/h, tenant/identifier5/h and authenticated tenant/subject10/h. All operations in one profile share these continuously refilled budgets. Admission requires every applicable bucket to allow. Per-bucket debits are atomic, but not one cross-bucket transaction: earlier debits remain after later failure. Known exhaustion checks remaining buckets for the longest retry delay, rounded up and clamped to 1s..1h. No result is reusable authorization. Close after draining; copies share terminal closure, and the borrowed backend is never closed here. Other canonical rate profiles and actual OTP HTTP handlers remain pending.

Start and stop

Construct NewHTTPServer(ServerConfig{...}, handler) with explicit address, read-header/read/write/idle/handler/drain timeouts and a header limit. Construction does no IO. Start(ctx) binds and blocks until serving/cleanup ends; callers that run it in a goroutine must join its result. Serve(ctx, listener) transfers listener ownership only after valid arguments and successful start reservation. Use it for deployment-supplied TLS termination/listeners. Neither method owns domain stores, pools or providers.

Started() closes on listener acquisition/admission, not dependency readiness. Also observe the serving result: the channel remains open after startup failure or pre-start shutdown. Address() explicitly returns the bound local address; never put it in a public probe. State() reports lifecycle only.

Shutdown(ctx) refuses admission and gracefully drains under the earlier caller deadline or configured drain. On cancellation/timeout it cancels request work and closes owned connections; repeated calls retain its original result. Shutdown before start is terminal. A reserved start, even failed bind, cannot restart. An invalid/pre-cancelled start does not reserve ownership. Successful ordinary shutdown returns nil from serving; owning-context cancellation returns the original context error. Network failures return sanitized ErrUnavailable.

Deadlines and security

Reusable durations must be positive and no greater than 24 hours; header bytes must be 1024..65536. Handler timeout must not exceed write timeout. The reference deployment's narrower reviewed bounds remain its composition responsibility. There are no silently chosen production defaults. Handler context retains an earlier inbound deadline, bounds response streaming, and interrupts blocked request-body reads on cancellation without extending the read timeout.

Handlers must honor cancellation and release their resources. Go cannot kill arbitrary caller code that ignores it. Connection closure/cancellation never proves a domain mutation rolled back. No hijacking or detached handler work is supported. http.ResponseController.Flush is supported inside the deadline; the writer does not expose Hijacker, ReaderFrom, or Unwrap bypasses. Raw standard-library panic/connection diagnostics are discarded because they can disclose request values. No caller callback runs under the lifecycle lock.

Typed-nil http.HandlerFunc and a nil handler interface are rejected; arbitrary handler implementations must be valid usable values. No reflection-driven handler introspection is performed. Supplied listeners must implement prompt Close/Accept interruption; callers retain rejected listeners. Deployment owns TLS credentials and protocol negotiation; no TLS or outbound provider policy is inferred here.

Health and readiness

NewProbeHandlers(ProbeConfig{State: server.State, Readiness: provider, Timeout: time.Second}) returns separate handlers. Register them only at /healthz and /readyz on a private mux before starting the server, as shown in the executable example. Construction never calls the provider or lifecycle callback. Timeout is explicit, positive and at most two seconds; earlier request deadlines win. Callbacks must be prompt/concurrent-safe and readiness work must cooperate with cancellation. No goroutine is detached to impose a timeout on code that ignores cancellation.

Only exact GET requests are accepted: no implicit HEAD, escaped/trailing-slash aliases, query strings, bodies, trailers or Idempotency-Key. Wrong paths return 404; wrong methods return 405 and Allow: GET; nonempty requests return 400. Every response has Content-Type: application/json, Cache-Control: no-store and X-Content-Type-Options: nosniff. HEAD rejection has no body.

Liveness returns 200 and {"status":"alive"} without consulting state or dependencies. Readiness returns 200 and {"ready":true} only when the server is running before and after an affirmative composed check, with no blocking codes, errors or expired context. All other readiness outcomes return 503 and {"ready":false}. Fixed bodies end in a newline. Error bodies are the fixed {"error":"not_found"}, {"error":"method_not_allowed"} or {"error":"invalid_request"}; request/callback values are never reflected.

Composition owns actual dependency/migration/key checks; do not substitute listener acquisition or an unconditional true callback in a real deployment. The callback's internal codes and errors are never serialized. Optional-provider degradation does not block readiness unless composition makes it mandatory. No readiness result is a future lease. During drain the server may refuse network admission or close its listener even though process liveness itself does not depend on readiness. The handlers add no routes or global registry. They do not yet implement canonical direct operations, route rate policy, endpoint-manifest/OpenAPI bijection or complete reference readiness.

Explicit feature selection

NewDocumentedHandler(ctx, DocumentConfig{Title: "Identity", Version: "1"}, selection) returns exact selected dispatch and immutable OpenAPI 3.1.1 from the same snapshot. document.JSON() and document.Operations() return detached exports. Empty selection advertises no routes or components. The real probe and optional session factories supply separate WireOperation definitions; a direct-contract summary alone is rejected. No document route is enabled implicitly. The complete security-policy router and explicit metadata endpoint remain separate required work.

Trusted extension factories may attach bounded WireOperation JSON and unique schema/security components. The builder rejects malformed/duplicate JSON, colliding components, unresolved/external references, invalid schemas and missing definitions before returning a handler. Internal JSON pointers only are supported: $id, $dynamicRef and external examples are excluded from this self-contained profile. Schema compilation and normative validation use the pinned Golib OpenAPI owner, with no network access. Construction has a five-second budget that never extends the caller's deadline; no runtime dependency executes. Canonical dotted operation IDs are deliberately preserved despite the owner's programming-identifier portability advisory; client generators must map them. All other diagnostics and saturated reports deny. This is not release or generated-client qualification.

NewSelectedHandler(FeatureServices{Features: selected}) freezes exact dispatch of validated selected features. Empty selection returns 404 and advertises nothing. There are no redirects, path cleaning, implicit HEAD/OPTIONS, prefix matches or escaped aliases. Unsupported methods return 405 with a sorted Allow; HEAD errors have no body. Selection/collision failures return no partial handler. This root-mounted dispatcher preserves each feature's parsing, authentication, CSRF, rate and outcomes; it does not repeat those checks or debit counters twice. Use real owned feature factories or trusted extensions implementing their full declared controls. NewSelectedHandler is not the planned complete NewRouter/middleware/wire-OpenAPI builder. The server still owns bounded IO.

Owned optional factories use NewCanonicalOperation(CanonicalOperationConfig) to bind real handlers to the embedded catalog without maintaining policy or digest copies. Unknown/nonrouted operations and incompatible owner/policy/schema bounds deny. Returned schema summaries describe direct Go contracts, not HTTP wire schemas. For self-signout and password change, CSRFNone means an explicitly selected bearer handler; CSRFSessionState means exact cookie Origin and the active-session token. Only self-signout permits absent/inactive origin-only success; password change always requires an active session. The latter binds token lifetime to active stored session state and claims no independent maximum-age enforcement. Factories retain responsibility for selecting and enforcing the matching mode. Password verification additionally permits explicit CSRFNone for a strict bearer handler or CSRFSessionState for a cookie handler backed by native stored-synchronizer protection. The cookie profile requires its configured token header, denied Referer fallback and no independent token-age claim. Factories must enforce the selected custody and native protection; canonical metadata recognition alone selects no endpoint or authenticates a request. Conditional pre-auth/session canonical rows also permit an explicit CSRFExactOrigin with denied Referer fallback for a pre-auth-only selected handler. That handler must reject session/MFA variants, never infer pre-auth authority from ambient session credentials, and describe its restricted wire profile. Session-only rows cannot select this narrowing. Full conditional selection and its token requirements remain unchanged. SafeRateLimiter.Configured() rejects nil/zero/closed owners without IO; true is not backend health or future admission and may race with Close.

NewOperationDescriptors(FeatureServices{}) returns no operations. Nothing is enabled by importing this module. NewProbeFeature contributes the two real probe handlers; pass the resulting feature in FeatureServices.Features to select them. Other feature owners contribute handlers with NewFeature. This constructor validates metadata; it does not supply missing domain handlers.

Feature dependencies must all be selected, in any order. Missing dependencies, cycles, duplicate feature/operation IDs, exact method/path collisions, and component-name or schema-ID collisions reject the entire selection with ErrInvalidInput, without partial output. Canonical operations must retain their catalog ownership, transport policies and public-contract content digests. Custom operations use extension.<owner, replacing slash with dot>.<suffix>. Direct-only and middleware operations cannot become routes through this API.

Input and output metadata are detached snapshots, sorted by operation ID; mutating a returned slice does not change the feature. Handlers are borrowed, not cloned: their owner must supply valid, concurrent-safe implementations. Construction performs no IO or handler calls. Limits are 64 selected features, 1024 operations, 4096 components overall, and 64 components per operation. See the public type documentation for individual field bounds.

RatePolicyID identifies a complete canonical policy, including multi-bucket requirements; selection does not enforce it. TypedSchema binds a declared contract and digest, not a rendered JSON Schema. Selected descriptor summaries are not an OpenAPI document or proof of route security. The secure router, shared-token-route dispatch, OpenAPI renderer and domain feature contributions remain required subsequent work. The embedded canonical catalog is a validation input, not an advertised list of implemented endpoints. Regenerate it from the active planning snapshot with scripts/generate-http-catalog.rb; the generator records source provenance and does not claim handler completion. Contract digests use recursively key-sorted compact JSON followed by one LF; they do not depend on a pretty printer's empty-collection formatting. Run ruby scripts/generate-http-catalog_test.rb from the repository root for the fixed empty-collection digest oracle.

Native email OTP recovery definitions live in otp/email_verification_contracts.json at the repository root; pass it as the generator's optional fifth input. Definitions and semantics bind the distinct verification and primary-change recovery methods, not their HTTP wire schemas. The primary-change addition preserves existing embedded operation rows and records the new native-source and combined-operation digests. Unrelated drift in the historical planning snapshot is not adopted by this scoped addition.

The generator accepts an optional fifth NATIVE_CONTRACTS.json input for new native-owned operation/semantics/type definitions not yet present in the preserved planning snapshot. Duplicate operation/type authority is rejected before replacing output; existing rows are not overridden. The pending-email OTP recovery source is otp/email_verification_contracts.json at repository root:

ruby scripts/generate-http-catalog.rb \
  "$IDENTITY_PLAN/API_OPERATIONS.md" "$IDENTITY_PLAN/OPERATION_SEMANTICS.json" \
  "$IDENTITY_PLAN/PUBLIC_CONTRACTS.json" /tmp/identity-http-catalog.json \
  otp/email_verification_contracts.json

Review generated differences against the embedded native baseline before adoption. A newer planning input can also contain unrelated changes; registering one native operation does not authorize replacing previously retained contracts. The checked-in catalog retains those original rows and their source provenance alongside the new native source identity. It advertises no unselected handler.

Phone verification recovery's native definitions are owned by phone/verification_contracts.json. The embedded catalog retains all earlier rows unchanged and adds that original-session, code-free recovery contract with its own source digest. The selected existing-account phone profile permits only the original stored X-CSRF-Token synchronizer with exact Origin and denied Referer fallback; it neither issues an independently aged token nor enables phone signup/signin/reset or any handler by registration alone.

Phone SMS-code signin's separate Send and historical-recovery definitions live in phone/signin_contracts.json. Use that as the generator's fifth input for these native additions; retain all existing embedded rows and their provenance when adopting only the two new operations. The embedded combined-operation and native-source digests bind the retained rows plus these additions. Catalog registration alone enables no route or authentication method.

Bounded ordinary request parsing

Construct one NewRequestDecoder(Limits{...}) with every bound explicit. DecodeJSON[T] accepts one JSON object, validates its structural/resource boundary, then decodes into the chosen public or feature-owned input type. Duplicate and case-alias member names, escaped aliases, invalid UTF-8 or unpaired surrogates, trailing values and unknown typed fields deny without returning a partially populated value. JSON's standard field matching otherwise applies. Caller-supplied custom unmarshaling is trusted code and must cooperate with cancellation; it runs only after structural validation.

Root nesting depth is one. Field budgets count object members and array elements across the entire document. Decoded byte budgets include unescaped UTF-8 keys, string values and scalar lexical bytes, not punctuation or whitespace. Raw bytes have a separate ceiling. Form parses only the urlencoded body; Query parses only URL.RawQuery; neither merges cached request forms or the other channel. Empty form/query is valid, a missing equals sign means an empty value, and empty names/segments, bare semicolons, invalid percent encoding or UTF-8, duplicate/case-alias names and idempotency-key alternatives deny atomically.

Credential header binding

RequestDecoder.IdempotencyKey returns the existing opaque kernel key and a presence flag. It preserves exact header bytes under the generic 1..256-byte HTTP-token grammar, without normalization, hashing or retry-state management. HeaderRequired is for keyed commands; HeaderOptional preserves explicitly optional protocol extensions; HeaderForbidden (zero) rejects even empty header presence. Unknown policy values reject as configuration errors.

RequestDecoder.Bearer returns the existing authentication credential, not a verified principal. Its selected grammar is case-insensitive Bearer, exactly one space, and 1..8192 ASCII letters/digits or -._~+/, with optional trailing = padding and at least one non-padding byte. The envelope limit may impose a smaller ceiling. Session token parsing remains the session owner's stricter profile. Forbidden mode supports bearer issuance rejecting Authorization.

Both helpers reject duplicate/case-alias headers, enforce the envelope and never read or close the body. Query/cookie/trailer retry-key alternatives deny; bearer query alternatives and Proxy-Authorization deny. Ambient cookies never provide bearer authority. Call the appropriate body decoder as well: header extraction alone cannot prove absence of body alternatives. Multipart/protocol codecs, route-derived policy, trusted proxy provenance, prevention of middleware credential injection and full routing remain subsequent work. A syntactically valid header cannot reveal whether an upstream proxy replaced it.

RequestDecoder.CookieBearer acquires one exact case-sensitive cookie into the same opaque bearer type. It rejects Authorization/proxy credentials and selected-name query alternatives rather than choosing a fallback. All Cookie lines share 16KiB/32-part bounds; malformed siblings reject. The selected part is unique, unquoted and at most 3800 bytes including its name. Original value bytes are unchanged. Optional/required/forbidden policy applies to that selected cookie only. Owning session/flow services still validate their token format, possession and current authority. This helper creates no flow, route or session.

RequestDecoder.AuxiliaryCookieBearer explicitly acquires auxiliary custody alongside one syntactically valid direct Authorization bearer. The primary credential is still authenticated by its owner; the cookie must never serve as a session credential or fallback. Proxy/query alternatives deny. It shares all strict cookie limits above without modifying the request or weakening CookieBearer exclusivity. Use it for explicitly selected signed browser-device correlation, then validate that provenance with the session device codec.

Envelope and body ownership

Media types are explicit application/json or application/x-www-form-urlencoded, optionally with only UTF-8 charset. Compression is absent or explicitly identity, never implicitly decompressed. Duplicate sensitive headers, inconsistent lengths, unsupported transfer metadata, trailers, and oversized represented headers/URLs reject before body reads. Chunked bodies are supported through net/http's parsed transfer metadata; the decoder is not a replacement for wire-level server framing validation. Header accounting includes each represented name, colon/space, value and CRLF, final CRLF and the separately represented Host. URL accounting covers the represented origin-form request URI and rejects conflicting RequestURI/URL. Trusted host/proxy semantics require the network resolver below. Origin admission is separate; token-bound CSRF/CORS and router enforcement remain unfinished.

Bodies are borrowed: the server or direct caller closes them. Reads consume at most the applicable raw or declared-length bound plus one byte, and do not drain excess input. Earlier request cancellation wins; an arbitrary caller body that blocks without honoring server deadlines cannot be forcibly interrupted by this synchronous API. Returned DecodeError retains no raw input or reader diagnostic. No parser authenticates a credential or performs domain work. Trusted credential routing, protocol-specific and multipart decoding, router status/error integration and complete endpoint verification remain pending.

Trusted network and exact tenant admission

NewNetworkResolver(NetworkConfig{...}) copies one immutable deployment policy. Supply explicit decoder limits, 1..16 maximum forwarding elements (reference 8), at most 128 canonical masked proxy prefixes and 1..1024 total exact host bindings. Empty proxy prefixes trust none. Duplicate or IPv4-mapped prefixes reject; overlapping prefixes are an explicit set union. Tenant/public hosts cannot overlap. Configuration hosts must already be canonical and port-free; Unicode request hosts use the identifier owner's fixed Unicode 15.1 nontransitional UTS46 tables. DNS names are lowercase ASCII, IPv4 dotted decimal and IPv6 bracketed canonical text. Wildcards, trailing dots (including mapped Unicode dots), unknown hosts and suffix-derived tenants deny. Numeric/hex-looking final DNS labels also deny to avoid browser legacy-IPv4 reinterpretation; use canonical IP literals instead.

Resolve(request) returns opaque NetworkFacts or zero facts with an error. An untrusted socket peer ignores forwarding semantics and uses its actual IP, request Host and TLS state. A trusted socket peer requires exactly one Forwarded map key and value. The selected closed RFC 7239 subset requires:

  • At most the configured elements and 16 parameters per element; no empty elements/pairs, duplicate case-insensitive names, or whitespace around =.
  • A concrete unicast for address in every element; optional by must also be concrete. Unknown/obfuscated identifiers and ports, zones, unspecified and multicast addresses deny. Quoted IPv6/IP-with-port syntax is supported.
  • All chain syntax is validated, including attacker-controlled prefixes. Walk right-to-left through trusted senders to the first untrusted for address. That element alone must supply both valid host and proto=http|https. Never borrow earlier host/proto or fall back to the proxy/direct host.
  • Unknown extension parameters are syntax-checked but supply no authority. Quoted separators and escaped quote/backslash are handled as data.

All X-Forwarded-* semantics are ignored, not combined with Forwarded. Missing, malformed, duplicate or exhausted trusted chains return sanitized ErrUnavailable; invalid envelope/host or an unmapped host returns DecodeError. Cancellation preserves its context error. Parsed header limits still apply to ignored fields. No DNS, body reads, goroutines, stores or domain callbacks occur.

ClientIP() returns the full canonical unicast address, unmapping IPv4-mapped IPv6 with no subnet aggregation. Host() is the exact port-free map key; Authority() preserves a validated explicit decimal port. Origin() omits only the scheme's default port. Realm() explicitly distinguishes RealmPublic from RealmTenant; only the latter has a TenantID(). A query/body/header tenant cannot override that binding. Facts are redacted in ordinary diagnostics and cannot serialize implicitly; getters deliberately disclose metadata to trusted code.

Resolve once for each owned request before tenant-scoped work. Facts are neither authentication proof nor a reusable authority lease. Server composition owns RemoteAddr/Host/TLS/header custody: arbitrary in-process requests or mutating middleware cannot establish provenance. Reload by replacing a newly validated resolver, not mutating configuration. Routing, rate enforcement, cookies and token-bound CSRF/CORS integration remain required subsequent work.

HTTPS origin admission

NewOriginGuard(OriginGuardConfig{...}) borrows the immutable network resolver and copies 1..128 distinct trusted origins. This profile requires canonical HTTPS origins: lowercase ASCII/IDNA hosts, no default port, path, query, fragment, userinfo or wildcard. Configuration and Origin header values are rejected rather than repaired when noncanonical. Nondefault ports are distinct. Extension and local-HTTP deployment profiles are not yet supported.

Check(request) resolves the current network facts and requires one exact allowlisted Origin. A present empty, malformed, duplicate or untrusted Origin never falls back to Referer. Zero fallback and RefererFallbackDeny both deny an absent Origin. Only explicit RefererFallbackSameOriginHTTPS admits one absolute HTTPS Referer with an origin equal to the current trusted effective origin and the allowlist. Referer path/query is permitted; userinfo and fragments reject. Referer origins normalize ASCII host case/default ports through the same fixed identifier policy, never by suffix or cross-origin trust. Referer path/query bytes must satisfy RFC 3986 URI grammar; raw characters such as | and malformed percent escapes deny, while encoded equivalents remain valid.

CheckBound(request, initiatingOrigin) additionally compares the exact validated origin to the workflow's stored binding. It does not validate or consume that workflow's state, nonce, PKCE, continuation or one-time token. Both methods return an empty origin on failure, retain cancellation/network classifications, and never read/close the body or mutate the request. Invalid origin evidence returns redacted OriginError; envelope errors retain DecodeError.

Admission alone is not complete CSRF protection, authentication or authorization. Cookie mutations still require session-bound tokens. Token-bound CSRF and secure router integration remain separate work; consumers must not expose unprotected operation handlers directly.

Exact-route CORS admission

NewCORSHandler(CORSConfig{Network: network, Routes: policies}, next) validates and copies up to 1024 distinct exact method/path policies. Each route explicitly selects CORSNoCredentials, CORSCookieCredentials or CORSBearerCredentials; incoming cookies never select or widen the mode. Empty origins disable CORS for a route, requiring empty header/exposure lists and zero max age. Empty Routes disables cross-origin access entirely. Configured origins follow the same canonical HTTPS profile as OriginGuard, without Referer fallback.

Enabled routes require 1..128 exact origins, up to 64 distinct case-insensitive allowed/exposed header tokens each (128 bytes per token), and whole-second max age in 0..24 hours. Wildcards and exposed Set-Cookie/Set-Cookie2 reject. The reference composition retains its narrower fixed origin/header profile. There is no global method grant: preflight selects the requested exact route and uses only its origin, method, header and credential policy. Missing or ambiguous control fields, private-network requests and unsupported headers deny without handler, authentication, quota/state consumption or body reads/closes.

Ordinary requests without Origin or from the current effective origin remain usable even with CORS disabled, but receive no grants unless explicitly allowed. Cross-origin requests require exact method/path and origin membership; unknown routes deny. This behavior is not CSRF proof. The wrapper resolves current network facts itself, never using cached/caller-submitted facts or arbitrary Host. It passes the original borrowed request to next, using a detached bounded header map only for the existing generic Fetch CORS engine.

Response controls remove all case aliases of downstream CORS grants and restore only the approved snapshot before ordinary status/write/flush/ReaderFrom paths and after header-only return. Vary preserves initial and downstream dimensions plus Origin; preflights additionally vary by requested method and headers. Bearer-mode responses and all preflight/denial responses force no-store, even without Origin. Denials return a fixed bounded redacted response with no redirect target. Cancellation denies before handler admission. The wrapper starts no goroutines, does no IO and does not buffer response bodies. Trusted downstream code must not unwrap/hijack the writer to bypass transport controls.

This exact-path standalone foundation is not the secure router. Actual descriptor binding, parameterized route selection, authentication/authorization, cookie and token CSRF, protocol flows and browser journeys remain required integration work. Do not treat CORS grants as permission to expose unprotected domain handlers. ExampleNewCORSHandler demonstrates separate cookie/bearer policy and preflight without invoking the supplied handler.

Exact HTTPS redirects

NewRedirectGuard(RedirectGuardConfig{Network: network, Targets: targets}) copies 1..128 distinct full HTTPS URLs, each at most 2048 bytes. The immutable network owner supplies fixed host syntax only; redirect destinations need not be served hosts. Scheme and authority must already be canonical, without an explicit default port. Wildcards, fragments, userinfo, backslashes, malformed escapes and non-URI raw characters reject during construction.

Check(ctx, target) returns the original target bytes only on full equality. Path, percent-escape spelling, query order and empty-query markers are distinct; the guard never repairs, resolves or serializes a URL to obtain a match. It never consults request headers, emits Location or appends response parameters. Denial returns an empty string and redacted OriginError; cancelled contexts retain their error. Invalid guard/context returns ErrInvalidInput.

Construct a guard per registered policy owner. A union of clients' redirect sets is not client-specific registration validation. Admission does not establish tenant, client or initiating-flow binding, consume state/nonce/PKCE or authorize arbitrary parameter appends. Owning protocol workflows still perform those checks and construct their responses. Never send even an OAuth error redirect to a denied target. This HTTPS foundation does not implement native-application or loopback HTTP exceptions, browser redirect journeys or full secure routing.

Construct NewCSRFDecoder(CSRFDecoderConfig{Origins: origins, FormField: "csrf_token"}) with an explicit immutable origin guard and form-field name. There is no default field. Names are lowercase ASCII identifiers, 1..64 bytes, starting with a letter; idempotency-key names are reserved. The fixed header is X-CSRF-Token. This profile is selected by trusted cookie-route composition, not by inspecting an inbound Authorization header or Cookie.

The opt-in session feature calls DecodeCSRF(decoder, request, session.ParseCSRFToken). Base HTTP does not import session. The parser is a trusted synchronous, bounded, no-IO credential constructor, not an arbitrary authentication callback. Errors return no credential or form data; parser diagnostics and token values are never reflected. Cancellation is preserved.

Only exact POST, PUT, PATCH and DELETE are supported. Origin admission and bounded envelope/query decoding precede token parsing. Exactly one canonical 43-byte unpadded base64url token must arrive through the fixed header or the configured URL-encoded form field. Duplicate/case-alias/query channels and header/form coexistence deny even when values agree. Cached Form/PostForm are ignored. Forms are read under the configured limits even with a header; success returns remaining form fields without the token. JSON requires the header and remains unread for the owning strict typed decoder, which must reject undeclared body credential fields before any domain work. Multipart is not supported. Body interruption and borrowed ownership follow RequestDecoder.Form; this helper does not close or drain the body or start background work.

Successful input admission is not authentication or a session-validity lease. The feature must pass the opaque token to session PostgreSQL RequireCSRF with the frozen original session in the protected transaction, retaining primary fences through protected work. Token age metadata is not enforced here; no timestamp, freshness or authority is invented. Safe methods, bearer-only routes, pre-authentication, origin-only and bound-origin flows use separate profiles. This helper does not install routes or replace that feature-owned integration. The external consumer fixture in testdata/sessioncsrf/composition_test.go exercises the public HTTP/session seam without adding a base-module dependency.

Tradeoffs and FAQ

Why is Start blocking? Startup/bind errors and the final serving outcome have one explicit return owner. A caller choosing concurrency must retain and join it.

Can a stopped instance restart? No. Construct a new owner so terminal cleanup and shutdown results cannot be confused with another listener generation.

Canceled requests cannot emit successful responses or credentials. Before any response commitment, an owner may synchronously return one explicit 503 with its safe original command status, bounded to 16KiB. Only content type, retry and transport-owned CORS headers survive; cookies, CSRF fields and trailers do not. Oversized canceled outcomes and cancellation after response commitment abort the connection. This does not retry work, extend deadlines or guarantee delivery to a disconnected client; retain the original recovery correlation.

Does cancellation undo a command? No. The domain owner must reconcile any possibly committed mutation before retrying; transport errors cannot decide it.

API and verification boundary

NewOperationPipeline[T] is the feature integration seam for actual middleware phases. A feature supplies its existing bounded decoder, authentication owner, admission (authorization/rate/CSRF), domain handler and safe error mapper through OperationPipelineConfig[T]. All five callbacks are explicit, including public operations' no-auth decisions; no authentication or rate engine is added here.

Execution is decode → before-auth wrappers → authentication → after-auth wrappers → admission → before-handler wrappers → domain handler → after-handler observers. Within a phase, unique numeric order fixes entry order and reverse unwind order. Short circuit or error stops later stages; repeated next calls cannot repeat the protected operation. PipelineValue[T] exposes the owner's typed state only after authentication and only during the request. This is not a reusable proof.

Middleware receives detached metadata, no TLS/associated-response pointers and no readable body. Feature callbacks retain the original bounded snapshot, so middleware cannot rewrite the host or credential inputs seen by those callbacks. After-handler wrappers run only after normal, uncanceled handler return, with a read-only response facade: they observe an attempt, not a guarantee of transaction commit. They cannot emit or replace a response. Callbacks/wrappers are trusted synchronous code and must not retain requests, spawn detached work, unwrap/hijack writers or ignore deadlines. Constructor callbacks must do no IO. The server still owns raw IO and streaming bounds. Precommit panics use the feature's safe error mapper; once output or streaming has begun, panics abort the HTTP response rather than successfully completing a partial result. No raw panic diagnostic is exposed. Neither path proves rollback.

WithOperationMiddleware(handler, hooks) appends selected router hooks through OperationMiddlewareBinder at those same real phases. Pipelines retain frozen callbacks/policy and copy the combined wrapper list; existing handlers remain unchanged. Combined duplicate owner/name or phase/order fails before factories run. Each new binding constructs its own wrappers, including existing ones, so factory callbacks must remain side-effect-free and returned handlers must be concurrency-safe. An empty hook list returns the original nonnil handler. Opaque handlers reject nonempty binding; wrapping them outside cannot fabricate an after-auth phase. Trusted integrations must preserve their response guards around the bound pipeline. Binding is not full NewRouter qualification.

The pipeline does not itself resolve hosts, parse credentials, authenticate, authorize or register routes. Existing direct feature handlers remain unchanged; their adoption and the complete NewRouter policy binding are pending. Do not wrap an already complete feature handler with a second authentication pipeline.

See Go documentation for ServerConfig, HTTPServer, lifecycle states and classifiable errors. ExampleHTTPServer is an executable public adoption example. Unit/network tests exercise timeout, listener ownership, graceful/forced drain, failure and concurrent transitions. Complete route/security/parser/browser, OpenAPI/client, exact coverage/mutation, interoperability and release gates remain pending. This module is not marked verified.

Documentation

Overview

Package identityhttp owns opt-in identity HTTP transport. The current development surface provides server lifecycle and probes, not domain routes or deployment composition. Handlers must honor cancellation and own their bounded callbacks.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrInvalidInput = errors.New("identityhttp: invalid input")

ErrInvalidInput rejects invalid configuration, nil contexts and zero owners.

View Source
var ErrLifecycle = errors.New("identityhttp: lifecycle conflict")

ErrLifecycle rejects a second start, including after a failed start or stop.

View Source
var ErrRateLimited = errors.New("identity.http.rate_limited")

ErrRateLimited denies a request whose applicable rate bucket is exhausted.

View Source
var ErrRateUnavailable = errors.New("identity.http.rate_unavailable")

ErrRateUnavailable denies unavailable, ambiguous or invalid rate evidence. Backend diagnostics and subject identifiers are never retained.

View Source
var ErrUnavailable = errors.New("identityhttp: unavailable")

ErrUnavailable reports a bind or serving failure without disclosing addresses, peer values or underlying transport diagnostics.

Functions

func DecodeCSRF

func DecodeCSRF[T any](decoder *CSRFDecoder, request *http.Request, parse func(string) (T, error)) (T, url.Values, error)

DecodeCSRF admits exact origin and exactly one token channel for POST, PUT, PATCH or DELETE, then invokes the selected credential owner's parser once. Pass session.ParseCSRFToken from the opt-in session integration. The generic seam keeps base HTTP independent of optional feature modules; it is not a crypto callback, session validator or policy substitute. The trusted parser must synchronously copy opaque custody, perform no IO and return boundedly. Parser errors are redacted; every error returns zero T and nil form data. Cancellation is checked before and after the callback, not by detached work.

Tokens are exactly 43 canonical unpadded base64url bytes encoding 32 bytes. Query alternatives, duplicate/case-alias channels and header/form coexistence deny, including equal values. Cached Form/PostForm are ignored. URL-encoded forms are bounded/read even with a header, and success returns their remaining fields with the selected credential removed. JSON requires the header and leaves its body unread for the owning strict typed decoder, which must reject undeclared body credential fields before domain work. Other media types deny.

The existing network decoder supplies all envelope/form/query limits. Body borrowing and blocking-read interruption follow RequestDecoder.Form; nothing here closes/drains it or creates a goroutine. The server retains request metadata custody throughout decoding. GET/HEAD/OPTIONS, bearer-only routes, pre-auth, bound-origin and origin-only flows must use their own profiles; passing them here is invalid and does no body/parser work.

Success is NOT a current session proof or lease. The protected operation must validate the named-key token against its frozen original and active primary session under the same transaction as protected work (RequireCSRF), retaining all owner fences and terminal validity checks. This decoder does not enforce TokenMaximumAge metadata, renew freshness, consume tokens or issue sessions.

func DecodeJSON

func DecodeJSON[T any](decoder *RequestDecoder, request *http.Request) (T, error)

DecodeJSON reads one ordinary application/json object into T. It rejects unknown typed fields, trailing values, duplicate/case-alias member names at every depth, invalid UTF-8/surrogates, and idempotency-key body alternatives. Root depth is one. MaximumFields counts all object members and array elements globally; decoded bytes count unescaped UTF-8 string/key bytes and scalar lexical bytes, excluding whitespace/punctuation. This validation precedes typed decoding and its caller-owned custom unmarshaling methods.

On any error the returned T is zero, even after partial codec assignment. Standard encoding/json field matching otherwise applies. Custom unmarshaling methods are trusted caller code and must be bounded/cancellation-cooperative. The body is borrowed, never closed or drained beyond the configured bound plus one byte. The HTTP server/direct caller retains closure and blocking-read interruption ownership; no detached read goroutine is created. Cancellation preserves context errors; other input/reader errors become DecodeError.

func NewCORSHandler

func NewCORSHandler(config CORSConfig, next http.Handler) (http.Handler, error)

NewCORSHandler validates/copies route policy without IO or handler invocation. Invalid network/handler, bounds, duplicates or unsafe policy returns ErrInvalidInput and no handler. The generic Golib Fetch CORS engine handles protocol responses after strict identity admission. Preflights never invoke next, authenticate, consume quota/state, or read/close the request body. Ordinary missing-Origin and same-effective-origin calls may invoke next with no grants even when CORS is disabled; this is NOT origin/token CSRF proof. Other origins require exact route and origin membership. No Referer fallback, wildcard, dynamic origin or private-network grant is supported.

The returned immutable handler is concurrent-safe if next is. It borrows next and Network; callers own their lifetimes and must not mutate constructor inputs during construction. Request metadata is never mutated. It retains Vary and CORS/cache header custody through ordinary commitment paths and handler return, without buffering responses or starting goroutines. Trusted handlers must not unwrap/hijack the writer to bypass transport policy. This wrapper is not a secure router, authorizer, cookie serializer or protocol-flow validator.

Example
package main

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"net/http/httptest"
	"time"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	network, err := identityhttp.NewNetworkResolver(identityhttp.NetworkConfig{
		Limits:               identityhttp.Limits{MaximumHeaderBytes: 4096, MaximumURLBytes: 2048, MaximumBodyBytes: 4096, MaximumDecodedBytes: 4096, MaximumFields: 32, MaximumDepth: 8},
		MaximumForwardedHops: 8, PublicHosts: []string{"login.example"},
	})
	if err != nil {
		panic(err)
	}
	calls := 0
	handler, err := identityhttp.NewCORSHandler(identityhttp.CORSConfig{Network: network, Routes: []identityhttp.CORSRoutePolicy{
		{Method: "POST", Path: "/cookie", Credentials: identityhttp.CORSCookieCredentials, Origins: []identityhttp.Origin{"https://client.example"}, AllowedHeaders: []string{"Content-Type", "X-CSRF-Token"}, MaxAge: 10 * time.Minute},
		{Method: "POST", Path: "/bearer", Credentials: identityhttp.CORSBearerCredentials, Origins: []identityhttp.Origin{"https://client.example"}, AllowedHeaders: []string{"Authorization"}},
	}}, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { calls++; w.WriteHeader(http.StatusAccepted) }))
	if err != nil {
		panic(err)
	}
	request := httptest.NewRequest("OPTIONS", "/cookie", nil)
	request.Host, request.RemoteAddr, request.TLS = "login.example", "198.51.100.7:1234", &tls.ConnectionState{}
	request.Header.Set("Origin", "https://client.example")
	request.Header.Set("Access-Control-Request-Method", "POST")
	response := httptest.NewRecorder()
	handler.ServeHTTP(response, request)
	fmt.Println(response.Code, calls, response.Header().Get("Access-Control-Allow-Credentials"))
	// The real owning router must still authenticate, authorize, rate-limit and
	// enforce token-bound CSRF. This example proves only CORS preflight admission.
	

func NewOperationPipeline

func NewOperationPipeline[T any](c OperationPipelineConfig[T]) (handler http.Handler, err error)

NewOperationPipeline validates the complete configuration before invoking any wrapper constructor, then freezes deterministic phase/order composition. It invokes no feature stage, store or network operation. Invalid values, duplicate ownership/order, nil returned handlers or constructor panics fail atomically. At most 64 wrappers are accepted. Inputs must remain stable during construction.

Request processing is Decode -> BeforeAuth -> Authenticate -> AfterAuth -> Admit -> BeforeHandler -> Handle -> AfterHandler. Wrapper entry order is ascending and unwind order descending within each phase. A short circuit or error stops later phases. AfterHandler sees a read-only response facade and runs only after Handle returns normally, regardless of its domain outcome. Middleware receives detached metadata, no TLS/Response pointers and no readable body; owner stages use the original bounded request snapshot, so wrapper metadata changes cannot replace credential/tenant inputs or extend the owner deadline. Cancellation is checked before each stage. Re-entry does not repeat authentication, admission or mutation. Precommit panics map to ErrUnavailable. After output/streaming has begun, panics abort the transport via http.ErrAbortHandler rather than completing a partial response or overwriting committed bytes. An intentional ErrAbortHandler is preserved in either state; no raw panic value escapes to server diagnostics.

func NewProbeHandlers

func NewProbeHandlers(config ProbeConfig) (liveness, readiness http.Handler, err error)

NewProbeHandlers returns separate immutable liveness and readiness handlers. Their only accepted requests are exact GET /healthz and GET /readyz, respectively; HEAD is not implicitly enabled. Query strings, escaped aliases, nonempty/framed bodies and Idempotency-Key are rejected. Routing mismatches are 404, methods are 405 with Allow: GET, malformed empty requests are 400. Responses are fixed application/json with Cache-Control: no-store and nosniff. Liveness emits {"status":"alive"}; readiness emits only {"ready":true/false}, never callback codes or errors. Readiness is 200 only while Running with a valid affirmative snapshot and a live check context; every other result is 503. A final state check denies callbacks finishing during drain. Like every instantaneous probe, success describes its final observation, not a lease.

The returned handlers support OperationMiddlewareBinder. Bound hooks observe explicit public/no-auth stages after input validation, with Authorization, proxy credentials and cookies removed from detached request views. They receive no principal. Their response writer discards output: short circuits deny with fixed503 rather than invent readiness; only the probe owns output. Original unbound handlers remain unchanged. Bound hooks share a whole-call budget of max(Timeout,1ms); readiness retains its original narrower bound. Rebinding appends hooks without changing owners; cancellation and final drain are checked again after hook unwind. No response or readiness is cached.

Register these handlers at their exact paths on a private mux before serving. They do not register domain routes or implement the full transport rate policy, direct operation API, endpoint manifest or OpenAPI contract. A draining/closed HTTPServer can refuse network admission independently of handler liveness.

func NewSelectedHandler

func NewSelectedHandler(services FeatureServices) (http.Handler, error)

NewSelectedHandler freezes exact dispatch for validated explicitly selected feature contributions. Empty selects nothing and returns 404 for all paths. Construction validates dependencies/collisions and invokes no handler or IO. Routes are exact, root-mounted and case-sensitive: no cleaning, prefix match, escaped aliases, redirects, implicit HEAD or automatic OPTIONS. Query/body, authentication, authorization, CSRF, rate and domain outcomes remain owned by the selected real feature handlers; no second read, authentication or debit is performed. Callers must use owned feature factories or trusted extension handlers that implement their full declared controls. This dispatcher is NOT the planned complete NewRouter/security/OpenAPI builder and exports no metadata endpoint. Full wire-schema/router qualification remains required.

Wrong paths return 404, wrong methods 405 with a sorted Allow, malformed nil requests 400 and already canceled requests 503. Dispatcher errors are fixed no-store/nosniff JSON; HEAD errors omit the body. Returned routing is immutable and concurrency-safe; handlers/dependencies remain borrowed and must outlive requests. The server owns whole-request/write deadlines and resource cleanup.

func PipelineValue

func PipelineValue[T any](r *http.Request) (T, bool)

PipelineValue returns this pipeline's typed state only after successful Authenticate. It is for trusted synchronous middleware, not a general-purpose credential or a reusable proof. T is copied shallowly; immutable opaque owner values retain their original validity limits. Before-auth, nil, unrelated and differently typed requests return false. The original borrowed request never receives this private context state; middleware cannot inject it using headers.

func WithOperationMiddleware

func WithOperationMiddleware(handler http.Handler, middleware []MiddlewareDescriptor) (bound http.Handler, err error)

WithOperationMiddleware binds trusted synchronous hooks into the real owned phases of handler. Empty selection returns the original nonnil handler. Nonempty selection requires OperationMiddlewareBinder; opaque complete handlers fail closed rather than receiving fabricated after-authentication phases. The returned handler preserves existing hooks and owners; the new binding's wrapper factories may run once each, including existing factories. No request stage is invoked. Binding errors, nil results and panics return redacted ErrInvalidInput without a partial result. This seam does not replace NewRouter's policy, route/document validation or real feature security.

Types

type AuthRateConfig

type AuthRateConfig struct {
	Shared          ratelimit.StrictBackend
	Namespace       string
	Now             func() time.Time
	IdentifierKey   [32]byte
	MaximumDuration time.Duration
}

AuthRateConfig selects fixed original-flow, signup, signin and delivery policies, not caller-defined capacities. Shared must be a borrowed strong backend; Now must be a trusted prompt concurrency-safe clock. IdentifierKey is a stable nonzero deployment HMAC key, independent of credential keys. Namespace isolates deployment state. MaximumDuration is 1ms..2s; earlier caller deadlines win. Retain this owner across routes and requests. Changing its namespace/key resets corresponding budgets and requires deliberate rollout.

func (AuthRateConfig) Format

func (AuthRateConfig) Format(s fmt.State, _ rune)

Format prevents disclosure of the deployment identifier key.

func (AuthRateConfig) LogValue

func (AuthRateConfig) LogValue() slog.Value

LogValue redacts deployment configuration in structured logging.

func (AuthRateConfig) MarshalJSON

func (AuthRateConfig) MarshalJSON() ([]byte, error)

MarshalJSON rejects implicit export of deployment key configuration.

func (AuthRateConfig) MarshalText

func (AuthRateConfig) MarshalText() ([]byte, error)

MarshalText rejects implicit text export of deployment key configuration.

type AuthRateLimiter

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

AuthRateLimiter composes primitive-owned atomic token buckets with no local fallback, retry, goroutine or owned store. Independent calls/Close are safe. Multiple bucket debits are ordered per-item, not an all-or-nothing transaction: earlier successful debits remain after a later denial/failure. All applicable buckets must allow before the workflow may proceed. Zero is invalid.

func NewAuthRateLimiter

func NewAuthRateLimiter(c AuthRateConfig) (*AuthRateLimiter, error)

NewAuthRateLimiter freezes selected policy without admission or storage IO. All signin operations share IP20/10m and tenant/identifier10/10m. Delivery operations share IP10/h, tenant/identifier5/h and, when authenticated, tenant/subject10/h. Signup shares IP5/h and tenant/identifier3/day without a subject dimension. Different profiles have independent budgets. Identifier and subject key derivation length-frames the tenant and value under HMAC.

func (*AuthRateLimiter) Admit

func (l *AuthRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (out AuthRateResult, err error)

Admit checks all selected buckets under one bounded context and trusted clock observation. Unknown operations, missing dimensions and malformed inputs deny before any backend work. Known exhaustion evaluates remaining buckets to retain the longest retry delay. Outage, ambiguity, cancellation or invalid backend results return ErrRateUnavailable with no positive observation or fallback. The owner neither authenticates nor runs/retries protected domain work.

func (*AuthRateLimiter) AdmitDeliveryIP

func (l *AuthRateLimiter) AdmitDeliveryIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)

AdmitDeliveryIP applies shared delivery IP10/hour before native destination normalization. Only canonical delivery operations and mapped tenant facts are accepted. A failed source lookup retains this debit. After admission, call AdmitDeliveryIdentifier once for the same immutable anonymous request; do not also call Admit. This grants no identity or delivery authority.

func (*AuthRateLimiter) AdmitDeliveryIdentifier

func (l *AuthRateLimiter) AdmitDeliveryIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)

AdmitDeliveryIdentifier applies shared tenant/destination5/hour after native normalization and AdmitDeliveryIP. It does not debit IP again or refund an earlier debit. UserID must be empty: this split profile is anonymous; use Admit for authenticated delivery requiring the additional subject budget. Both profiles share the same destination bucket. Outage or ambiguity denies without fallback, retries, or a reusable admission ticket.

func (*AuthRateLimiter) AdmitOriginalFlow

func (l *AuthRateLimiter) AdmitOriginalFlow(ctx context.Context, facts NetworkFacts) (out AuthRateResult, err error)

AdmitOriginalFlow applies rate.auth's IP-only 20/10m token bucket before a tenant-bound browser flow exists. No identifier or challenge exists at this stage; this method is not suitable for operations that already have either. Full normalized IP budgets span tenants and owners sharing the namespace, independently of signin/delivery. Call before entropy or durable flow work. The caller still owns HTTPS, origin and all other operation admission. Outage, ambiguity, panic and cancellation fail closed without fallback; an earlier caller deadline wins. Admission is not a reusable clearance.

func (*AuthRateLimiter) AdmitPasskeyOptionsIP

func (l *AuthRateLimiter) AdmitPasskeyOptionsIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)

AdmitPasskeyOptionsIP applies the shared rate.auth IP20/10m bucket before native flow/proof lookup for exactly register-options and signin-options. It shares AdmitOriginalFlow's namespace and budget, never signin's bucket. Options have no issued ceremony yet: do not invent a challenge from a proof, session, flow or retry key. Identified signin additionally requires the same attempt's AdmitPasskeyOptionsIdentifier after owner canonicalization. Discovery and enrollment have no identifier input. This is rate admission, not authentication or reusable permission; outages and ambiguity deny.

func (*AuthRateLimiter) AdmitPasskeyOptionsIdentifier

func (l *AuthRateLimiter) AdmitPasskeyOptionsIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)

AdmitPasskeyOptionsIdentifier debits only rate.auth's tenant/identifier10/10m after successful AdmitPasskeyOptionsIP for this same identified signin-options attempt. Identifier must be owner-canonical; UserID must be absent. Neither anonymous discovery nor authenticated enrollment may invent this dimension. Earlier IP debit survives denial; no second IP debit, retry or lookup occurs.

func (*AuthRateLimiter) AdmitSigninIP

func (l *AuthRateLimiter) AdmitSigninIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)

AdmitSigninIP applies the existing shared rate.signin trusted-IP20/10m bucket before original-custody/source lookup. Only canonical signin operations and mapped tenant facts are accepted. On nil-error Allowed, the transport may discover its native canonical source, then call AdmitSigninIdentifier once. A failed source lookup retains this debit. Do not also call Admit for the same request: that would debit IP twice. Admission is request-local, not authority or a reusable clearance. Cancellation, panic and backend outage deny without fallback. This method neither reads custody nor creates an identifier bucket.

func (*AuthRateLimiter) AdmitSigninIdentifier

func (l *AuthRateLimiter) AdmitSigninIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)

AdmitSigninIdentifier applies only the existing tenant/canonical-identifier rate.signin10/10m bucket after native source discovery. The caller MUST first obtain AdmitSigninIP for this same request; this method grants no independent complete admission. Identifier must come from the original authenticated workflow custody, never a replacement request field. UserID must be empty. Earlier IP debits are not rolled back by rejection or outage. No ticket, local fallback, new budget or domain lookup is created; existing Admit and split callers share exactly the same primitive-owned bucket identities.

func (*AuthRateLimiter) AdmitSignupIP

func (l *AuthRateLimiter) AdmitSignupIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)

AdmitSignupIP applies shared rate.signup IP5/hour before native address normalization. Only canonical signup operations and mapped tenant facts are accepted. A failed subsequent source lookup retains this debit. After nil- error Allowed, call AdmitSignupIdentifier for the same immutable request; never also call Admit, which would debit IP twice. Admission is request-local, not authority. No account lookup, fallback, retry or goroutine runs here.

func (*AuthRateLimiter) AdmitSignupIdentifier

func (l *AuthRateLimiter) AdmitSignupIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)

AdmitSignupIdentifier applies shared tenant/canonical-identifier3/day after native owning-workflow normalization and prior AdmitSignupIP for this request. UserID must be empty; signup cannot select an authenticated subject budget. It grants no complete admission, confirms no account existence and shares exactly the same primitive-owned bucket as Admit. Rejections and outages do not refund earlier IP debits. Retry metadata retains the one-hour ceiling without shortening the actual daily budget's continuous refill period.

func (*AuthRateLimiter) Close

func (l *AuthRateLimiter) Close() error

Close terminally denies new admission without closing the borrowed backend. In-flight debits are not undone. Drain handlers before releasing dependencies.

func (*AuthRateLimiter) Configured

func (l *AuthRateLimiter) Configured() bool

Configured reports structural, nonterminal owner validity without IO. Close may race immediately afterward; true is not health or an admission lease.

func (AuthRateLimiter) Format

func (AuthRateLimiter) Format(s fmt.State, _ rune)

Format prevents traversal of keys and borrowed backend configuration.

func (AuthRateLimiter) LogValue

func (AuthRateLimiter) LogValue() slog.Value

LogValue redacts runtime composition in structured logging.

func (AuthRateLimiter) MarshalJSON

func (AuthRateLimiter) MarshalJSON() ([]byte, error)

MarshalJSON rejects implicit export of runtime composition.

func (AuthRateLimiter) MarshalText

func (AuthRateLimiter) MarshalText() ([]byte, error)

MarshalText rejects implicit text export of runtime composition.

type AuthRateRequest

type AuthRateRequest struct {
	OperationID string
	Identifier  string
	UserID      identity.UserID
}

AuthRateRequest supplies the canonical operation and actual owning workflow's normalized identifier. Never use an unrelated client-claimed identifier for challenge completion: resolve original challenge custody first. UserID, when present, must come from authenticated current owner custody, not JSON. This input performs no normalization or authentication. Identifiers are 1..1024 UTF-8 bytes without whitespace/control characters; subjects are 1..128 ASCII bytes when present. Neither raw value is a backend key or diagnostic field.

func (AuthRateRequest) Format

func (AuthRateRequest) Format(s fmt.State, _ rune)

Format prevents accidental identifier/subject disclosure.

func (AuthRateRequest) LogValue

func (AuthRateRequest) LogValue() slog.Value

LogValue redacts admission input in structured logging.

func (AuthRateRequest) MarshalJSON

func (AuthRateRequest) MarshalJSON() ([]byte, error)

MarshalJSON rejects implicit export of sensitive admission input.

func (AuthRateRequest) MarshalText

func (AuthRateRequest) MarshalText() ([]byte, error)

MarshalText rejects implicit text/map-key export of sensitive admission input.

type AuthRateResult

type AuthRateResult struct {
	Allowed    bool
	RetryAfter time.Duration
}

AuthRateResult is a request-local observation, never authentication or a reusable clearance. Allowed requires nil error. RetryAfter on a known denial is the longest exhausted bucket delay, rounded up and clamped to 1s..1h.

type CORSConfig

type CORSConfig struct {
	Network *NetworkResolver
	Routes  []CORSRoutePolicy
}

CORSConfig borrows the immutable current-request network owner and copies 0..1024 distinct method/path policies. Empty Routes disables all cross-origin access. The future secure router must bind these to its actual descriptors; this standalone admission wrapper does not validate feature dependencies.

type CORSCredentialMode

type CORSCredentialMode uint8

CORSCredentialMode fixes a route's browser credential profile, never selected from request cookies/headers. Zero is invalid. This is not authentication.

const (
	// CORSNoCredentials never grants browser ambient credentials.
	CORSNoCredentials CORSCredentialMode = iota + 1
	// CORSCookieCredentials grants credentials only to exact configured origins.
	CORSCookieCredentials
	// CORSBearerCredentials never grants ambient credentials and forces no-store,
	// including ordinary requests without Origin. Bearer acquisition is separate.
	CORSBearerCredentials
)

type CORSRoutePolicy

type CORSRoutePolicy struct {
	Method, Path   string
	Credentials    CORSCredentialMode
	Origins        []Origin
	AllowedHeaders []string
	ExposedHeaders []string
	MaxAge         time.Duration
}

CORSRoutePolicy binds one exact method/path to copied CORS policy. Methods are the seven supported uppercase HTTP methods; paths follow Route's exact grammar. Credentials is always explicit. Empty Origins disables CORS for the route and requires empty headers/exposure and zero MaxAge. Otherwise Origins contains 1..128 distinct canonical HTTPS origins, each <=2048 bytes. Header lists contain <=64 distinct case-insensitive HTTP tokens, each <=128 bytes, with no wildcard; exposed Set-Cookie/Set-Cookie2 are forbidden. MaxAge is whole seconds in [0,24h]. No implicit header, method or credential grant.

type CSRFDecoder

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

CSRFDecoder borrows immutable origin/network policy and copies its form name. Zero is invalid; independent requests may share it. It owns no credentials, keys, session state, goroutines or resources. This is input/origin admission, not authentication, token HMAC validation or current-authority admission.

func NewCSRFDecoder

func NewCSRFDecoder(config CSRFDecoderConfig) (*CSRFDecoder, error)

NewCSRFDecoder validates the complete input policy without IO. Selected feature composition must call this profile only after choosing cookie authentication, never infer that mode from attacker-controlled credentials.

type CSRFDecoderConfig

type CSRFDecoderConfig struct {
	Origins   *OriginGuard
	FormField string
}

CSRFDecoderConfig selects only the unsafe cookie-mutation input profile. Origins is a required immutable origin/network owner. FormField is an explicit 1..64-byte lowercase ASCII identifier (letters, digits and underscore), with a letter first; no default or case alias is accepted. Idempotency-key names are reserved. The header channel is exactly X-CSRF-Token. JSON and URL-encoded forms are the only supported bodies; multipart/protocol parsers are separate.

type CSRFError

type CSRFError struct{}

CSRFError denies missing, ambiguous or noncanonical synchronizer input without retaining a credential, field name or owner-parser diagnostic.

func (CSRFError) Error

func (CSRFError) Error() string

Error returns the stable redacted token-channel denial.

type CSRFMode

type CSRFMode uint8

CSRFMode identifies the required protection, not proof it has been enforced.

const (
	CSRFNone CSRFMode = iota + 1
	CSRFExactOrigin
	CSRFSessionOriginAndToken
	CSRFConditionalPreAuthSession
	CSRFBoundOrigin
	// CSRFSessionState requires exact origin for cookie-mode requests and the
	// current session-bound synchronizer token for active sessions. Absent or
	// inactive self-signout needs origin only. Token lifetime follows active
	// session state; no independent token-age bound is claimed.
	CSRFSessionState
	// CSRFVariantOriginSessionState requires exact Origin for the explicitly
	// selected pre-auth signin/signup variant and original stored-session
	// synchronizer plus Origin for enrollment confirmation. No independent token
	// age is minted; the feature must reject mixed or ambiguous branch custody.
	CSRFVariantOriginSessionState
)

type CSRFPolicy

type CSRFPolicy struct {
	Mode            CSRFMode
	TokenHeader     string
	TokenMaximumAge time.Duration
	RefererFallback RefererFallbackPolicy
}

CSRFPolicy is a closed discriminated policy. SessionOriginAndToken and ConditionalPreAuthSession require TokenHeader (1..128 HTTP-token bytes) and TokenMaximumAge (positive, at most 24h). SessionState requires TokenHeader but zero TokenMaximumAge, binding lifetime to active session state instead. VariantOriginSessionState applies that stored-token lifetime only to the explicit enrollment branch; login requires exact Origin. RefererFallback is required except for None, where it must be zero.

type CanonicalOperationConfig

type CanonicalOperationConfig struct {
	Owner                  ModuleID
	OperationID            string
	Handler                http.Handler
	CSRF                   CSRFPolicy
	MaximumSerializedBytes uint64
}

CanonicalOperationConfig binds one trusted feature-owned handler to the embedded canonical direct-contract record. Owner must be accountable for the operation. CSRF selects an actually enforced profile, not a request-selected fallback. Conditional pre-auth/session rows additionally admit ExactOrigin with denied Referer fallback for pre-auth-only handlers that reject session and MFA variants. Self-signout and password-change additionally admit None (explicit bearer) or SessionState (cookie origin plus active-state token). Password change requires an active session; only self-signout admits absent/ inactive origin-only success. Password verification additionally admits None for strict bearer custody or SessionState with a configured valid header, zero independent token age and denied Referer fallback for native protected cookie custody. Factories own enforcement; selection issues no CSRF token. Organization create/update/member-add/member-update/member-remove/member-leave, owner-transfer, archive/restore, team create/update/delete/member-add/member-remove role create/update/delete and invitation send/accept/resend/cancel/reject/expire additionally admit None only for explicitly selected strict bearer handlers that ignore ambient cookies. Owner-transfer retains its native fresh proof. Other organization mutations retain their canonical policy. API-key create/rotate/delete/update additionally admit explicit bearer-only None with keyed custody, matching the native management contracts. Those handlers must reject cookie fallback and require the original Idempotency-Key. MaximumSerializedBytes bounds each schema summary (1..16MiB); it is not response-size enforcement. Handler must be usable/concurrency-safe and own its declared security. Nil handlers and typed-nil HandlerFunc are invalid. No callback is invoked here.

type ChallengeVerificationRateConfig

type ChallengeVerificationRateConfig = PasswordVerificationRateConfig

ChallengeVerificationRateConfig shares the immutable native rate.verify deployment profile with password verification. Use the same namespace/key and borrowed atomic backend to share IP and subject budgets across flows. This alias preserves one owner for policy validation and redacted config.

type ChallengeVerificationRateLimiter

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

ChallengeVerificationRateLimiter owns no store or goroutine. It composes the existing strict native atomic owner; no retry, local fallback or second journal is introduced. Independent calls/Close are safe. Zero is invalid. Each call receives fresh server-side debit correlation, never a client retry key.

func NewChallengeVerificationRateLimiter

func NewChallengeVerificationRateLimiter(c ChallengeVerificationRateConfig) (*ChallengeVerificationRateLimiter, error)

NewChallengeVerificationRateLimiter freezes canonical issued-challenge rate.verify selection without IO or route registration. Password verification stays on its existing no-challenge contract. Core HTTP gains no feature import.

func (*ChallengeVerificationRateLimiter) Admit

Admit atomically counts all three dimensions for an already-authenticated direct caller. Do not call this after AdmitIP for the same attempt. Invalid input denies before dispatch; ambiguity/outage/panic/cancellation/close deny without retry. Known exhaustion retains the longest1s..1h ceiling delay.

func (*ChallengeVerificationRateLimiter) AdmitChallenge

AdmitChallenge atomically counts tenant/subject10/10m and original challenge 10/lifetime after the same attempt's successful AdmitIP and source authentication. It never recounts IP or authenticates client input. A positive result is not reusable clearance; original expiry/current policy and the workflow's stricter protected attempt counters still apply.

func (*ChallengeVerificationRateLimiter) AdmitIP

AdmitIP counts trusted IP20/10m before cryptographic/source lookup work. It does not authorize any operation and must be followed by AdmitChallenge with authenticated original bindings before mutation. The two phases are not one transaction: the IP debit remains after proof rejection or later failure.

func (*ChallengeVerificationRateLimiter) AdmitPasskey

AdmitPasskey counts all three original assertion dimensions atomically for an already custody-authenticated direct caller. Do not call after AdmitIP for the same attempt. Outage, ambiguity, panic, cancellation and closure deny without retry. Only nil error and Allowed permit continuing to native verification.

func (*ChallengeVerificationRateLimiter) AdmitPasskeyChallenge

AdmitPasskeyChallenge applies only the original assertion source10/10m and challenge10/lifetime after this attempt's successful AdmitIP and native custody authentication. Identifier and anonymous-flow buckets have separate HMAC domains; neither impersonates the existing user-subject bucket. No IP recount, source lookup, workflow retry or authentication occurs here.

func (*ChallengeVerificationRateLimiter) AdmitPasskeySignup

AdmitPasskeySignup counts IP and original anonymous signup flow/ceremony dimensions atomically for a custody-authenticated direct caller. Do not call after AdmitIP on the same attempt. Failure denies without retry or fallback; only a nil error and Allowed permit proceeding to native verification.

func (*ChallengeVerificationRateLimiter) AdmitPasskeySignupChallenge

AdmitPasskeySignupChallenge counts only the authenticated anonymous signup flow10/10m and original create ceremony10/lifetime, after this attempt's successful AdmitIP. The original anonymous-flow bucket is shared with signin; ceremonies remain operation-separated. It never recounts IP or verifies proof.

func (*ChallengeVerificationRateLimiter) Close

Close terminally denies calls without closing borrowed storage or undoing prior debits. Drain selected handlers before disposing composition owners.

func (*ChallengeVerificationRateLimiter) Configured

func (l *ChallengeVerificationRateLimiter) Configured() bool

Configured reports nonterminal structural validity, not health or admission.

func (ChallengeVerificationRateLimiter) Format

Format redacts runtime composition.

func (ChallengeVerificationRateLimiter) LogValue

LogValue returns only a constant marker.

func (ChallengeVerificationRateLimiter) MarshalJSON

func (ChallengeVerificationRateLimiter) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit composition export.

func (ChallengeVerificationRateLimiter) MarshalText

func (ChallengeVerificationRateLimiter) MarshalText() ([]byte, error)

MarshalText denies implicit composition export.

type ChallengeVerificationRateRequest

type ChallengeVerificationRateRequest struct {
	OperationID       string
	UserID            identity.UserID
	ChallengeID       string
	ChallengeLifetime time.Duration
}

ChallengeVerificationRateRequest contains authenticated original source bindings, never JSON-selected identity or bearer bytes. UserID and ChallengeID must come from the owning workflow's authenticated, purpose-bound original issuance. ChallengeLifetime is its immutable policy lifetime, not remaining expiry, a renewed interval or an override; integral microseconds in 1s..24h. This subject-bound input retains its existing shape; anonymous passkey signin uses the separately typed PasskeyVerificationRateRequest below. It proves neither authority nor challenge validity. Explicit/private field copies remain sensitive; inputs must remain immutable during the call.

func (ChallengeVerificationRateRequest) Format

Format redacts source bindings.

func (ChallengeVerificationRateRequest) LogValue

LogValue returns only a constant marker.

func (ChallengeVerificationRateRequest) MarshalJSON

func (ChallengeVerificationRateRequest) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit source export.

func (ChallengeVerificationRateRequest) MarshalText

func (ChallengeVerificationRateRequest) MarshalText() ([]byte, error)

MarshalText denies implicit text export.

type CoverageStatus

type CoverageStatus string

CoverageStatus describes selection, never completion or security evidence.

const (
	// CoverageSelectedRouted has one selected descriptor and wire operation.
	CoverageSelectedRouted CoverageStatus = "selected-routed"
	// CoverageUnselectedRouted is a canonical obligation not selected here.
	CoverageUnselectedRouted CoverageStatus = "unselected-routed"
	// CoverageDirectOnly has no HTTP route by contract; implementation is unproven.
	CoverageDirectOnly CoverageStatus = "direct-only"
	// CoverageMiddleware is not a standalone HTTP operation; enforcement is unproven.
	CoverageMiddleware CoverageStatus = "middleware"
	// CoverageSharedVariantUnavailable retains a shared-route variant whose
	// runtime implementation this selected builder cannot currently establish.
	CoverageSharedVariantUnavailable CoverageStatus = "shared-variant-unavailable"
)

type DecodeError

type DecodeError struct{}

DecodeError is a redacted invalid-request classification. It retains no submitted data, field names or underlying reader/codec diagnostics.

func (DecodeError) Error

func (DecodeError) Error() string

Error returns the stable invalid-request identity without submitted data.

type DocumentConfig

type DocumentConfig struct{ Title, Version string }

DocumentConfig contains static public document identity, not runtime hosts, credentials, tenant names or provider configuration. Both strings are printable ASCII (title 1..128, version 1..64). No server URL is inferred.

type Exposure

type Exposure uint8

Exposure identifies canonical transport disposition. Zero is invalid. Routed descriptors accept only Both and Protocol. Direct/middleware coverage belongs to the complete acceptance catalog, not fabricated HTTP handlers.

const (
	ExposureBoth Exposure = iota + 1
	ExposureDirect
	ExposureProtocol
	ExposureMiddleware
)

type Feature

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

Feature is a validated immutable contribution, not a service locator. Its zero value is invalid. Inputs/getters are deeply copied except borrowed handlers, whose concurrent execution/resources remain their feature owner's contract. It can be shared across independently constructed selections without locks.

func NewFeature

func NewFeature(config FeatureConfig) (Feature, error)

NewFeature validates then copies a single contribution without IO or handler invocation. It rejects malformed metadata, missing handlers, local collisions, wrong canonical policies/ownership and non-routed catalog rows. Missing selected dependencies and cross-feature collisions are rejected by selection. Callers must not mutate input slices concurrently with construction.

func NewProbeFeature

func NewProbeFeature(config ProbeConfig) (Feature, error)

NewProbeFeature is the owned opt-in probe contribution. It invokes no checks, selects no domain features and retains reference's accountable readiness ownership. Canonical schema summaries describe direct public contracts; the separate Wire contribution describes the fixed safe handler projection for NewDocumentedHandler. Direct summaries never become response schemas.

func (Feature) ID

func (f Feature) ID() ModuleID

ID returns the contribution identity; a zero feature returns empty.

func (Feature) Operations

func (f Feature) Operations() []OperationDescriptor

Operations returns detached descriptor metadata. Handler values are borrowed, not cloned or closed; mutating a handler's internals is not snapshot mutation.

type FeatureConfig

type FeatureConfig struct {
	ID         ModuleID
	Requires   []ModuleID
	Operations []OperationDescriptor
}

FeatureConfig supplies one feature-owned integration's actual handlers. ID owns its canonical operations or the extension.<ID with slashes replaced by dots> namespace. Requires contains at most 64 distinct other selected IDs. Operations contains 1..1024 routed contributions. Feature factories must validate their actual typed service dependencies before calling NewFeature; dependency IDs alone cannot certify a usable domain service.

type FeatureServices

type FeatureServices struct{ Features []Feature }

FeatureServices selects only the supplied validated contributions. Empty is a valid selection with no routes, probes, schemas or advertised capabilities. No global registry, optional feature imports or implicit default feature exist.

type HTTPServer

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

HTTPServer owns its accepted listener, HTTP connections and cancellation watcher. It does not close feature stores, pools or provider owners. Immutable config and handler may be shared; mu alone owns mutable lifecycle state. Its zero value is invalid. All public methods are safe for concurrent calls.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"time"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	mux := http.NewServeMux()
	server, err := identityhttp.NewHTTPServer(identityhttp.ServerConfig{
		Address: "127.0.0.1:0", ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second,
		WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, HandlerTimeout: 15 * time.Second,
		ShutdownDrain: 30 * time.Second, MaximumHeaderBytes: 65536,
	}, mux)
	if err != nil {
		panic(err)
	}
	live, ready, err := identityhttp.NewProbeHandlers(identityhttp.ProbeConfig{
		State: server.State, Timeout: time.Second,
		Readiness: identityhttp.ReadinessProvider{Check: func(ctx context.Context) (identityhttp.ReadinessSnapshot, error) {
			// This lifecycle-only example selects no domain dependencies. A real
			// composition must check every selected store, migration and key.
			return identityhttp.ReadinessSnapshot{Ready: true}, ctx.Err()
		}},
	})
	if err != nil {
		panic(err)
	}
	mux.Handle("/healthz", live)
	mux.Handle("/readyz", ready)
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}
	done := make(chan error, 1)
	go func() { done <- server.Serve(context.Background(), listener) }()
	<-server.Started()
	if err := server.Shutdown(context.Background()); err != nil {
		panic(err)
	}
	if err := <-done; err != nil {
		panic(err)
	}
	fmt.Println(server.State() == identityhttp.ServerStopped)
}
Output:
true

func NewHTTPServer

func NewHTTPServer(config ServerConfig, handler http.Handler) (*HTTPServer, error)

NewHTTPServer validates and copies config without IO. A nil interface or nil http.HandlerFunc is rejected. Other implementations must be usable non-nil handlers; their internal validity is the caller's contract. The server does not reflect into arbitrary handler values. Handlers must not hijack connections or start detached work; the response writer deliberately exposes no Hijacker.

func (*HTTPServer) Address

func (s *HTTPServer) Address() string

Address returns the bound local address after listener acquisition, or empty before it. It remains available after stop for explicit operator inspection; callers must not expose it in public probes or error envelopes.

func (*HTTPServer) Serve

func (s *HTTPServer) Serve(ctx context.Context, ln net.Listener) error

Serve is Start with a deployment-supplied listener (including TLS). Ownership transfers only after argument validation and successful start reservation; on ErrInvalidInput/ErrLifecycle or an already cancelled context the caller retains it. The accepted listener is always closed, including serving failure. An implementation must honor Close and return promptly from a closed Accept.

func (*HTTPServer) Shutdown

func (s *HTTPServer) Shutdown(ctx context.Context) error

Shutdown is terminal even before start. It refuses new admission immediately and drains with the earlier of ctx's deadline and ShutdownDrain. Concurrent callers wait under their own same bound; repeated calls return the original shutdown result. On deadline/cancellation it cancels requests and force-closes connections. Cooperative handlers must release owned work on cancellation; Go cannot terminate arbitrary caller code that ignores it. Neither cancellation nor connection closure proves an authoritative mutation rolled back.

func (*HTTPServer) Start

func (s *HTTPServer) Start(ctx context.Context) error

Start binds the configured address and blocks until serving and owned cleanup finish. Bind failure is terminal and sanitized as ErrUnavailable. A cancelled context before start leaves the owner reusable; cancellation after reservation shuts it down and returns the context error. Ordinary Shutdown returns nil.

func (*HTTPServer) Started

func (s *HTTPServer) Started() <-chan struct{}

Started closes once a listener is acquired and admission begins. It never closes after a failed/pre-cancelled start or pre-start shutdown; consumers must also observe Start/Serve's return. Zero owners return a nil channel.

func (*HTTPServer) State

func (s *HTTPServer) State() ServerState

State returns an instantaneous lifecycle snapshot; zero owners return zero.

type HeaderRequirement

type HeaderRequirement uint8

HeaderRequirement declares whether a route forbids, permits or requires a credential header. Zero forbids it. Unknown values are invalid configuration. Optional idempotency is reserved for protocol rows explicitly permitting it; the router must derive this policy from the selected operation, not the client.

const (
	// HeaderForbidden rejects presence, including an empty value.
	HeaderForbidden HeaderRequirement = iota
	// HeaderOptional accepts either absence or one valid header.
	HeaderOptional
	// HeaderRequired requires exactly one valid header.
	HeaderRequired
)

type KeyedSecretRateConfig

type KeyedSecretRateConfig struct {
	Shared          ratelimit.StrictAtomicBatchBackend
	Namespace       string
	Key             [32]byte
	Now             func() time.Time
	MaximumDuration time.Duration
}

KeyedSecretRateConfig selects shared fixed rate.secret buckets for canonical keyed and original-command operations. Shared must be a concurrency-safe native atomic owner (PostgreSQL requires its additive atomic schema); no per-item fallback exists. Shared and prompt concurrency-safe Now remain borrowed and must outlive calls. Namespace/Key are immutable deployment rate identity, not credential keys. Do not silently rotate them: doing so resets budgets and changes original debit-command correlation. MaximumDuration is 1ms..2s; earlier caller deadlines win. Constructors perform no admission/storage IO.

func (KeyedSecretRateConfig) Format

func (KeyedSecretRateConfig) Format(s fmt.State, _ rune)

Format redacts runtime key/callback/backend configuration for every verb.

func (KeyedSecretRateConfig) LogValue

func (KeyedSecretRateConfig) LogValue() slog.Value

LogValue redacts deployment configuration.

func (KeyedSecretRateConfig) MarshalJSON

func (KeyedSecretRateConfig) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit configuration export.

func (KeyedSecretRateConfig) MarshalText

func (KeyedSecretRateConfig) MarshalText() ([]byte, error)

MarshalText denies implicit configuration text/map-key export.

type KeyedSecretRateLimiter

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

KeyedSecretRateLimiter enforces all applicable buckets through one explicit atomic dispatch and native retained outcome. It adds no counter algorithm, workflow journal, local fallback, automatic retry or goroutine. Share one owner across routes and requests. Independent calls/Close are safe; zero is invalid. Rate admission neither grants authentication/authorization nor bypasses the protected workflow's cumulative risk/abuse policy.

func NewKeyedSecretRateLimiter

func NewKeyedSecretRateLimiter(c KeyedSecretRateConfig) (*KeyedSecretRateLimiter, error)

NewKeyedSecretRateLimiter freezes canonical keyed rate.secret selection. Every request requires trusted IP10/10m and tenant/subject10/10m; an original challenge adds5/lifetime. Buckets span selected operations, not per-route copies. Construction selects no route, advertised feature or HTTP metadata.

func (*KeyedSecretRateLimiter) Admit

Admit performs one bounded atomic debit, never protected domain work. Same original key bytes/tenant/subject/operation preserve command identity; changed IP/challenge/policy is compared against the native original intent, not given a fresh command. Unknown outcomes reconcile only through the same input/key. Original challenge dimensions are caller-owned trusted facts, not overrides. Known denial returns ErrRateLimited and the longest exhausted delay, ceiling seconds clamped1..3600. Invalid input or an already closed/nil/zero owner denies before dispatch with ErrInvalidInput. Operational, ambiguous, contradictory, panicking, canceled or closure-after-dispatch paths fail closed with ErrRateUnavailable, never raw backend diagnostics. Cancellation after admission may leave a known debit: failure is not refund/rollback evidence. Hooks/domain work must separately honor the original cancellation and risk.

func (*KeyedSecretRateLimiter) AdmitInspectionIP

func (l *KeyedSecretRateLimiter) AdmitInspectionIP(ctx context.Context, facts NetworkFacts, operation string) (AuthRateResult, error)

AdmitInspectionIP charges the canonical secret IP10/10m bucket before any cryptographic or primary-source lookup for a repeatable secret inspection. Each invocation gets a fresh native debit identity; client retry keys are not accepted. Earlier deadlines win, unknown/closed/cancelled results deny, and no authority or ownership is established. Follow successful admission with AdmitInspectionSubject using the authenticated original source, not body IDs.

func (*KeyedSecretRateLimiter) AdmitInspectionSubject

AdmitInspectionSubject atomically charges subject10/10m and original challenge5/lifetime after the same attempt's successful AdmitInspectionIP. The shared challenge-source metadata shape is reused, not rate.verify limits. Signed lifetime is unchanged, never remaining expiry. Each read gets fresh debit correlation: inspection replay must not become a free cached read. No IP recount, proof consumption, local fallback or automatic retry occurs.

func (*KeyedSecretRateLimiter) AdmitOriginalCommand

AdmitOriginalCommand enforces the same shared secret buckets for canonical one-time operations. Original command/tenant/subject/operation retain debit identity across retry. Changed network/challenge inputs must reconcile with the atomic backend's original intent, not receive a successor debit command. It performs no challenge authentication, reservation or domain mutation.

func (*KeyedSecretRateLimiter) Close

func (l *KeyedSecretRateLimiter) Close() error

Close terminally denies new calls without closing borrowed storage. It is idempotent. Drain handlers before releasing dependencies; committed debits are not undone, and previously returned admission is not revoked.

func (*KeyedSecretRateLimiter) Configured

func (l *KeyedSecretRateLimiter) Configured() bool

Configured reports structural, observed nonterminal owner validity without IO. It is not health, authority or admission; Close may race afterward.

func (KeyedSecretRateLimiter) Format

func (KeyedSecretRateLimiter) Format(s fmt.State, _ rune)

Format prevents traversal of retained keys and borrowed owners.

func (KeyedSecretRateLimiter) LogValue

func (KeyedSecretRateLimiter) LogValue() slog.Value

LogValue redacts retained runtime composition.

func (KeyedSecretRateLimiter) MarshalJSON

func (KeyedSecretRateLimiter) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit runtime composition export.

func (KeyedSecretRateLimiter) MarshalText

func (KeyedSecretRateLimiter) MarshalText() ([]byte, error)

MarshalText denies implicit runtime composition text export.

type KeyedSecretRateRequest

type KeyedSecretRateRequest struct {
	OperationID       string
	UserID            identity.UserID
	IdempotencyKey    identity.IdempotencyKey
	ChallengeID       string
	ChallengeLifetime time.Duration
}

KeyedSecretRateRequest borrows exactly the owning operation's original key. UserID must be the current authenticated/capability subject, never a claimed body field. Optional ChallengeID and ChallengeLifetime must come together from the original challenge owner; lifetime is the immutable original policy, not remaining expiry or a request-selected duration. This input authenticates none of those facts and is not transport parsing. It has no alternate key. Non-keyed operations are unsupported: they require their own stable native debit correlation and must not manufacture an Idempotency-Key header.

func (KeyedSecretRateRequest) Format

func (KeyedSecretRateRequest) Format(s fmt.State, _ rune)

Format redacts original subject, key and challenge custody.

func (KeyedSecretRateRequest) LogValue

func (KeyedSecretRateRequest) LogValue() slog.Value

LogValue redacts original admission input.

func (KeyedSecretRateRequest) MarshalJSON

func (KeyedSecretRateRequest) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit request export.

func (KeyedSecretRateRequest) MarshalText

func (KeyedSecretRateRequest) MarshalText() ([]byte, error)

MarshalText denies implicit request text/map-key export.

type Limits

type Limits struct {
	MaximumHeaderBytes  uint32
	MaximumURLBytes     uint32
	MaximumBodyBytes    uint64
	MaximumDecodedBytes uint64
	MaximumFields       uint32
	MaximumDepth        uint32
}

Limits bounds untrusted ordinary HTTP input. Header bytes are 1024..65536, URL bytes 1024..8192, raw/decoded bytes 1024..1048576, fields 1..1000 and nesting depth 1..32. Every field is explicit; zero is invalid. Header limits here cover net/http's parsed representation, not original normalized wire bytes; the server must also enforce its raw header and read deadlines.

type MetadataConfig

type MetadataConfig struct {
	Public          bool
	Network         NetworkConfig
	Rate            *SafeRateLimiter
	MaximumDuration time.Duration
}

MetadataConfig explicitly opts into public GET /openapi.json. Public must be true; zero does not select exposure. This profile does not authenticate administrators or implement protected metadata: use no metadata endpoint until that deployment's protected-router composition is available. Network policy is copied; Rate is borrowed and shared with other safe routes. MaximumDuration is 1ms..30s and never extends an incoming deadline. Stores must honor it. A server additionally owns bounded response streaming.

type Middleware

type Middleware struct {
	Name    string
	Phase   MiddlewarePhase
	Order   uint32
	Handler MiddlewareFunc
}

Middleware names one wrapper at a fixed phase and order. Name contains 1..128 ASCII letters, digits, underscores, dots or hyphens; Order is 1..10000. An equal order within a phase is rejected, not resolved using incidental slice order.

type MiddlewareDescriptor

type MiddlewareDescriptor struct {
	Owner      ModuleID
	Middleware Middleware
}

MiddlewareDescriptor attributes a wrapper to a validated ModuleID. The owner/name pair must be unique across phases. Attribution is not authority.

type MiddlewareFunc

type MiddlewareFunc func(http.Handler) http.Handler

MiddlewareFunc constructs a trusted, synchronous wrapper once. It must not perform IO during construction, retain requests, start goroutines or unwrap response writers. Calling next is optional; repeated calls cannot repeat an owned stage. Returned handlers must support concurrent independent requests.

type MiddlewarePhase

type MiddlewarePhase uint8

MiddlewarePhase identifies a real execution boundary, not a metadata claim.

const (
	// MiddlewareBeforeAuth runs after Decode, before credential validation.
	MiddlewareBeforeAuth MiddlewarePhase = iota + 1
	// MiddlewareAfterAuth runs after Authenticate, before Admit.
	MiddlewareAfterAuth
	// MiddlewareBeforeHandler runs after Admit, before the protected operation.
	MiddlewareBeforeHandler
	// MiddlewareAfterHandler observes a normally returned operation attempt.
	// It cannot change the response and does not imply a committed transaction.
	MiddlewareAfterHandler
)

type ModuleID

type ModuleID string

ModuleID is an explicit case-sensitive feature or accountable module ID. IDs contain 1..128 lowercase ASCII letters, digits, dots, dashes and slashes; separators cannot be leading, trailing or adjacent. IDs confer no authority.

type NetworkConfig

type NetworkConfig struct {
	Limits               Limits
	TrustedProxyCIDRs    []netip.Prefix
	MaximumForwardedHops uint8
	TenantHosts          []TenantHost
	PublicHosts          []string
}

NetworkConfig is copied by NewNetworkResolver. Limits are all explicit. MaximumForwardedHops is 1..16 (reference composition selects 8). At most 128 distinct canonical masked proxy prefixes and 1024 total exact host bindings are accepted. Empty prefixes trust none; overlaps are an explicit set union. At least one tenant or public binding is required. Public and tenant hosts must not overlap; there is no suffix, wildcard or unknown-host fallback. Reloading means constructing/replacing a complete validated snapshot.

type NetworkFacts

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

NetworkFacts is an immutable resolved snapshot, not an authentication proof. Zero cannot admit a realm. Only Resolve constructs a valid value; copies preserve it. Ordinary formatting/logging is redacted and serialization denied. Explicit getters disclose validated network/tenant metadata to trusted code. Facts do not bind an arbitrary future request: routing must resolve once for its own request and must not accept caller-submitted or cached facts as input.

func (NetworkFacts) Authority

func (f NetworkFacts) Authority() string

Authority returns canonical host and any explicit validated decimal port.

func (NetworkFacts) ClientIP

func (f NetworkFacts) ClientIP() netip.Addr

ClientIP returns the full canonical unicast address; mapped IPv6 is unmapped.

func (NetworkFacts) Format

func (NetworkFacts) Format(s fmt.State, _ rune)

Format redacts network and tenant metadata for every diagnostic verb.

func (NetworkFacts) Forwarded

func (f NetworkFacts) Forwarded() bool

Forwarded reports successful trusted-chain selection, not arbitrary headers.

func (NetworkFacts) Host

func (f NetworkFacts) Host() string

Host returns the canonical exact host-map key without a port.

func (NetworkFacts) LogValue

func (NetworkFacts) LogValue() slog.Value

LogValue redacts network and tenant metadata in ordinary structured logs.

func (NetworkFacts) MarshalJSON

func (NetworkFacts) MarshalJSON() ([]byte, error)

MarshalJSON rejects implicit projection of internal network/tenant facts.

func (NetworkFacts) MarshalText

func (NetworkFacts) MarshalText() ([]byte, error)

MarshalText rejects implicit text/map-key projection of internal facts.

func (NetworkFacts) Origin

func (f NetworkFacts) Origin() string

Origin returns canonical effective origin, omitting the scheme's default port.

func (NetworkFacts) Realm

func (f NetworkFacts) Realm() NetworkRealm

Realm returns an explicit public or tenant discriminator, or zero if invalid.

func (NetworkFacts) Scheme

func (f NetworkFacts) Scheme() string

Scheme returns http/https selected from actual TLS or the trusted boundary.

func (NetworkFacts) TenantID

func (f NetworkFacts) TenantID() identity.TenantID

TenantID returns the mapped tenant only for RealmTenant; public/zero is empty.

func (NetworkFacts) Valid

func (f NetworkFacts) Valid() bool

Valid reports successful resolution, not authentication or future validity.

type NetworkRealm

type NetworkRealm uint8

NetworkRealm distinguishes an explicitly allowed public host from a tenant host. Zero is invalid; absence of a tenant never implies public admission.

const (
	// RealmPublic admits only an explicitly allowlisted public host.
	RealmPublic NetworkRealm = iota + 1
	// RealmTenant admits only a host bound to one configured tenant.
	RealmTenant
)

type NetworkResolver

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

NetworkResolver owns immutable copied deployment policy. Zero is invalid. Independent requests may resolve concurrently; callers must not mutate a request during resolution. No DNS, body reads, callbacks or goroutines occur.

func NewNetworkResolver

func NewNetworkResolver(config NetworkConfig) (*NetworkResolver, error)

NewNetworkResolver validates/copies one complete policy without IO. Invalid bounds, noncanonical prefixes/hosts, duplicate/overlapping host bindings and invalid tenants return redacted ErrInvalidInput. Prefix overlap is harmless explicit union; duplicate or IPv4-mapped prefixes are rejected as ambiguous. Host normalization reuses identifier's fixed Unicode15.1 nontransitional UTS46 implementation under identity.http.host.uts46-15.1/version1.

Example
package main

import (
	"fmt"
	"net/http/httptest"
	"net/netip"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	resolver, err := identityhttp.NewNetworkResolver(identityhttp.NetworkConfig{
		Limits: identityhttp.Limits{
			MaximumHeaderBytes: 8192, MaximumURLBytes: 2048,
			MaximumBodyBytes: 65536, MaximumDecodedBytes: 65536,
			MaximumFields: 100, MaximumDepth: 16,
		},
		MaximumForwardedHops: 8,
		TrustedProxyCIDRs:    []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")},
		TenantHosts:          []identityhttp.TenantHost{{Host: "tenant.example", TenantID: "tenant-a"}},
		PublicHosts:          []string{"login.example"},
	})
	if err != nil {
		panic("invalid deployment policy")
	}
	request := httptest.NewRequest("GET", "/resource", nil)
	request.RemoteAddr = "10.0.0.3:1234"
	request.Header.Set("Forwarded", "for=198.51.100.7;host=tenant.example;proto=https")
	facts, err := resolver.Resolve(request)
	if err != nil {
		panic("request denied")
	}
	// Deliberately disclose only selected validated fields, not raw requests.
	fmt.Println(facts.Origin(), facts.Realm() == identityhttp.RealmTenant)
}
Output:
https://tenant.example true

func (*NetworkResolver) Resolve

func (n *NetworkResolver) Resolve(r *http.Request) (NetworkFacts, error)

Resolve bounds/validates the parsed request envelope before deriving facts. An untrusted socket peer ignores all forwarding semantics and uses Host/TLS. A trusted peer requires one Forwarded field/value, fully validated under the closed RFC7239 subset (at most 16 parameters per element, concrete for/by, no empty pairs/elements or whitespace around '=', unique case-insensitive names, token/quoted values; unknown extensions are syntax-only), then walks right-to-left to the first untrusted for-address. Its trusted sender's element supplies BOTH host/proto; no direct fallback or left-of-boundary field borrowing is permitted. X-Forwarded-* never supplies facts. Invalid peer/chain or exhausted trusted chain returns ErrUnavailable; invalid envelope/unknown host returns DecodeError. Cancellation preserves the context error. Every failure returns zero facts. RemoteAddr/Host/TLS/header custody belongs to server/trusted composition: arbitrary in-process requests or middleware mutations cannot prove provenance.

type OpenAPIDocument

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

OpenAPIDocument owns deterministic validated 3.1.1 JSON and the matching selected operation snapshot. Zero has no document. No endpoint is registered implicitly. Metadata exports do not confer authentication or authority.

func NewDocumentedHandler

func NewDocumentedHandler(ctx context.Context, config DocumentConfig, services FeatureServices) (http.Handler, OpenAPIDocument, error)

NewDocumentedHandler freezes exact selected dispatch and concrete OpenAPI from the same immutable feature selection. Every selected route must supply its real wire definition; direct summaries are never rendered as schemas. Invalid/missing definitions, duplicate components, unresolved/external references or normative validation failures return no partial result. Empty selection has empty paths/components and no advertised capabilities.

Construction invokes no handlers, stores, clocks or network IO. The owned OpenAPI engine applies bounded strict parsing and normative/schema validation; a five-second construction budget never extends ctx. Nil/canceled ctx denies. Canonical dotted operation IDs are retained even though the OpenAPI owner advises portable programming identifiers; generated clients must map them. All other validation diagnostics deny, and a saturated diagnostic budget denies rather than allowing truncation to hide errors. Schema references use internal JSON pointers only; externalValue, $id and $dynamicRef are excluded from this self-contained profile. Errors redact descriptions and schema diagnostics. The 16MiB document cap, depth64 and finite node/component limits apply before unbounded allocation. Inputs must not change during construction; exports and dispatch are safe for concurrent use. This does not replace the complete NewRouter/security-policy builder: existing real feature handlers retain enforcement and bounded IO.

Example
package main

import (
	"context"
	"fmt"
	"time"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	feature, err := identityhttp.NewProbeFeature(identityhttp.ProbeConfig{
		State:   func() identityhttp.ServerState { return identityhttp.ServerRunning },
		Timeout: time.Second,
		Readiness: identityhttp.ReadinessProvider{Check: func(context.Context) (identityhttp.ReadinessSnapshot, error) {
			return identityhttp.ReadinessSnapshot{Ready: true}, nil
		}},
	})
	if err != nil {
		panic(err)
	}
	handler, document, err := identityhttp.NewDocumentedHandler(context.Background(), identityhttp.DocumentConfig{Title: "Identity", Version: "1"}, identityhttp.FeatureServices{Features: []identityhttp.Feature{feature}})
	if err != nil {
		panic(err)
	}
	fmt.Println(handler != nil, len(document.Operations()))
}
Output:
true 2

func NewDocumentedMetadataHandler

func NewDocumentedMetadataHandler(ctx context.Context, config DocumentConfig, metadata MetadataConfig, services FeatureServices) (http.Handler, OpenAPIDocument, error)

NewDocumentedMetadataHandler explicitly selects the canonical metadata operation alongside services and returns their one validated immutable document and matching dispatcher. Existing constructors remain metadata-off. The export includes itself exactly once; collisions/missing dependencies fail atomically. Construction performs no IO or calls to feature handlers.

Only exact HTTPS GET /openapi.json on an allowlisted public or tenant host is accepted, with no query/body/trailer/Idempotency-Key or Authorization header. Cookies confer no authority and are ignored. Trusted network facts feed the shared canonical rate.safe limiter once; malformed requests never debit it. Success serves exact JSON(), never a runtime-derived wrapper or revalidation. Errors are fixed no-store/nosniff JSON: 400 malformed, 421 unknown host, 429 exhausted, 503 unavailable/canceled. Dispatcher additionally owns 404/405. No automatic HEAD, CORS, cache validators, cookies or coverage-report endpoint are added. The separate Coverage export retains disabled obligations, while wire paths/components contain only selected operations. This remains a selected public profile, not the complete NewRouter/security composition.

func NewRouter

func NewRouter(policy TransportPolicy, operations []OperationDescriptor, middleware []MiddlewareDescriptor) (http.Handler, OpenAPIDocument, error)

NewRouter validates and freezes exact selected routing, shared transport policy, feature-owned middleware phases and matching OpenAPI 3.1.1. Obtain operations from NewOperationDescriptors, which validates selected feature dependencies; this constructor revalidates every descriptor and collision. Empty selection exposes nothing. Invalid policy/wire/metadata or unsupported middleware binding returns no handler/document. No request, authentication, readiness, store or network work occurs during construction. Trusted wrapper factories may run once per operation only after static validation/document validation; their own frozen-pipeline conflicts may still reject construction.

Runtime order is bounded envelope/network/HTTPS -> route-specific CORS -> exact dispatch -> real feature Decode/authentication/admission/domain phases. Preflight never enters feature handlers or hooks. Middleware is appended via OperationMiddlewareBinder, never wrapped around fictitious auth boundaries; nonempty middleware with an opaque handler is rejected. The selected owner must actually enforce its advertised authentication, rate and CSRF controls: metadata/arbitrary caller handlers are not evidence of those decisions.

Responses retain no-store, nosniff and no-referrer through supported write, flush and ReaderFrom paths. CORS retains its separate grant/Vary custody; cookie emission remains with the feature serializer. Trusted code must not unwrap/hijack writers or retain request/writer state. Panics before commitment yield redacted 503; after commitment they abort with http.ErrAbortHandler. Requests are cloned, raw bodies are bounded but not pre-read or closed, and context deadlines never extend upstream bounds. Server raw read/write and connection deadlines remain required: use NewHTTPServer. Arbitrary callbacks ignoring cancellation cannot be forcibly terminated; no detached work is started. Before any response commitment, a canceled owner may synchronously return one explicit 503 outcome of at most 16KiB; only content type, retry and CORS headers survive, never credentials or trailers. Other canceled writes deny; already committed responses abort. This preserves known command status without retrying work or asserting that a disconnected client received it. Returned snapshots are concurrent-safe when borrowed handlers are.

Example
package main

import (
	"fmt"
	"time"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	operations, err := identityhttp.NewOperationDescriptors(identityhttp.FeatureServices{})
	if err != nil {
		panic(err)
	}
	handler, document, err := identityhttp.NewRouter(identityhttp.TransportPolicy{
		Network: identityhttp.NetworkConfig{PublicHosts: []string{"login.example"}, MaximumForwardedHops: 8,
			Limits: identityhttp.Limits{MaximumHeaderBytes: 32768, MaximumURLBytes: 4096, MaximumBodyBytes: 65536,
				MaximumDecodedBytes: 65536, MaximumFields: 128, MaximumDepth: 16}},
		HandlerTimeout: time.Second, Document: identityhttp.DocumentConfig{Title: "Identity", Version: "1"},
	}, operations, nil)
	if err != nil {
		panic(err)
	}
	// Empty selection exposes no routes, probes or metadata endpoint. Real
	// applications explicitly select owned features before constructing a server.
	fmt.Println(handler != nil, document.Version(), len(document.Operations()))
}
Output:
true 3.1.1 0

func (OpenAPIDocument) Coverage

func (d OpenAPIDocument) Coverage() []OperationCoverage

Coverage returns a detached operation-ID-sorted selection report retaining every canonical row, including non-HTTP obligations. Zero returns no rows. Concurrent exports are safe. This is not a unit-completion certificate.

func (OpenAPIDocument) CoverageJSON

func (d OpenAPIDocument) CoverageJSON() []byte

CoverageJSON returns deterministic detached JSON for Coverage, or nil for zero. It includes unselected baseline names; do not mistake this diagnostic inventory for enabled feature metadata or automatically publish it.

func (OpenAPIDocument) Digest

func (d OpenAPIDocument) Digest() [32]byte

Digest identifies the exact exported JSON snapshot; zero returns zero. This content identity is not a signature, authority or release certificate.

func (OpenAPIDocument) JSON

func (d OpenAPIDocument) JSON() []byte

JSON returns detached document bytes; callers own the returned storage.

func (OpenAPIDocument) OpenapiDocument

OpenapiDocument exports this previously constructed snapshot without IO, callbacks, rate debit or mutation. Holding the snapshot grants static export access, not permission to expose an HTTP endpoint. A zero document or nil context is invalid; cancellation retains its standard context identity. This direct in-process static export has no client-initiated rate policy.

func (OpenAPIDocument) Operations

func (d OpenAPIDocument) Operations() []OperationDescriptor

Operations returns detached matching metadata, borrowing only handlers.

func (OpenAPIDocument) Schemas

func (d OpenAPIDocument) Schemas() []WireComponent

Schemas returns detached named wire components in name order. Inline schemas remain in JSON; canonical direct-contract summaries are deliberately excluded.

func (OpenAPIDocument) Version

func (d OpenAPIDocument) Version() string

Version returns exactly 3.1.1, or empty for a zero document.

type OpenAPISchema

type OpenAPISchema struct {
	Name                   string
	Schema                 TypedSchema
	MaximumSerializedBytes uint64
}

OpenAPISchema declares one globally unique component name (1..128 ASCII identifier bytes), its typed identity, and positive serialized limit <=16MiB. Schema material/rendering remains owned by the later OpenAPI builder.

type OpenapiDocumentRequest

type OpenapiDocumentRequest struct{}

OpenapiDocumentRequest has no input. Export cannot select hosts, tenants, credentials, feature sets or another snapshot from request data.

type OpenapiDocumentResult

type OpenapiDocumentResult struct {
	Document OpenAPIDocument
	Digest   [32]byte
}

OpenapiDocumentResult preserves the exact immutable document and its digest. Accessors return detached storage; no authentication authority is conveyed.

type OperationCoverage

type OperationCoverage struct {
	OperationID           string         `json:"operationID"`
	Exposure              Exposure       `json:"exposure"`
	Status                CoverageStatus `json:"status"`
	RegisteredOperationID string         `json:"registeredOperationID,omitempty"`
	Extension             bool           `json:"extension,omitempty"`
}

OperationCoverage is one canonical obligation or selected namespaced extension. Only selected routed rows have RegisteredOperationID. Baseline rows do not advertise enabled capabilities, direct implementation, middleware enforcement or full parity. These records belong to the separate diagnostic coverage export, never the served OpenAPI document. Values own no resources.

type OperationDescriptor

type OperationDescriptor struct {
	Record  OperationRecord
	Route   Route
	Handler http.Handler
	Schemas []OpenAPISchema
	// Wire is separate from canonical direct-contract summaries. Nil retains
	// compatibility for undisclosed routes but cannot enter documented composition.
	Wire *WireOperation
}

OperationDescriptor pairs metadata with a real feature-owned handler and 3..64 component declarations including its request/result/error summaries. Handlers must be usable concurrent-safe values; typed-nil HandlerFunc is rejected, other implementation validity is the feature owner's contract. Do not serve a descriptor directly as an identity endpoint: it is input to the future security router, not a replacement for authentication/rate/CSRF work.

func NewCanonicalOperation

func NewCanonicalOperation(c CanonicalOperationConfig) (OperationDescriptor, error)

NewCanonicalOperation constructs a detached descriptor from owned catalog data, avoiding feature-maintained policy/digest copies. Unknown, direct-only, middleware, shared-token variant and incompatible owner/policy inputs deny atomically. The three schema summaries bind DIRECT public contracts, not HTTP wire schemas or rendered OpenAPI. Construction is side-effect-free and does not certify that arbitrary caller handlers enforce authentication or policy; use the owned feature factory, not a placeholder handler for identity routes.

func NewOperationDescriptors

func NewOperationDescriptors(services FeatureServices) ([]OperationDescriptor, error)

NewOperationDescriptors validates a complete selected dependency graph, then returns detached canonical-ID-sorted descriptors. Limits are 64 features, 1024 aggregate operations and 4096 aggregate components. Identical as well as conflicting duplicate IDs/routes/components reject atomically. Cycles/missing dependencies reject independently of input order; zero Feature values reject. An empty feature list is valid and selects nothing. It performs no IO/callbacks and returns no partial result on error.

Device-token is a token-endpoint request variant, not a standalone descriptor; it is rejected here until the owned token dispatcher/variant composition is implemented. Full direct/middleware coverage and security/OpenAPI composition remain distinct required work, not inferred from this selected routed list.

Example
package main

import (
	"context"
	"fmt"
	"time"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	operations, err := identityhttp.NewOperationDescriptors(identityhttp.FeatureServices{})
	if err != nil {
		panic(err)
	}
	fmt.Println(len(operations))
	probes, err := identityhttp.NewProbeFeature(identityhttp.ProbeConfig{
		State:   func() identityhttp.ServerState { return identityhttp.ServerStarting },
		Timeout: time.Second,
		Readiness: identityhttp.ReadinessProvider{Check: func(ctx context.Context) (identityhttp.ReadinessSnapshot, error) {
			// No dependencies are selected in this construction-only example.
			return identityhttp.ReadinessSnapshot{Ready: true}, ctx.Err()
		}},
	})
	if err != nil {
		panic(err)
	}
	operations, err = identityhttp.NewOperationDescriptors(identityhttp.FeatureServices{Features: []identityhttp.Feature{probes}})
	if err != nil {
		panic(err)
	}
	for _, operation := range operations {
		fmt.Println(operation.Record.OperationID, operation.Route.Path)
	}
}
Output:
0
identity.health /healthz
identity.readiness /readyz

type OperationMiddlewareBinder

type OperationMiddlewareBinder interface {
	http.Handler
	WithOperationMiddleware([]MiddlewareDescriptor) (http.Handler, error)
}

OperationMiddlewareBinder exposes an already constructed feature's actual execution phases to selected transport composition. Implementations retain the same immutable policy and borrowed domain owners, append to existing hooks and validate combined ownership/order before constructing wrappers. They must return an independent concurrent-safe handler, perform no request, store or network work, and preserve all response protections. This is a trusted integration contract, not proof that an arbitrary handler is secure. Construction must not race mutation of supplied descriptors. Existing handlers remain usable and unchanged; binding never transfers resource ownership.

type OperationPipelineConfig

type OperationPipelineConfig[T any] struct {
	MaximumDuration time.Duration
	Middleware      []MiddlewareDescriptor
	Decode          func(*http.Request) (T, error)
	Authenticate    func(*http.Request, T) (T, error)
	Admit           func(*http.Request, T) error
	Handle          func(http.ResponseWriter, *http.Request, T)
	Reject          func(http.ResponseWriter, *http.Request, error)
}

OperationPipelineConfig exposes feature-owned execution stages to transport composition without importing optional implementations or repeating their security work. Every callback is required, including explicit public/no-auth decisions. Decode owns bounded input acquisition; Authenticate acquires the owner's request-scoped identity exactly once; Admit owns authorization, rate, origin and transactional CSRF admission appropriate to that operation. Handle owns the domain call, response and ambiguous outcome semantics. Reject maps errors to a redacted response; it must never expose raw diagnostics.

T is a feature's typed per-request state, never a package-global principal or caller-supplied authorization proof. Callbacks and middleware are trusted concurrency-safe borrowed code, must honor the request context and must not retain requests/state or start detached work. MaximumDuration is 1ms..30s; the server separately owns raw IO and response streaming bounds. This seam alone is not NewRouter, network admission, a decoder or an authentication implementation; real feature integrations must supply their existing owners.

type OperationRecord

type OperationRecord struct {
	OperationID                              string
	Exposure                                 Exposure
	Owners                                   []ModuleID
	Access                                   string
	Authorization                            RouteAuthorization
	CSRFOrigin                               CSRFPolicy
	RiskClass                                string
	RatePolicy                               RatePolicyID
	Idempotency, EventSemantics              string
	RequestSchema, ResultSchema, ErrorSchema TypedSchema
	HandlerOwner                             ModuleID
	Method, Path, OpenAPIOperationID         string
}

OperationRecord is selected operation metadata. Owners contains 1..16 unique module IDs with the accountable owner first. Admission limits are 128 bytes for operation identifiers, 32 for risk/idempotency and 4096 for event semantics. Canonical records must match the pinned catalog; extension IDs are namespaced. This is neither full canonical coverage nor evidence of security enforcement.

type Origin

type Origin string

Origin is a canonical HTTPS origin, not a URL or a CSRF token. In this profile it has a lowercase ASCII/IDNA host, no default port, path, query, fragment or userinfo. Conversion alone does not validate an Origin.

type OriginError

type OriginError struct{}

OriginError classifies malformed, missing, ambiguous or untrusted origin evidence without disclosing submitted input or the configured allowlist.

func (OriginError) Error

func (OriginError) Error() string

Error returns the stable redacted origin-denial classification.

type OriginGuard

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

OriginGuard owns copied immutable origin policy and borrows an immutable NetworkResolver. Zero is invalid. Independent requests may share it. No body, DNS, callback, authentication, token consumption or domain work occurs. This is only origin admission: cookie mutations still require session-bound CSRF tokens, and callbacks require their protocol/continuation bindings.

func NewOriginGuard

func NewOriginGuard(config OriginGuardConfig) (*OriginGuard, error)

NewOriginGuard validates and copies one complete policy without IO. Invalid network owners, bounds, noncanonical/duplicate origins or unknown fallback values return ErrInvalidInput without retaining partial policy.

func (*OriginGuard) Check

func (g *OriginGuard) Check(r *http.Request) (Origin, error)

Check requires one exact canonical allowlisted Origin on the current request. A present invalid/empty Origin never falls back. Only explicit SameOriginHTTPS may admit an absent Origin through one absolute HTTPS Referer whose normalized origin equals BOTH the resolved effective origin and one trusted origin. Referer may contain path/query, but not userinfo or fragment. Request origin bytes are not repaired or silently normalized. Referer host normalization uses NetworkResolver's fixed Unicode 15.1 IDNA policy. Network/envelope/context errors are preserved; origin denial is redacted. Every error returns an empty Origin. Network facts are resolved internally for this request, never supplied by a caller or taken from a cache. Server composition must retain custody of request metadata during this call.

func (*OriginGuard) CheckBound

func (g *OriginGuard) CheckBound(r *http.Request, bound Origin) (Origin, error)

CheckBound adds an exact initiating-origin comparison to Check. The stored bound value must itself be a configured canonical origin; empty/untrusted bindings deny. It does not verify or consume the owning workflow's nonce, state, token or continuation. A caller cannot use it as standalone proof of authentication, CSRF token validation or one-time completion.

Example
package main

import (
	"crypto/tls"
	"fmt"
	"net/http/httptest"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	network, err := identityhttp.NewNetworkResolver(identityhttp.NetworkConfig{
		Limits:               identityhttp.Limits{MaximumHeaderBytes: 4096, MaximumURLBytes: 2048, MaximumBodyBytes: 4096, MaximumDecodedBytes: 4096, MaximumFields: 32, MaximumDepth: 8},
		MaximumForwardedHops: 8, PublicHosts: []string{"login.example"},
	})
	if err != nil {
		panic(err)
	}
	guard, err := identityhttp.NewOriginGuard(identityhttp.OriginGuardConfig{
		Network: network, TrustedOrigins: []identityhttp.Origin{"https://login.example"},
		RefererFallback: identityhttp.RefererFallbackDeny,
	})
	if err != nil {
		panic(err)
	}
	r := httptest.NewRequest("POST", "/callback", nil)
	r.Host, r.RemoteAddr, r.TLS = "login.example", "198.51.100.7:1234", &tls.ConnectionState{}
	r.Header.Set("Origin", "https://login.example")
	origin, err := guard.CheckBound(r, "https://login.example")
	// Origin admission is only one prerequisite. The owning workflow must still
	// validate its nonce/state/token and atomically consume its continuation.
	fmt.Println(origin, err)
}
Output:
https://login.example <nil>

type OriginGuardConfig

type OriginGuardConfig struct {
	Network         *NetworkResolver
	TrustedOrigins  []Origin
	RefererFallback RefererFallbackPolicy
}

OriginGuardConfig selects the HTTPS browser profile. Network is required and remains the sole proxy/host policy owner. TrustedOrigins contains 1..128 distinct canonical origins of at most 2048 bytes each; no wildcard, opaque, extension or local HTTP origins are supported by this profile. Zero or Deny disables Referer fallback. SameOriginHTTPS permits it only when Origin is absent and the Referer matches the current effective origin and allowlist.

type OriginalCommandSecretRateRequest

type OriginalCommandSecretRateRequest struct {
	OperationID       string
	UserID            identity.UserID
	CommandID         identity.CommandID
	ChallengeID       string
	ChallengeLifetime time.Duration
}

OriginalCommandSecretRateRequest retains the stable native command of a canonical one-time secret operation. It must not manufacture an idempotency header or request key. UserID comes from current authenticated custody; challenge ID/lifetime come from the original native challenge owner, not claimed transport fields or a renewed expiry. Structure grants no authority.

type PasskeySignupVerificationRateRequest

type PasskeySignupVerificationRateRequest struct {
	AnonymousFlowID   identity.PreAuthTransactionID
	ChallengeID       string
	ChallengeLifetime time.Duration
}

PasskeySignupVerificationRateRequest carries only the original native- authenticated anonymous signup flow and create ceremony. A prospective user is not a rate subject or authenticated identity. The caller must authenticate original cookie/flow/ceremony custody before admission; this metadata grants no validity or authority. ChallengeLifetime is the immutable original policy duration, with the same bounds as ChallengeVerificationRateRequest.

func (PasskeySignupVerificationRateRequest) Format

Format redacts original signup browser/ceremony correlation.

func (PasskeySignupVerificationRateRequest) LogValue

LogValue exposes only the fixed redacted marker.

func (PasskeySignupVerificationRateRequest) MarshalJSON

func (PasskeySignupVerificationRateRequest) MarshalJSON() ([]byte, error)

MarshalJSON rejects implicit original signup-custody projection.

func (PasskeySignupVerificationRateRequest) MarshalText

func (PasskeySignupVerificationRateRequest) MarshalText() ([]byte, error)

MarshalText rejects implicit text/map-key correlation.

type PasskeyVerificationRateRequest

type PasskeyVerificationRateRequest struct {
	Identifier        string
	AnonymousFlowID   identity.PreAuthTransactionID
	ChallengeID       string
	ChallengeLifetime time.Duration
}

PasskeyVerificationRateRequest contains native-authenticated original assertion custody, not JSON-selected identity or bearer bytes. Exactly one original canonical Identifier or AnonymousFlowID is required. Discovery's flow is an anonymous rate dimension, never a user ID or authenticated principal. There is no user or operation selector: this input serves identity.passkey.signin-verify only. ChallengeID and integral-microsecond ChallengeLifetime (1s..24h) belong to the original issued ceremony, never remaining expiry or a renewed interval. The native owner must first authenticate exact browser/ceremony/mode custody; cryptographic verification still owns identity. Fields remain borrowed and immutable during a call. This record grants no authentication or validity.

func (PasskeyVerificationRateRequest) Format

Format redacts original passkey assertion correlation and source bindings.

func (PasskeyVerificationRateRequest) LogValue

LogValue exposes only the fixed redacted diagnostic marker.

func (PasskeyVerificationRateRequest) MarshalJSON

func (PasskeyVerificationRateRequest) MarshalJSON() ([]byte, error)

MarshalJSON rejects implicit original-custody projection.

func (PasskeyVerificationRateRequest) MarshalText

func (PasskeyVerificationRateRequest) MarshalText() ([]byte, error)

MarshalText rejects implicit text/map-key correlation.

type PasswordVerificationRateConfig

type PasswordVerificationRateConfig struct {
	Shared          ratelimit.StrictAtomicBatchBackend
	Namespace       string
	Key             [32]byte
	Now             func() time.Time
	MaximumDuration time.Duration
}

PasswordVerificationRateConfig selects the fixed rate.verify profile for identity.password.verify only. Shared must be an initialized concurrency-safe native atomic owner; PostgreSQL needs its atomic schema. Shared and prompt, concurrency-safe Now remain borrowed. Namespace/Key are immutable deployment rate identity, not credential keys; rotating them resets budgets. MaximumDuration is 1ms..2s; earlier caller deadlines win. No constructor IO.

func (PasswordVerificationRateConfig) Format

Format redacts deployment composition for every verb.

func (PasswordVerificationRateConfig) LogValue

LogValue redacts borrowed deployment composition.

func (PasswordVerificationRateConfig) MarshalJSON

func (PasswordVerificationRateConfig) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit deployment export.

func (PasswordVerificationRateConfig) MarshalText

func (PasswordVerificationRateConfig) MarshalText() ([]byte, error)

MarshalText denies implicit deployment text/map-key export.

type PasswordVerificationRateLimiter

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

PasswordVerificationRateLimiter debits trusted IP20/10m and tenant/subject 10/10m atomically through the existing rate primitive. Each call owns a fresh server-generated native debit command, including repeated verification attempts. No retry, journal, counter algorithm, fallback or goroutine is added. This profile cannot admit challenge-based verification operations. Share one owner across password verification requests; concurrent calls/Close are safe.

func NewPasswordVerificationRateLimiter

func NewPasswordVerificationRateLimiter(c PasswordVerificationRateConfig) (*PasswordVerificationRateLimiter, error)

NewPasswordVerificationRateLimiter validates immutable deployment composition without storage IO or selecting any route/metadata. It owns no borrowed resource.

func (*PasswordVerificationRateLimiter) Admit

Admit makes one bounded native atomic dispatch. Known denial returns ErrRateLimited with the longest exhausted delay rounded up to seconds and clamped1..3600. Invalid/nil/already closed input returns ErrInvalidInput before dispatch. Operational, ambiguous, contradictory, panicking, canceled or closure-after-dispatch paths return ErrRateUnavailable without raw diagnostics. A lost admission outcome must not be retried automatically: the debit may have committed. A later user verification attempt is a fresh counted attempt. Cancellation after debit is not refund evidence; domain work must separately honor cancellation, original session validity and cumulative native risk.

func (*PasswordVerificationRateLimiter) Close

Close terminally denies new calls without closing borrowed storage or undoing committed debits. Drain handlers before releasing dependencies. Idempotent.

func (*PasswordVerificationRateLimiter) Configured

func (l *PasswordVerificationRateLimiter) Configured() bool

Configured reports observed nonterminal structural validity, not health or admission. Close may race after it returns; no authority is cached here.

func (PasswordVerificationRateLimiter) Format

Format prevents traversal of retained keys and borrowed owners.

func (PasswordVerificationRateLimiter) LogValue

LogValue redacts runtime composition.

func (PasswordVerificationRateLimiter) MarshalJSON

func (PasswordVerificationRateLimiter) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit runtime export.

func (PasswordVerificationRateLimiter) MarshalText

func (PasswordVerificationRateLimiter) MarshalText() ([]byte, error)

MarshalText denies implicit runtime text/map-key export.

type PasswordVerificationRateRequest

type PasswordVerificationRateRequest struct {
	UserID identity.UserID
}

PasswordVerificationRateRequest borrows the current authenticated subject, never a client-claimed user. Password verification has no pre-issued challenge and is non-keyed: there is no challenge override or idempotency key. Admission does not authenticate the subject, validate a password or grant proof authority.

func (PasswordVerificationRateRequest) Format

Format redacts trusted subject custody for every verb.

func (PasswordVerificationRateRequest) LogValue

LogValue redacts original subject custody.

func (PasswordVerificationRateRequest) MarshalJSON

func (PasswordVerificationRateRequest) MarshalJSON() ([]byte, error)

MarshalJSON denies implicit request export.

func (PasswordVerificationRateRequest) MarshalText

func (PasswordVerificationRateRequest) MarshalText() ([]byte, error)

MarshalText denies implicit request text/map-key export.

type ProbeConfig

type ProbeConfig struct {
	State     ServerStateFunc
	Readiness ReadinessProvider
	Timeout   time.Duration
}

ProbeConfig is copied at construction. State and Readiness.Check are required; Timeout must be positive and at most two seconds. An earlier request deadline always wins. Construction performs no checks, IO or global route registration.

type RatePolicyID

type RatePolicyID string

RatePolicyID names one complete canonical multi-bucket rate policy. A named binding does not prove admission, counter atomicity or outage handling. The later transport limiter must expand this ID through its owned rate catalog; it must not approximate multiple buckets with one window/burst override.

type ReadinessCheckFunc

type ReadinessCheckFunc func(context.Context) (ReadinessSnapshot, error)

ReadinessCheckFunc must be safe for concurrent calls, honor cancellation and complete within its context deadline. It runs synchronously, without lifecycle locks or transport retries. The transport cannot terminate arbitrary callback code that ignores cancellation and does not detach it in another goroutine.

type ReadinessCode

type ReadinessCode string

ReadinessCode identifies a blocking condition in a composed snapshot. Codes are opaque, non-secret ASCII labels of 1..128 bytes, not diagnostic messages. Public probe responses deliberately do not serialize these internal labels.

type ReadinessProvider

type ReadinessProvider struct{ Check ReadinessCheckFunc }

ReadinessProvider supplies the selected composition's real readiness check. The zero value is invalid. A healthy listener alone is not a valid check.

type ReadinessSnapshot

type ReadinessSnapshot struct {
	Ready bool
	Codes []ReadinessCode
}

ReadinessSnapshot is the composition owner's current dependency, migration and key decision. Ready requires no blocking Codes; not-ready requires 1..64 Codes. Optional dependency degradation need not prevent Ready. Callbacks own the returned slice; it must remain unchanged until the check returns to its caller. The transport retains neither it nor its values after the request.

type RedirectGuard

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

RedirectGuard owns a copied immutable full-target allowlist. Zero is invalid; independent calls may share a constructed guard. It does not derive trust from request Host, Origin, Referer, proxy or tenant metadata and does no IO. A successful check is target admission only, not client/tenant/flow binding, authentication, state/nonce/PKCE validation or permission to append parameters. Use a separate guard for each policy owner; never union unrelated clients' redirect sets to replace their protocol-specific registration checks.

Example
package main

import (
	"context"
	"fmt"

	identityhttp "github.com/faustbrian/go-identity/http"
)

func main() {
	network, err := identityhttp.NewNetworkResolver(identityhttp.NetworkConfig{
		Limits: identityhttp.Limits{MaximumHeaderBytes: 4096, MaximumURLBytes: 2048,
			MaximumBodyBytes: 4096, MaximumDecodedBytes: 4096, MaximumFields: 32, MaximumDepth: 8},
		MaximumForwardedHops: 8, PublicHosts: []string{"login.example"},
	})
	if err != nil {
		panic(err)
	}
	guard, err := identityhttp.NewRedirectGuard(identityhttp.RedirectGuardConfig{
		Network: network, Targets: []string{"https://client.example/callback?view=compact"},
	})
	if err != nil {
		panic(err)
	}
	target, err := guard.Check(context.Background(), "https://client.example/callback?view=compact")
	fmt.Println(target, err)
	target, err = guard.Check(context.Background(), "https://client.example/callback?view=other")
	fmt.Println(target == "", err)
}
Output:
https://client.example/callback?view=compact <nil>
true identity.http.origin_denied

func NewRedirectGuard

func NewRedirectGuard(config RedirectGuardConfig) (*RedirectGuard, error)

NewRedirectGuard validates and copies the complete allowlist without IO. Invalid owners, bounds, duplicates or malformed/noncanonical URLs return ErrInvalidInput and no partial guard. Network's fixed Unicode host policy is reused for syntax validation; redirect hosts need not be served host-map keys.

func (*RedirectGuard) Check

func (g *RedirectGuard) Check(ctx context.Context, target string) (string, error)

Check returns target unchanged only for exact allowlist membership. It never normalizes a candidate, emits Location, appends protocol parameters or returns a fallback URL. Nil context/zero owner returns ErrInvalidInput; cancellation retains its context error; all other denials return redacted OriginError. Every error returns an empty string. Callers must not redirect even an OAuth error to a denied target. After admission the owning workflow must still bind the registered client, tenant and initiating transaction, and separately own any protocol-defined response construction.

type RedirectGuardConfig

type RedirectGuardConfig struct {
	Network *NetworkResolver
	Targets []string
}

RedirectGuardConfig selects the exact HTTPS redirect profile. Network is an immutable host-syntax owner, not a source of redirect trust. Targets contains 1..128 distinct full URLs of at most 2048 bytes each. Scheme/authority must already be canonical: lowercase https and ASCII/IDNA host, canonical port, no explicit default port. Path/query bytes are retained exactly; no wildcard, userinfo, fragment or backslash is allowed. Caller must not mutate inputs during construction. Local HTTP and native-application profiles are separate future work, not implicit exceptions to this profile.

type RefererFallbackPolicy

type RefererFallbackPolicy uint8

RefererFallbackPolicy is explicit when a CSRF variant uses origin protection.

const (
	RefererFallbackDeny RefererFallbackPolicy = iota + 1
	RefererFallbackSameOriginHTTPS
)

type RequestDecoder

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

RequestDecoder owns immutable copied limits and no request state. Its zero value is invalid. Concurrent independent requests may share one decoder; callers must not concurrently mutate a request or consume its body.

func NewRequestDecoder

func NewRequestDecoder(limits Limits) (*RequestDecoder, error)

NewRequestDecoder validates every limit without IO or implicit defaults.

func (*RequestDecoder) AuxiliaryCookieBearer

func (d *RequestDecoder) AuxiliaryCookieBearer(r *http.Request, name string, requirement HeaderRequirement) (authentication.BearerCredential, bool, error)

AuxiliaryCookieBearer acquires exact auxiliary cookie custody only alongside one syntactically valid direct Authorization bearer. It does not authenticate either input: callers validate the original primary credential and auxiliary provenance with their respective owners. Never use the returned cookie as a session credential or fallback. Proxy-Authorization and query alternatives deny. All CookieBearer framing, limits, cancellation and zero-on-error rules apply unchanged; this method never mutates headers or accesses the body. Existing CookieBearer retains its no-Authorization exclusivity.

func (*RequestDecoder) Bearer

Bearer extracts only Authorization into the existing authentication custody type; extraction is not credential validation or authorization. The selected grammar is case-insensitive Bearer, exactly one ASCII space, then 1..8192 bytes from letters, digits, '-._~+/' with optional trailing '=' padding. At least one non-padding byte is required. Whitespace, commas, controls and non-ASCII deny. Envelope header limits may impose a smaller effective bound. Cookies are ignored, never a fallback. Proxy-Authorization and query bearer alternatives deny. Body and redirect fields never supply bearer authority. No body reads, closure, cryptography or domain calls occur. Invalid/absent optional input returns the zero credential and false; errors retain no input. HeaderForbidden implements the bearer-issue route's Authorization rejection. A later trusted-network owner must prove provenance; syntax cannot detect an upstream proxy that replaced an otherwise syntactically valid header.

func (*RequestDecoder) CookieBearer

func (d *RequestDecoder) CookieBearer(r *http.Request, name string, requirement HeaderRequirement) (authentication.BearerCredential, bool, error)

CookieBearer acquires only the exact case-sensitive selected cookie into authentication's existing opaque bearer custody. This is not authentication: the owning workflow must validate its token format and original possession. Authorization and Proxy-Authorization are forbidden; query values never substitute for cookies, and a selected-name query alternative is rejected.

Name is a 1..128-byte HTTP token. Requirement applies to this cookie, not unrelated cookies. All Cookie lines share at most 16KiB and 32 parts; each part has a valid name and RFC6265 cookie-octet value. Malformed siblings deny. The selected part must be unique, unquoted, nonempty, at most 3800 bytes including its name and '=', and match the bounded bearer token grammar. Values are never trimmed, decoded or normalized. Header envelope limits may impose stricter bounds. Every error returns zero custody and false.

The decoder owns no request/resource and performs no IO, body reads or token comparison. Independent requests may share it; requests must not be mutated concurrently. Route owners still forbid body/redirect alternatives and apply network/origin/CSRF policy before domain work. Parsed headers cannot establish their original proxy/network provenance.

func (*RequestDecoder) Form

func (d *RequestDecoder) Form(r *http.Request) (url.Values, error)

Form reads application/x-www-form-urlencoded body parameters only; it never merges URL query parameters. Empty body is valid. Names must be nonempty; missing '=' means an empty value. Empty segments, bare semicolons, invalid percent escapes/UTF-8, duplicate or case-alias names and idempotency-key body alternatives reject atomically. Percent decoding uses '+' as space. Limits count entries globally and decoded UTF-8 name/value bytes before allocation. Body ownership, cancellation and blocking-read rules match DecodeJSON.

func (*RequestDecoder) IdempotencyKey

func (d *RequestDecoder) IdempotencyKey(r *http.Request, requirement HeaderRequirement) (identity.IdempotencyKey, bool, error)

IdempotencyKey copies one exact Idempotency-Key into the kernel's existing opaque custody type. It applies the immutable envelope limits, rejects query, cookie and trailer alternatives, and never trims or normalizes key bytes. The kernel constructor owns the 1..256 ASCII HTTP-token grammar. Absence when permitted returns a zero key and false; every error does likewise. This method never reads/closes the body, authenticates, hashes or deduplicates. The route must also decode its body through the appropriate strict codec; ordinary JSON/form codecs reject body alternatives. Multipart and protocol codecs must enforce their own equivalent channel checks before domain work. Trusted composition must prevent middleware from synthesizing credential headers: a parsed Request cannot prove its original network provenance.

func (*RequestDecoder) Query

func (d *RequestDecoder) Query(r *http.Request) (url.Values, error)

Query parses URL.RawQuery only and never reads or closes the body or merges cached Request.Form/PostForm fields. It uses Form's duplicate/encoding and decoded-byte rules, plus MaximumURLBytes for the complete represented URI. It does not authorize credential acquisition from query parameters.

type Route

type Route struct {
	Method, Path  string
	Authorization RouteAuthorization
	CSRF          CSRFPolicy
	RatePolicy    RatePolicyID
}

Route records an exact method/path and the same policy as its operation. Supported methods are GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. Paths contain 1..2048 ASCII bytes, start with / and cannot contain escapes, query, fragments, repeated separators or dot segments. No path normalization occurs.

type RouteAuthorization

type RouteAuthorization struct{ Access, Policy string }

RouteAuthorization records explicit access and authorization policy text, respectively 1..256 and 1..512 printable ASCII bytes. It is not an authorizer.

type SafeRateConfig

type SafeRateConfig struct {
	Shared               ratelimit.StrictBackend
	Namespace            string
	Now                  func() time.Time
	Degraded             func(context.Context, SafeRateEvent) error
	MaximumDuration      time.Duration
	EmergencyMaximumKeys int
}

SafeRateConfig selects the canonical rate.safe profile, not custom limits. Shared is a borrowed concurrency-safe strong backend from the rate primitive; it must outlive calls. Namespace isolates a deployment (primitive key grammar). Now is a trusted, prompt concurrency-safe clock. Degraded must synchronously record each fallback decision within ctx and return an error on failure. No callback may retain ctx or launch detached work. MaximumDuration is 1ms..2s; earlier caller deadlines win. EmergencyMaximumKeys is 1..1000000. Use one shared limiter per process/namespace, not one per route or request; constructing independent instances creates independent emergency budgets.

type SafeRateEvent

type SafeRateEvent struct {
	OperationID string
	Allowed     bool
}

SafeRateEvent attributes one emergency-store decision to a canonical operation. It contains no network address, tenant, subject or credential.

type SafeRateLimiter

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

SafeRateLimiter composes primitive-owned counters without a second rate algorithm. It borrows Shared, owns bounded process-local emergency storage, and starts no goroutines. Independent calls and Close are concurrency-safe. Zero is invalid. Bucket admission is ordered, not a refund transaction: an IP debit may remain when a later subject bucket denies or becomes unknown.

func NewSafeRateLimiter

func NewSafeRateLimiter(c SafeRateConfig) (*SafeRateLimiter, error)

NewSafeRateLimiter validates and freezes policy without storage IO. Shared must actually provide strong consistency; the primitive enforces the selected policy on each operation. The emergency store is deliberately process-local, bounded and fail-closed on saturation. No optional identity feature is imported.

func (*SafeRateLimiter) Admit

func (l *SafeRateLimiter) Admit(ctx context.Context, facts NetworkFacts, operationID string, user identity.UserID) (out SafeRateResult, err error)

Admit enforces trusted IP100/minute and, for a nonempty authenticated user, tenant-scoped subject300/minute. facts must come from NetworkResolver and user must come from current authenticated owner custody, never request input. operationID must name a canonical rate.safe row; no fallback for unknown rows. IP keys use the exact normalized full address, never subnet aggregation. Counters span all rate.safe operations, not independently per operation. Known shared-store unavailability selects IP20/minute emergency admission and emits a required bounded degradation event. Ambiguous dispatch, cancellation, malformed replies or event failure deny without fallback authority. This method does not authenticate, refresh proof, execute a handler or retry work.

func (*SafeRateLimiter) Close

func (l *SafeRateLimiter) Close() error

Close terminally denies future admission and closes only owned emergency storage. It is idempotent and does not close the borrowed shared backend. It does not revoke admissions already returned or interrupt caller callbacks; the composition must drain handlers before releasing shared dependencies.

func (*SafeRateLimiter) Configured

func (l *SafeRateLimiter) Configured() bool

Configured reports constructor validity and observed nonterminal local ownership without IO or callbacks. Nil/zero/closed owners are false. Close may race immediately afterward; true is not health, admission or a lease. Independent inspections, Admit and Close are concurrency-safe.

func (SafeRateLimiter) Format

func (SafeRateLimiter) Format(s fmt.State, _ rune)

Format prevents traversal of borrowed backend/callback configuration.

func (SafeRateLimiter) MarshalJSON

func (SafeRateLimiter) MarshalJSON() ([]byte, error)

MarshalJSON denies accidental export of runtime composition.

func (SafeRateLimiter) MarshalText

func (SafeRateLimiter) MarshalText() ([]byte, error)

MarshalText denies accidental export of runtime composition.

type SafeRateResult

type SafeRateResult struct {
	Allowed, Degraded bool
	RetryAfter        time.Duration
}

SafeRateResult is one admission observation, never a reusable authority. Allowed is true only with nil error. Degraded identifies local fallback; RetryAfter is populated on a known limit denial, not on infrastructure errors.

type ServerConfig

type ServerConfig struct {
	Address            string
	ReadHeaderTimeout  time.Duration
	ReadTimeout        time.Duration
	WriteTimeout       time.Duration
	IdleTimeout        time.Duration
	HandlerTimeout     time.Duration
	ShutdownDrain      time.Duration
	MaximumHeaderBytes uint32
}

ServerConfig is copied by NewHTTPServer. Address is an explicit TCP host:port (port zero permits an ephemeral listener). All durations must be 1ns..24h; HandlerTimeout cannot exceed WriteTimeout. MaximumHeaderBytes is 1KiB..64KiB. These are reusable bounds, not the stricter reference deployment profile. TLS termination belongs to deployment or a TLS listener supplied to Serve. No defaults, provider calls or listener acquisition occur during construction.

type ServerState

type ServerState uint8

ServerState is a lifecycle snapshot, not dependency or identity readiness. Zero is invalid. A server never restarts after a start attempt or shutdown.

const (
	// ServerNew owns no listener and has not begun starting.
	ServerNew ServerState = iota + 1
	// ServerStarting has reserved its only start attempt.
	ServerStarting
	// ServerRunning has acquired its listener and permits handler admission.
	ServerRunning
	// ServerDraining refuses new admission while existing requests finish.
	ServerDraining
	// ServerStopped is terminal, including a bind failure or pre-start shutdown.
	ServerStopped
)

type ServerStateFunc

type ServerStateFunc func() ServerState

ServerStateFunc returns the current lifecycle state. Supply HTTPServer.State in composition; the function must be concurrent-safe, prompt and perform no IO. It is called before and after readiness work, never under a transport lock.

type TenantHost

type TenantHost struct {
	Host     string
	TenantID identity.TenantID
}

TenantHost binds one exact canonical host to a tenant. Host has no port; DNS names are lowercase UTS46 ASCII, IPv4 is canonical dotted decimal and IPv6 is canonical bracketed text. Noncanonical configuration is rejected, not silently merged. TenantID is nonempty, case-sensitive and <=128 UTF-8 bytes.

type TransportPolicy

type TransportPolicy struct {
	Network        NetworkConfig
	CORS           []CORSRoutePolicy
	HandlerTimeout time.Duration
	Document       DocumentConfig
}

TransportPolicy selects shared transport enforcement for NewRouter. Network is a complete copied host/proxy/input-limit policy; unknown hosts never select a tenant. HandlerTimeout is 1ms..30s and cannot extend an inbound deadline. Document supplies public static title/version, not runtime host information. CORS is opt-in per exact selected method/path (at most 1024 policies); empty disables cross-origin grants. All origins and bounds are validated/copied.

Canonical routes are root-mounted exactly as declared, including probes and protocol endpoints. There is no prefix rewrite, redirect or implicit metadata endpoint. Feature factories remain the single owners of their cookie policy, credential acquisition, authorization, rate admission and transactional CSRF. Share their actual network policy with Network; a router admission alone is not a credential, session, rate decision or proof of feature enforcement.

type TypedSchema

type TypedSchema struct {
	ID           string
	Digest       [32]byte
	MaximumDepth uint32
}

TypedSchema identifies a public contract, nonzero content digest and depth bound (1..128). IDs are 1..256 printable non-space ASCII bytes. This summary is not a rendered or validated JSON Schema or OpenAPI document.

type WireComponent

type WireComponent struct {
	Name       string
	Definition []byte
}

WireComponent is one concrete JSON Schema or Security Scheme Object. Name is a 1..128-byte component identifier. Definition is strict bounded JSON; references must be internal. Feature owners supply public, non-secret data. Inputs must not be mutated concurrently with construction.

type WireOperation

type WireOperation struct {
	Definition               []byte
	Schemas, SecuritySchemes []WireComponent
}

WireOperation contains the actual OpenAPI Operation Object, separate from direct Go-contract identities. Definition is 1..64KiB, schemas/security each contain at most 64 components of 1..64KiB, and aggregate size is <=512KiB. Schemas and schemes are globally unique, including identical duplicates. Declare shared components once and refer to them from other operations. Factories own correspondence with their real handlers; validation cannot prove arbitrary caller handlers enforce a schema or security declaration.

Directories

Path Synopsis
anonymous module
apikey module
email module
magiclink module
mfa module
oauthserver module
organization module
otp module
passkey module
password module
phone module
session module

Jump to

Keyboard shortcuts

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