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 ¶
- Variables
- func DecodeCSRF[T any](decoder *CSRFDecoder, request *http.Request, parse func(string) (T, error)) (T, url.Values, error)
- func DecodeJSON[T any](decoder *RequestDecoder, request *http.Request) (T, error)
- func NewCORSHandler(config CORSConfig, next http.Handler) (http.Handler, error)
- func NewOperationPipeline[T any](c OperationPipelineConfig[T]) (handler http.Handler, err error)
- func NewProbeHandlers(config ProbeConfig) (liveness, readiness http.Handler, err error)
- func NewSelectedHandler(services FeatureServices) (http.Handler, error)
- func PipelineValue[T any](r *http.Request) (T, bool)
- func WithOperationMiddleware(handler http.Handler, middleware []MiddlewareDescriptor) (bound http.Handler, err error)
- type AuthRateConfig
- type AuthRateLimiter
- func (l *AuthRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (out AuthRateResult, err error)
- func (l *AuthRateLimiter) AdmitDeliveryIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitDeliveryIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitOriginalFlow(ctx context.Context, facts NetworkFacts) (out AuthRateResult, err error)
- func (l *AuthRateLimiter) AdmitPasskeyOptionsIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitPasskeyOptionsIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitSigninIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitSigninIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitSignupIP(ctx context.Context, facts NetworkFacts, operationID string) (AuthRateResult, error)
- func (l *AuthRateLimiter) AdmitSignupIdentifier(ctx context.Context, facts NetworkFacts, r AuthRateRequest) (AuthRateResult, error)
- func (l *AuthRateLimiter) Close() error
- func (l *AuthRateLimiter) Configured() bool
- func (AuthRateLimiter) Format(s fmt.State, _ rune)
- func (AuthRateLimiter) LogValue() slog.Value
- func (AuthRateLimiter) MarshalJSON() ([]byte, error)
- func (AuthRateLimiter) MarshalText() ([]byte, error)
- type AuthRateRequest
- type AuthRateResult
- type CORSConfig
- type CORSCredentialMode
- type CORSRoutePolicy
- type CSRFDecoder
- type CSRFDecoderConfig
- type CSRFError
- type CSRFMode
- type CSRFPolicy
- type CanonicalOperationConfig
- type ChallengeVerificationRateConfig
- type ChallengeVerificationRateLimiter
- func (l *ChallengeVerificationRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r ChallengeVerificationRateRequest) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) AdmitChallenge(ctx context.Context, facts NetworkFacts, r ChallengeVerificationRateRequest) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) AdmitIP(ctx context.Context, facts NetworkFacts) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) AdmitPasskey(ctx context.Context, facts NetworkFacts, r PasskeyVerificationRateRequest) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) AdmitPasskeyChallenge(ctx context.Context, facts NetworkFacts, r PasskeyVerificationRateRequest) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) AdmitPasskeySignup(ctx context.Context, facts NetworkFacts, ...) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) AdmitPasskeySignupChallenge(ctx context.Context, facts NetworkFacts, ...) (AuthRateResult, error)
- func (l *ChallengeVerificationRateLimiter) Close() error
- func (l *ChallengeVerificationRateLimiter) Configured() bool
- func (ChallengeVerificationRateLimiter) Format(f fmt.State, _ rune)
- func (ChallengeVerificationRateLimiter) LogValue() slog.Value
- func (ChallengeVerificationRateLimiter) MarshalJSON() ([]byte, error)
- func (ChallengeVerificationRateLimiter) MarshalText() ([]byte, error)
- type ChallengeVerificationRateRequest
- type CoverageStatus
- type DecodeError
- type DocumentConfig
- type Exposure
- type Feature
- type FeatureConfig
- type FeatureServices
- type HTTPServer
- func (s *HTTPServer) Address() string
- func (s *HTTPServer) Serve(ctx context.Context, ln net.Listener) error
- func (s *HTTPServer) Shutdown(ctx context.Context) error
- func (s *HTTPServer) Start(ctx context.Context) error
- func (s *HTTPServer) Started() <-chan struct{}
- func (s *HTTPServer) State() ServerState
- type HeaderRequirement
- type KeyedSecretRateConfig
- type KeyedSecretRateLimiter
- func (l *KeyedSecretRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r KeyedSecretRateRequest) (out AuthRateResult, err error)
- func (l *KeyedSecretRateLimiter) AdmitInspectionIP(ctx context.Context, facts NetworkFacts, operation string) (AuthRateResult, error)
- func (l *KeyedSecretRateLimiter) AdmitInspectionSubject(ctx context.Context, facts NetworkFacts, r ChallengeVerificationRateRequest) (AuthRateResult, error)
- func (l *KeyedSecretRateLimiter) AdmitOriginalCommand(ctx context.Context, facts NetworkFacts, r OriginalCommandSecretRateRequest) (AuthRateResult, error)
- func (l *KeyedSecretRateLimiter) Close() error
- func (l *KeyedSecretRateLimiter) Configured() bool
- func (KeyedSecretRateLimiter) Format(s fmt.State, _ rune)
- func (KeyedSecretRateLimiter) LogValue() slog.Value
- func (KeyedSecretRateLimiter) MarshalJSON() ([]byte, error)
- func (KeyedSecretRateLimiter) MarshalText() ([]byte, error)
- type KeyedSecretRateRequest
- type Limits
- type MetadataConfig
- type Middleware
- type MiddlewareDescriptor
- type MiddlewareFunc
- type MiddlewarePhase
- type ModuleID
- type NetworkConfig
- type NetworkFacts
- func (f NetworkFacts) Authority() string
- func (f NetworkFacts) ClientIP() netip.Addr
- func (NetworkFacts) Format(s fmt.State, _ rune)
- func (f NetworkFacts) Forwarded() bool
- func (f NetworkFacts) Host() string
- func (NetworkFacts) LogValue() slog.Value
- func (NetworkFacts) MarshalJSON() ([]byte, error)
- func (NetworkFacts) MarshalText() ([]byte, error)
- func (f NetworkFacts) Origin() string
- func (f NetworkFacts) Realm() NetworkRealm
- func (f NetworkFacts) Scheme() string
- func (f NetworkFacts) TenantID() identity.TenantID
- func (f NetworkFacts) Valid() bool
- type NetworkRealm
- type NetworkResolver
- type OpenAPIDocument
- func NewDocumentedHandler(ctx context.Context, config DocumentConfig, services FeatureServices) (http.Handler, OpenAPIDocument, error)
- func NewDocumentedMetadataHandler(ctx context.Context, config DocumentConfig, metadata MetadataConfig, ...) (http.Handler, OpenAPIDocument, error)
- func NewRouter(policy TransportPolicy, operations []OperationDescriptor, ...) (http.Handler, OpenAPIDocument, error)
- func (d OpenAPIDocument) Coverage() []OperationCoverage
- func (d OpenAPIDocument) CoverageJSON() []byte
- func (d OpenAPIDocument) Digest() [32]byte
- func (d OpenAPIDocument) JSON() []byte
- func (d OpenAPIDocument) OpenapiDocument(ctx context.Context, _ OpenapiDocumentRequest) (OpenapiDocumentResult, error)
- func (d OpenAPIDocument) Operations() []OperationDescriptor
- func (d OpenAPIDocument) Schemas() []WireComponent
- func (d OpenAPIDocument) Version() string
- type OpenAPISchema
- type OpenapiDocumentRequest
- type OpenapiDocumentResult
- type OperationCoverage
- type OperationDescriptor
- type OperationMiddlewareBinder
- type OperationPipelineConfig
- type OperationRecord
- type Origin
- type OriginError
- type OriginGuard
- type OriginGuardConfig
- type OriginalCommandSecretRateRequest
- type PasskeySignupVerificationRateRequest
- type PasskeyVerificationRateRequest
- type PasswordVerificationRateConfig
- type PasswordVerificationRateLimiter
- func (l *PasswordVerificationRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r PasswordVerificationRateRequest) (out AuthRateResult, err error)
- func (l *PasswordVerificationRateLimiter) Close() error
- func (l *PasswordVerificationRateLimiter) Configured() bool
- func (PasswordVerificationRateLimiter) Format(s fmt.State, _ rune)
- func (PasswordVerificationRateLimiter) LogValue() slog.Value
- func (PasswordVerificationRateLimiter) MarshalJSON() ([]byte, error)
- func (PasswordVerificationRateLimiter) MarshalText() ([]byte, error)
- type PasswordVerificationRateRequest
- type ProbeConfig
- type RatePolicyID
- type ReadinessCheckFunc
- type ReadinessCode
- type ReadinessProvider
- type ReadinessSnapshot
- type RedirectGuard
- type RedirectGuardConfig
- type RefererFallbackPolicy
- type RequestDecoder
- func (d *RequestDecoder) AuxiliaryCookieBearer(r *http.Request, name string, requirement HeaderRequirement) (authentication.BearerCredential, bool, error)
- func (d *RequestDecoder) Bearer(r *http.Request, requirement HeaderRequirement) (authentication.BearerCredential, bool, error)
- func (d *RequestDecoder) CookieBearer(r *http.Request, name string, requirement HeaderRequirement) (authentication.BearerCredential, bool, error)
- func (d *RequestDecoder) Form(r *http.Request) (url.Values, error)
- func (d *RequestDecoder) IdempotencyKey(r *http.Request, requirement HeaderRequirement) (identity.IdempotencyKey, bool, error)
- func (d *RequestDecoder) Query(r *http.Request) (url.Values, error)
- type Route
- type RouteAuthorization
- type SafeRateConfig
- type SafeRateEvent
- type SafeRateLimiter
- func (l *SafeRateLimiter) Admit(ctx context.Context, facts NetworkFacts, operationID string, ...) (out SafeRateResult, err error)
- func (l *SafeRateLimiter) Close() error
- func (l *SafeRateLimiter) Configured() bool
- func (SafeRateLimiter) Format(s fmt.State, _ rune)
- func (SafeRateLimiter) MarshalJSON() ([]byte, error)
- func (SafeRateLimiter) MarshalText() ([]byte, error)
- type SafeRateResult
- type ServerConfig
- type ServerState
- type ServerStateFunc
- type TenantHost
- type TransportPolicy
- type TypedSchema
- type WireComponent
- type WireOperation
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidInput = errors.New("identityhttp: invalid input")
ErrInvalidInput rejects invalid configuration, nil contexts and zero owners.
var ErrLifecycle = errors.New("identityhttp: lifecycle conflict")
ErrLifecycle rejects a second start, including after a failed start or stop.
var ErrRateLimited = errors.New("identity.http.rate_limited")
ErrRateLimited denies a request whose applicable rate bucket is exhausted.
ErrRateUnavailable denies unavailable, ambiguous or invalid rate evidence. Backend diagnostics and subject identifiers are never retained.
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 ¶
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.
Output:
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 ¶
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 {
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 ¶
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 ¶
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.
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 ¶
func (l *ChallengeVerificationRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r ChallengeVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) AdmitChallenge(ctx context.Context, facts NetworkFacts, r ChallengeVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) AdmitIP(ctx context.Context, facts NetworkFacts) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) AdmitPasskey(ctx context.Context, facts NetworkFacts, r PasskeyVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) AdmitPasskeyChallenge(ctx context.Context, facts NetworkFacts, r PasskeyVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) AdmitPasskeySignup(ctx context.Context, facts NetworkFacts, r PasskeySignupVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) AdmitPasskeySignupChallenge(ctx context.Context, facts NetworkFacts, r PasskeySignupVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *ChallengeVerificationRateLimiter) Close() error
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 ¶
func (ChallengeVerificationRateLimiter) Format(f fmt.State, _ rune)
Format redacts runtime composition.
func (ChallengeVerificationRateLimiter) LogValue ¶
func (ChallengeVerificationRateLimiter) LogValue() slog.Value
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 ¶
func (ChallengeVerificationRateRequest) Format(f fmt.State, _ rune)
Format redacts source bindings.
func (ChallengeVerificationRateRequest) LogValue ¶
func (ChallengeVerificationRateRequest) LogValue() slog.Value
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" // 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.
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) 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 ¶
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 {
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 ¶
func (l *KeyedSecretRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r KeyedSecretRateRequest) (out AuthRateResult, err error)
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 ¶
func (l *KeyedSecretRateLimiter) AdmitInspectionSubject(ctx context.Context, facts NetworkFacts, r ChallengeVerificationRateRequest) (AuthRateResult, error)
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 ¶
func (l *KeyedSecretRateLimiter) AdmitOriginalCommand(ctx context.Context, facts NetworkFacts, r OriginalCommandSecretRateRequest) (AuthRateResult, error)
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 ¶
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 ¶
func (d OpenAPIDocument) OpenapiDocument(ctx context.Context, _ OpenapiDocumentRequest) (OpenapiDocumentResult, error)
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 ¶
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 ¶
func (PasskeySignupVerificationRateRequest) Format(f fmt.State, _ rune)
Format redacts original signup browser/ceremony correlation.
func (PasskeySignupVerificationRateRequest) LogValue ¶
func (PasskeySignupVerificationRateRequest) LogValue() slog.Value
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 ¶
func (PasskeyVerificationRateRequest) Format(f fmt.State, _ rune)
Format redacts original passkey assertion correlation and source bindings.
func (PasskeyVerificationRateRequest) LogValue ¶
func (PasskeyVerificationRateRequest) LogValue() slog.Value
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 {
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 ¶
func (PasswordVerificationRateConfig) Format(s fmt.State, _ rune)
Format redacts deployment composition for every verb.
func (PasswordVerificationRateConfig) LogValue ¶
func (PasswordVerificationRateConfig) LogValue() slog.Value
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 ¶
func (l *PasswordVerificationRateLimiter) Admit(ctx context.Context, facts NetworkFacts, r PasswordVerificationRateRequest) (out AuthRateResult, err error)
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 ¶
func (l *PasswordVerificationRateLimiter) Close() error
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 ¶
func (PasswordVerificationRateLimiter) Format(s fmt.State, _ rune)
Format prevents traversal of retained keys and borrowed owners.
func (PasswordVerificationRateLimiter) LogValue ¶
func (PasswordVerificationRateLimiter) LogValue() slog.Value
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 ¶
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 ¶
func (PasswordVerificationRateRequest) Format(s fmt.State, _ rune)
Format redacts trusted subject custody for every verb.
func (PasswordVerificationRateRequest) LogValue ¶
func (PasswordVerificationRateRequest) LogValue() slog.Value
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 ¶
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 ¶
func (d *RequestDecoder) Bearer(r *http.Request, requirement HeaderRequirement) (authentication.BearerCredential, bool, error)
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 ¶
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 ¶
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 {
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
Source Files
¶
- auth_rate.go
- binding.go
- challenge_verify_rate.go
- compose.go
- cookie_credential.go
- cors.go
- coverage.go
- credentials.go
- csrf.go
- decode.go
- metadata.go
- network.go
- openapi.go
- origin.go
- password_verify_rate.go
- pipeline.go
- probe_binding.go
- probe_wire.go
- probes.go
- rate.go
- redirect.go
- router.go
- secret_rate.go
- selection.go
- server.go
- wire_profile.go