Documentation
¶
Overview ¶
Package htpx is the HTTP application toolkit that backs apic-generated services. It wires a Gin-based TLS server (driven by the OpenAPI spec via oapi-codegen middleware, CORS, and slog request logging), a buffered outbound HTTP Client with retry, rate limiting, and a response-body size ceiling, and a TLS-terminating reverse Proxy with pluggable rules, subnet filters, and request rewriters. It also provides the request-authentication surfaces: API keys (with pluggable stores and scope checks), FIDO/WebAuthn, HMAC request signatures, and OIDC.
TLS here is server-side only (SEC-NEW2-04): serverTLSConfig builds its tls.Config through fipsx.NewServerTLSConfig(nil), which sets ClientAuth: tls.NoClientCert, and no ServerOption in this package sets ClientAuth or ClientCAs. htpx therefore does NOT enforce mutual TLS -- client-certificate authentication lives in api/ and in the generated server (server.tls.mtls_ca_path, pkg/securex/mtlsx). Do not read the server surface here as an mTLS boundary.
Structured logging flows through pkg/log.
Index ¶
- Constants
- Variables
- func APIKeyMiddleware(cfg *APIKeyAuthConfig) gin.HandlerFunc
- func Args(app string) cli.Args
- func AuthToken(ctx context.Context) (jwt.Token, error)
- func CacheMiddleware(cfg *CacheConfig) gin.HandlerFunc
- func CircuitBreakerMiddleware(cb *CircuitBreaker) gin.HandlerFunc
- func Clone(in *http.Request) *http.Request
- func CloneHeader(in http.Header) http.Header
- func CloneResponse(in *http.Response) *http.Response
- func CloneURL(in *url.URL) *url.URL
- func CompressionMiddleware(cfg *CompressionConfig) gin.HandlerFunc
- func ConstantTimeCompare(a, b string) bool
- func FIDOMiddleware(store FIDOStore) gin.HandlerFunc
- func GenerateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error)
- func GenerateTLSCertificate(l net.Listener, names ...string) (tls.Certificate, error)
- func LoadPrivateKey(pemBytes []byte) (any, error)
- func LoadPublicKey(pemBytes []byte) (any, error)
- func Proxy(ctx context.Context, l net.Listener, opts ...ProxyOption) error
- func RequestLogger(logger *slog.Logger) gin.HandlerFunc
- func RequireScope(scope string) gin.HandlerFunc
- func SignatureMiddleware(verifier *Verifier) gin.HandlerFunc
- func To[T any](c *Client, resp *http.Response) (T, error)
- type APIKey
- type APIKeyAuthConfig
- type APIKeyManager
- func (m *APIKeyManager) DeleteKey(id string) error
- func (m *APIKeyManager) GenerateKey(name string, scopes []string, metadata map[string]any, expiresAt *time.Time) (*APIKey, string, error)
- func (m *APIKeyManager) GetKey(id string) (*APIKey, error)
- func (m *APIKeyManager) ListKeys() ([]*APIKey, error)
- func (m *APIKeyManager) RevokeKey(id string) error
- func (m *APIKeyManager) ValidateKey(keyStr string) (*APIKey, error)
- type APIKeyStats
- type APIKeyStore
- type CacheConfig
- type CacheEntry
- type CacheStore
- type CircuitBreaker
- type CircuitBreakerConfig
- type CircuitState
- type Client
- func (c *Client) Close() (out error)
- func (c *Client) Delete(uri string, query url.Values, opts ...RequestOption) (*http.Response, error)
- func (c *Client) Do(req *http.Request) (*http.Response, error)
- func (c *Client) Get(uri string, query url.Values, opts ...RequestOption) (*http.Response, error)
- func (c *Client) JSONReader(in any) (io.ReadCloser, error)
- func (c *Client) LogValue() slog.Value
- func (c *Client) Post(uri string, query url.Values, body io.ReadCloser, opts ...RequestOption) (*http.Response, error)
- func (c *Client) Put(uri string, query url.Values, body io.ReadCloser, opts ...RequestOption) (*http.Response, error)
- func (c *Client) RoundTrip(req *http.Request) (*http.Response, error)
- func (c *Client) Transport() http.RoundTripper
- type ClientWrapper
- type CompressionConfig
- type FIDOConfig
- type FIDOCredential
- type FIDOServer
- type FIDOStore
- type FIDOUser
- func (u *FIDOUser) MarshalJSON() ([]byte, error)
- func (u *FIDOUser) UnmarshalJSON(data []byte) error
- func (u *FIDOUser) WebAuthnCredentials() []webauthn.Credential
- func (u *FIDOUser) WebAuthnDisplayName() string
- func (u *FIDOUser) WebAuthnID() []byte
- func (u *FIDOUser) WebAuthnIcon() string
- func (u *FIDOUser) WebAuthnName() string
- type HTTPClient
- type HTTPExchange
- type Host
- type ID
- type KeyStore
- type MemoryAPIKeyStore
- func (s *MemoryAPIKeyStore) Delete(id string) error
- func (s *MemoryAPIKeyStore) GetByHash(hash string) (*APIKey, error)
- func (s *MemoryAPIKeyStore) GetByID(id string) (*APIKey, error)
- func (s *MemoryAPIKeyStore) GetByPrefix(prefix string) (*APIKey, error)
- func (s *MemoryAPIKeyStore) List() ([]*APIKey, error)
- func (s *MemoryAPIKeyStore) Save(key *APIKey) error
- func (s *MemoryAPIKeyStore) UpdateLastUsed(id string, t time.Time) error
- type MemoryCacheStore
- type MemoryFIDOStore
- func (s *MemoryFIDOStore) GetCredentials(userID []byte) ([]*FIDOCredential, error)
- func (s *MemoryFIDOStore) GetUser(userID []byte) (*FIDOUser, error)
- func (s *MemoryFIDOStore) GetUserByName(username string) (*FIDOUser, error)
- func (s *MemoryFIDOStore) SaveCredential(userID []byte, credential *FIDOCredential) error
- func (s *MemoryFIDOStore) SaveUser(user *FIDOUser) error
- func (s *MemoryFIDOStore) UpdateCredential(credentialID []byte, credential *FIDOCredential) error
- type MemoryKeyStore
- type MockEndpoint
- type MockServer
- type OIDC
- type Option
- func WithAPIKey(key string) Option
- func WithAutoRefreshToken(refresher *TokenRefresher) Option
- func WithBasicAuth(username, password string) Option
- func WithBearerToken(token string) Option
- func WithCircuitBreaker(cb *CircuitBreaker) Option
- func WithDebugFile(file string) Option
- func WithDoer(client HTTPClient) Option
- func WithHeader(key, value string) Option
- func WithLimiter(ctx context.Context, delay time.Duration, retries int, concurrency int, ...) Option
- func WithLogger(log log.Logger) Option
- func WithMaxResponseBytes(n int64) Option
- func WithOAuth2(clientID, clientSecret, tokenURL string, scopes ...string) Option
- func WithRetry(cfg *RetryConfig) Option
- func WithRoot(rootURL string) Option
- func WithSignature(signer *Signer) Option
- func WithTransport(transport http.RoundTripper) Option
- type ProxyOption
- type Register
- type ReplayOptions
- type ReplayRequest
- type ReplayResult
- type Replayer
- func (r *Replayer) Replay(ctx context.Context, req *ReplayRequest) *ReplayResult
- func (r *Replayer) ReplayBatch(ctx context.Context, requests []*ReplayRequest, parallel bool) []*ReplayResult
- func (r *Replayer) ReplayBatchWithOptions(ctx context.Context, requests []*ReplayRequest, opts ReplayOptions) []*ReplayResult
- type RequestOption
- type RetryConfig
- type Rewriter
- type Rule
- type Server
- type ServerOption
- func RateLimit(limit int, duration time.Duration) ServerOption
- func WithAPIKeyManager(manager *APIKeyManager, adminAuth gin.HandlerFunc) ServerOption
- func WithCORS(allowedOrigins []string, methods ...string) ServerOption
- func WithErrorHandler(handler func(ctx *gin.Context, err error, i int)) ServerOption
- func WithFIDO(cfg *FIDOConfig) ServerOption
- func WithHost(host string) ServerOption
- func WithImpl(impl any) ServerOption
- func WithMaxBodyBytes(n int64) ServerOption
- func WithMiddleware(middleware ...gin.HandlerFunc) ServerOption
- func WithPerIPRateLimit() ServerOption
- func WithPort(port int) ServerOption
- func WithServerLogger(logger log.Logger) ServerOption
- func WithShutdownTimeout(d time.Duration) ServerOption
- func WithSwaggerSpec(spec *openapi3.T) ServerOption
- func WithTLS(cert, key string) ServerOption
- func WithTrustedProxies(proxies []string) ServerOption
- func WithValidation(ctx context.Context, oidc string, audience string) ServerOption
- type SignatureAlgorithm
- type SignatureConfig
- type SignatureInfo
- type SignatureStats
- type Signer
- type TOKEN
- type Timeout
- type TokenRefresher
- type TrafficDiff
- type TrafficDiffer
- type Verifier
Constants ¶
const ( // DefaultRead is the default http.Server.ReadTimeout used by the proxy's // listener. DefaultRead = Timeout(5 * time.Second) // DefaultWrite is the default http.Server.WriteTimeout used by the // proxy's listener. DefaultWrite = Timeout(10 * time.Second) // DefaultReadHeader is the default http.Server.ReadHeaderTimeout used by // the proxy's listener. DefaultReadHeader = Timeout(5 * time.Second) // DefaultClient is the default overall client-side request timeout // (documented alongside DefaultResponse; see the http.Client/Transport // timeout fields these constants mirror). DefaultClient = Timeout(10 * time.Second) // DefaultResponse is the default response-header wait timeout, mirroring // http.Transport.ResponseHeaderTimeout. DefaultResponse = Timeout(10 * time.Second) // DefaultTLSHandshake is the default TLS handshake timeout, mirroring // http.Transport.TLSHandshakeTimeout. DefaultTLSHandshake = Timeout(10 * time.Second) // DefaultKeepAlive is the default connection keep-alive interval, // mirroring net.Dialer.KeepAlive. DefaultKeepAlive = Timeout(30 * time.Second) // DefaultExpect is the default "Expect: 100-continue" wait timeout, // mirroring http.Transport.ExpectContinueTimeout. DefaultExpect = Timeout(1 * time.Second) )
const ( // OAuth2Scopes was intended as the context key under which per-path // OAuth2 scope requirements would be stashed for downstream handlers to // read, but nothing in this package ever sets or reads it. QG-095 // (#271). // // Deprecated: unused; will be removed in the next major version. OAuth2Scopes = "oauth2_scopes" // ENV is the environment variable Gin reads its running mode from // (GIN_MODE). This package consults gin.Mode() (backed by the same // variable) rather than reading it directly; the constant documents // which variable that is. ENV = "GIN_MODE" // running environment // DEVENV is Gin's debug-mode value ("debug") for GIN_MODE/ENV. Gin // defaults to this mode when GIN_MODE is unset, which is exactly why // the insecure-dev bypass (see INSECURE_DEV) additionally requires an // explicit APIC_ENV=development assertion rather than trusting debug // mode alone (securex.AllowInsecureDev's condition 4). DEVENV = "debug" // development environment // INSECURE_DEV names the environment variable (APIC_INSECURE_DEV) that, // together with debug mode and (for per-request use) an explicit // APIC_ENV=development assertion, opts into the insecure-dev bypass: // permitting a plaintext (non-TLS) loopback-only listener in newListener, // and forcing CORS to allow all origins in WithCORS. See // securex.AllowInsecureDev/AllowInsecureDevAtStartup for the full gate. // Leaving this set to a truthy value in a production (release-mode) // deployment is refused fail-closed (the securex helpers panic) rather // than silently doing nothing. INSECURE_DEV = "APIC_INSECURE_DEV" )
const DefaultMaxResponseBytes int64 = 32 << 20
DefaultMaxResponseBytes is the secure-default ceiling on buffered response bodies (32 MiB). An unauthenticated/compromised upstream returning a multi-gigabyte body would otherwise OOM the client via io.ReadAll (PERF-0024). Raise or disable per-client with WithMaxResponseBytes.
const MaxSignatureBodyBytes int64 = 32 << 20 // 32 MiB
MaxSignatureBodyBytes caps the request body size that signature signing and verification will hash for the Digest header. Bodies larger than this are rejected with ErrSignatureBodyTooLarge instead of being buffered into memory. The cap matches PERF-0014's response body cap so signed flows on either side of the wire share the same backpressure ceiling.
Variables ¶
var ( // ErrAPIKeyNotFound is returned by APIKeyStore implementations when // a lookup or delete targets a key that is not present. Replaces the // six "API key not found" anonymous errors.New sites in apikey.go. ErrAPIKeyNotFound = errors.New("htpx: api key not found") // ErrAPIKeyInvalid is returned by APIKeyManager.ValidateKey when the // provided key string does not hash to a known stored key. The // message intentionally avoids leaking whether the key was unknown // versus mismatched (to limit oracle behavior under brute-force). ErrAPIKeyInvalid = errors.New("htpx: invalid api key") // ErrAPIKeyDisabled is returned by APIKeyManager.ValidateKey when // the key exists but is not enabled. ErrAPIKeyDisabled = errors.New("htpx: api key is disabled") // ErrAPIKeyExpired is returned by APIKeyManager.ValidateKey when the // key exists, is enabled, but its ExpiresAt is in the past. ErrAPIKeyExpired = errors.New("htpx: api key is expired") // ErrFIDOUserNotFound is returned by FIDOStore implementations when // a user lookup misses. Returned by GetUser, GetUserByName, and // GetCredentials in MemoryFIDOStore. ErrFIDOUserNotFound = errors.New("htpx: fido user not found") // ErrFIDOConfigRequired is returned by NewFIDOServer when the // supplied configuration pointer is nil. ErrFIDOConfigRequired = errors.New("htpx: fido config is required") // ErrOIDCEndpointEmpty is returned by the API server constructor // when an authenticated build is requested without an OIDC endpoint // AND insecure-dev startup is not allowed. ErrOIDCEndpointEmpty = errors.New("htpx: oidc endpoint is empty") // ErrValidationSpecMissing is returned by WithValidation when OIDC // auth is configured but no OpenAPI spec was supplied. The OpenAPI // request validator mounted from the spec is the only component // that invokes the authentication function, so without a spec every // `security:`-declared route would be served unauthenticated // (fail-open). htpx has no embedded spec to fall back on; pass // WithSwaggerSpec before WithValidation. Sibling of appsec N-01 // fixed in api/ (73d6568). ErrValidationSpecMissing = errors.New( "htpx: oidc auth is configured but no OpenAPI spec was supplied;" + " pass WithSwaggerSpec before WithValidation so the auth" + " enforcer can be mounted", ) // ErrRegisterFunctionNil is returned by api.New when called without // a route registration function. ErrRegisterFunctionNil = errors.New("htpx: register function is nil") // ErrInsecureDevNonLoopback is returned by newListener when // APIC_INSECURE_DEV is set but the host is not a loopback address. ErrInsecureDevNonLoopback = errors.New("htpx: APIC_INSECURE_DEV is only allowed on loopback hosts") // ErrMissingTLSPaths is returned by newListener when neither // insecure-dev nor a cert/key path pair is provided. ErrMissingTLSPaths = errors.New("htpx: missing TLS cert/key paths") // ErrTransportNil is returned by WithTransport when the supplied // http.RoundTripper is nil. ErrTransportNil = errors.New("htpx: transport is nil") // ErrClientNil is returned by WithDoer when the supplied // HTTPClient is nil. ErrClientNil = errors.New("htpx: client is nil") // ErrCircuitBreakerOpen is returned by CircuitBreaker.Execute when // the breaker is in the open state and the call is short-circuited. ErrCircuitBreakerOpen = errors.New("htpx: circuit breaker is open") // ErrCacheMiss is returned by MemoryCacheStore.Get when no entry // exists for the requested key. ErrCacheMiss = errors.New("htpx: cache miss") // ErrCacheExpired is returned by MemoryCacheStore.Get when an entry // exists but its TTL has elapsed. ErrCacheExpired = errors.New("htpx: cache expired") // ErrReadResponseBody is wrapped via errors.Join when ReadJSON fails // to read the response body off the wire. ErrReadResponseBody = errors.New("htpx: failed to read response body") // ErrUnmarshalResponseBody is wrapped via errors.Join when ReadJSON // fails to decode the response body as JSON. ErrUnmarshalResponseBody = errors.New("htpx: failed to unmarshal response body") // ErrResponseBodyTooLarge is returned when a response body exceeds the // client's maxResponseBytes ceiling (see WithMaxResponseBytes). PERF-0024. ErrResponseBodyTooLarge = errors.New("htpx: response body exceeds maximum") // ErrNoTokenToRefresh is returned by TokenRefresher.Start when the // refresher has no current token to refresh. ErrNoTokenToRefresh = errors.New("htpx: no token to refresh") // ErrAuthTokenMissing is returned by AuthToken when no JWT is // present in the request context. ErrAuthTokenMissing = errors.New("htpx: failed to get auth token") // ErrJWTMissingScopeClaim is returned by OIDC.Authenticate when // scopes are required but the token has no "scope" claim. ErrJWTMissingScopeClaim = errors.New("htpx: missing scope claim in jwt") // ErrJWTScopeClaimNotString is returned by OIDC.Authenticate when // the "scope" claim exists but is not a string. ErrJWTScopeClaimNotString = errors.New("htpx: jwt scope claim is not a string") // ErrJWKEndpointEmpty is returned by OIDC.UnmarshalJSON when the // JWK endpoint returns no keys at cold start. ErrJWKEndpointEmpty = errors.New("htpx: no keys found in jwk endpoint") // ErrUnsupportedJWTAlg is returned by ExtractToken when an inbound // token declares a JWS algorithm outside the asymmetric allowlist // (e.g. "none" or HS256/384/512). Mirrors api/oidc.go (QG-060) so the // JWKS verification path cannot be downgraded via algorithm // confusion. QG-081. ErrUnsupportedJWTAlg = errors.New("htpx: unsupported jwt algorithm") // ErrSignatureConfigRequired is returned by NewSigner when given a // nil SignatureConfig. ErrSignatureConfigRequired = errors.New("htpx: signature config is required") // ErrSignatureKeyIDRequired is returned by NewSigner when the // SignatureConfig has an empty KeyID. ErrSignatureKeyIDRequired = errors.New("htpx: signature key id is required") // ErrSignaturePrivateKeyRequired is returned by NewSigner when the // SignatureConfig has a nil PrivateKey. ErrSignaturePrivateKeyRequired = errors.New("htpx: signature private key is required") // ErrSignatureHMACKeyType is returned by Signer.sign and Verifier.verify // when the configured key for HMAC algorithms is not a []byte. ErrSignatureHMACKeyType = errors.New("htpx: hmac key must be []byte") // ErrSignatureRSAPrivateKey is returned by Signer.sign when the // configured private key is not an *rsa.PrivateKey. ErrSignatureRSAPrivateKey = errors.New("htpx: rsa private key required") // ErrSignatureECDSAPrivateKey is returned by Signer.sign when the // configured private key is not an *ecdsa.PrivateKey. ErrSignatureECDSAPrivateKey = errors.New("htpx: ecdsa private key required") // ErrSignatureED25519PrivateKey is returned by Signer.sign when the // configured private key is not an ed25519.PrivateKey. ErrSignatureED25519PrivateKey = errors.New("htpx: ed25519 private key required") // ErrSignatureMissingAuthHeader is returned by VerifyRequest when // the request has no Authorization header. ErrSignatureMissingAuthHeader = errors.New("htpx: missing authorization header") // ErrSignatureDateHeaderMissing is returned by VerifyRequest when // the signed headers include "date" but the request has none. ErrSignatureDateHeaderMissing = errors.New("htpx: date header required but not found") // ErrSignatureClockSkew is returned by VerifyRequest when the date // header is outside the configured clock-skew window. The message // intentionally retains the substring "clock skew" for compatibility // with existing tests that match on it. ErrSignatureClockSkew = errors.New("htpx: date header outside allowed clock skew") // ErrSignatureHMACVerify is returned by Verifier.verify when the // HMAC signature does not match the expected value. ErrSignatureHMACVerify = errors.New("htpx: hmac verification failed") // ErrSignatureRSAPublicKey is returned by Verifier.verify when the // configured public key is not an *rsa.PublicKey. ErrSignatureRSAPublicKey = errors.New("htpx: rsa public key required") // ErrSignatureECDSAPublicKey is returned by Verifier.verify when the // configured public key is not an *ecdsa.PublicKey. ErrSignatureECDSAPublicKey = errors.New("htpx: ecdsa public key required") // ErrSignatureECDSAVerify is returned by Verifier.verify when the // ECDSA signature does not match. ErrSignatureECDSAVerify = errors.New("htpx: ecdsa verification failed") // ErrSignatureED25519PublicKey is returned by Verifier.verify when // the configured public key is not an ed25519.PublicKey. ErrSignatureED25519PublicKey = errors.New("htpx: ed25519 public key required") // ErrSignatureED25519Verify is returned by Verifier.verify when the // Ed25519 signature does not match. ErrSignatureED25519Verify = errors.New("htpx: ed25519 verification failed") // ErrSignatureHeaderFormat is returned by parseSignatureHeader when // the Authorization header does not start with "Signature ". ErrSignatureHeaderFormat = errors.New("htpx: invalid signature header format") // ErrSignatureHeaderIncomplete is returned by parseSignatureHeader // when the Signature header is missing required fields. ErrSignatureHeaderIncomplete = errors.New("htpx: incomplete signature header") // ErrSignatureDigestHeaderMissing is returned by verifyDigest when // the request is missing the Digest header that was signed. ErrSignatureDigestHeaderMissing = errors.New("htpx: digest header required but not found") // ErrSignatureDigestUnsupported is returned by verifyDigest when the // Digest header uses an algorithm other than SHA-256. ErrSignatureDigestUnsupported = errors.New("htpx: only SHA-256 digest supported") // ErrSignatureDigestMismatch is returned by verifyDigest when the // computed digest does not match the value supplied in the header. ErrSignatureDigestMismatch = errors.New("htpx: digest mismatch") // ErrSignaturePEMDecode is returned by LoadPrivateKey and // LoadPublicKey when the PEM block cannot be decoded. ErrSignaturePEMDecode = errors.New("htpx: failed to decode PEM block") )
Sentinel errors for the htpx package. Kept at package level per repo conventions. Messages use a lowercase, package-prefixed, diagnostic form so log output reads naturally; errors.Is callers should rely on pointer equality rather than substring matching of Error().
Sentinels that have richer documentation (ErrInvalidURL, ErrInvalidScheme, ErrAuthHeaderMissing, ErrMalformedAuthHeader, ErrEmptyAudience, ErrSignatureBodyTooLarge, ErrNotApplicable) are declared at their use sites.
var ( GET = WithMethod(http.MethodGet) POST = WithMethod(http.MethodPost) PUT = WithMethod(http.MethodPut) DELETE = WithMethod(http.MethodDelete) PATCH = WithMethod(http.MethodPatch) )
var ErrAuthHeaderMissing = errors.New("htpx: authorization header missing")
ErrAuthHeaderMissing is returned by ExtractToken when the request carries no Authorization header. OIDC.JWT treats this specific error as an anonymous request rather than aborting it.
var ErrEmptyAudience = errors.New("htpx: empty audience")
ErrEmptyAudience is returned by ExtractToken when the OIDC config's Aud field is empty, since an empty audience would make JWT audience validation meaningless (any token would match).
var ErrInvalidScheme = errors.New("htpx: invalid scheme")
ErrInvalidScheme is returned by LoadOIDC when the discovery URL's scheme is not "https", and by OIDC.UnmarshalJSON when any endpoint advertised in the discovery document (jwks_uri, authorization_endpoint, token_endpoint, userinfo_endpoint) is not https. OIDC endpoints are trust anchors for token verification, so a non-TLS scheme is refused rather than silently accepted.
var ErrInvalidURL = errors.New("htpx: invalid url")
ErrInvalidURL is returned when a configured URL fails to parse as a valid url.URL.
var ErrIssuerMismatch = errors.New("htpx: discovery issuer does not match the discovery URL")
ErrIssuerMismatch is returned by LoadOIDC when the discovery document's "issuer" does not match the discovery URL it was fetched from (OIDC Discovery 1.0 §4.3: issuer + "/.well-known/openid-configuration" MUST equal the request URL). Without this check, a discovery response could advertise an arbitrary issuer that ExtractToken would trust as the expected iss, letting a compromised or misconfigured discovery endpoint redirect trust to an issuer the operator never intended (SEC-0057, GitLab #311).
var ErrJWKSTransportWrapped = errors.New("htpx: jwks transport is a wrapped Doer (ClientWrapper), not a single-hop http.RoundTripper")
ErrJWKSTransportWrapped is returned by LoadOIDC (via jwksHTTPClient) when the client's transport is a ClientWrapper -- a whole *http.Client Do method (WithDoer, WithOAuth2, WithLimiter) masquerading as a RoundTripper. Such a transport resolves an entire redirect chain internally before oidcx's outer CheckRedirect (refuseJWKSRedirects) ever sees it, silently reopening the SEC-0057 (GitLab #311) JWKS-redirect bypass. The JWKS fetch needs a genuine single-hop RoundTripper, so this fails closed instead of guessing.
var ErrMalformedAuthHeader = errors.New("htpx: malformed authorization header")
ErrMalformedAuthHeader is returned by ExtractToken when the Authorization header is present but does not start with the "Bearer " prefix, and by rejectNonAsymmetricAlg when the token's JOSE header cannot be parsed.
var ErrNotApplicable = errors.New("htpx: rewriter not applicable")
ErrNotApplicable is returned by Rewriter.Process when in's host (or port, if From specifies one) does not match From, meaning this rule does not apply to the given request.
var ErrSignatureBodyTooLarge = errors.New("htpx: signed-request body exceeds size cap")
ErrSignatureBodyTooLarge is returned by computeDigest (via SignRequest and VerifyRequest) when the request body exceeds MaxSignatureBodyBytes. It is wrapped, so callers should use errors.Is to test it.
Functions ¶
func APIKeyMiddleware ¶
func APIKeyMiddleware(cfg *APIKeyAuthConfig) gin.HandlerFunc
APIKeyMiddleware creates a Gin middleware for API key authentication
func Args ¶
Args returns the shared cli.Args definitions for an htpx-based server (host, port, cert, key, ca, oidc, aud, cors, allowed-proxies, rate-limit, rate-duration), labeling the host argument's description with app. The definitions are built exactly once per process via sync.Once: only the first call's app value is ever used, and every call (with any app) thereafter returns that same shared cli.Args map.
func AuthToken ¶
AuthToken retrieves the verified JWT token that OIDC.JWT previously stashed on ctx under ctxTokenKey. Returns ErrAuthTokenMissing if ctx carries no such value (e.g. the JWT middleware never ran, or ran but skipped verification). The returned jwt.Token carries the caller's claims and must be treated as sensitive identity/authorization data.
func CacheMiddleware ¶
func CacheMiddleware(cfg *CacheConfig) gin.HandlerFunc
CacheMiddleware creates a response caching middleware
func CircuitBreakerMiddleware ¶
func CircuitBreakerMiddleware(cb *CircuitBreaker) gin.HandlerFunc
CircuitBreakerMiddleware creates a circuit breaker middleware for Gin
func Clone ¶
Clone returns a shallow copy of in with its URL (CloneURL) and Header (CloneHeader) deep-copied, so mutating the clone's URL/headers (as proxy Rules do) does not affect the original request. The Body reference itself is shared (not cloned), preserving the original method and body for forwarding.
func CloneHeader ¶
CloneHeader returns a deep copy of in: a new http.Header whose per-key value slices are independent copies, so appending to or mutating the clone's values never mutates in's (multi-value-header aware).
func CloneResponse ¶
CloneResponse returns a shallow copy of in with its Header deep-copied (CloneHeader), so mutating the clone's headers does not affect in's. The Body reference itself is shared, not cloned.
func CloneURL ¶
CloneURL returns a shallow copy of in as a new *url.URL value, so mutating the fields of the returned URL does not affect in.
func CompressionMiddleware ¶
func CompressionMiddleware(cfg *CompressionConfig) gin.HandlerFunc
CompressionMiddleware creates a response compression middleware
func ConstantTimeCompare ¶
ConstantTimeCompare performs a constant-time comparison of two strings
func FIDOMiddleware ¶
func FIDOMiddleware(store FIDOStore) gin.HandlerFunc
FIDOMiddleware creates a Gin middleware for FIDO2 authentication
func GenerateKeyPair ¶
GenerateKeyPair generates a new RSA key pair for testing
func GenerateTLSCertificate ¶
GenerateTLSCertificate generates a self-signed TLS certificate.
func LoadPrivateKey ¶
LoadPrivateKey loads a private key from PEM bytes
func LoadPublicKey ¶
LoadPublicKey loads a public key from PEM bytes
func Proxy ¶
Proxy runs a forward HTTP proxy on listener l until ctx is canceled, applying opts (WithClient, WithRule, WithSubnetFilter, WithCertificate, WithTLSConfig, WithRewriters) to configure it first. If WithTLSConfig set a *tls.Config, l is wrapped in a TLS listener using WithCertificate's certgen as GetCertificate. Any WithRewriters entries are collapsed into a single "rewriters" Rule that applies the first Rewriter whose Process succeeds. Blocks serving requests (see (*proxy).serve) until the listener is closed or ctx is done, at which point the server shuts down gracefully. CONNECT requests are not implemented and receive 501 Not Implemented.
func RequestLogger ¶
func RequestLogger(logger *slog.Logger) gin.HandlerFunc
RequestLogger returns the Gin request-logging middleware htpx installs on every server. It emits one structured slog record per request via github.com/samber/slog-gin with slog-gin's DefaultConfig: the request method and path are logged under a "request" group (request.method, request.path), the response status under a "response" group (response.status), and a request id under the "id" key (taken from the inbound X-Request-Id header, or generated when absent). Passing a logger backed by a custom slog.Handler lets callers/tests capture and assert that contract; New uses RequestLogger(slog.Default()) so the production wiring and the tested wiring share one construction point (GAP-0057).
func RequireScope ¶
func RequireScope(scope string) gin.HandlerFunc
RequireScope returns a middleware that requires a specific scope
func SignatureMiddleware ¶
func SignatureMiddleware(verifier *Verifier) gin.HandlerFunc
SignatureMiddleware creates a Gin middleware for signature verification
func To ¶
To reads and closes resp.Body, decoding it as JSON into a zero value of T and returning it. If resp.StatusCode indicates an error (>= 300), the body is read only as a diagnostic (truncation on a read failure is tolerated) and To instead returns the zero value of T along with an error describing the status code/text; a body-read failure wraps ErrReadResponseBody and a JSON-decode failure wraps ErrUnmarshalResponseBody. Callers must not also close resp.Body themselves.
Types ¶
type APIKey ¶
type APIKey struct {
ID string `json:"id"`
Name string `json:"name"`
KeyHash string `json:"key_hash"`
Prefix string `json:"prefix"`
Scopes []string `json:"scopes"`
Metadata map[string]any `json:"metadata"`
Created time.Time `json:"created"`
LastUsed time.Time `json:"last_used"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
RateLimitID string `json:"rate_limit_id"`
Enabled bool `json:"enabled"`
}
APIKey represents an API key
type APIKeyAuthConfig ¶
type APIKeyAuthConfig struct {
// Manager is the API key manager
Manager *APIKeyManager
// HeaderName is the header to check for the API key (default: X-API-Key)
HeaderName string
// RequireScopes are the scopes required for this route
RequireScopes []string
// Optional makes the API key optional (for public endpoints with optional auth)
Optional bool
}
APIKeyAuthConfig configures API key authentication
type APIKeyManager ¶
type APIKeyManager struct {
// contains filtered or unexported fields
}
APIKeyManager manages API keys
func NewAPIKeyManager ¶
func NewAPIKeyManager(store APIKeyStore, logger *slog.Logger) *APIKeyManager
NewAPIKeyManager creates a new API key manager
func (*APIKeyManager) DeleteKey ¶
func (m *APIKeyManager) DeleteKey(id string) error
DeleteKey deletes an API key
func (*APIKeyManager) GenerateKey ¶
func (m *APIKeyManager) GenerateKey(name string, scopes []string, metadata map[string]any, expiresAt *time.Time) (*APIKey, string, error)
GenerateKey generates a new API key
func (*APIKeyManager) GetKey ¶
func (m *APIKeyManager) GetKey(id string) (*APIKey, error)
GetKey returns a specific API key by ID
func (*APIKeyManager) ListKeys ¶
func (m *APIKeyManager) ListKeys() ([]*APIKey, error)
ListKeys returns all API keys
func (*APIKeyManager) RevokeKey ¶
func (m *APIKeyManager) RevokeKey(id string) error
RevokeKey revokes an API key
func (*APIKeyManager) ValidateKey ¶
func (m *APIKeyManager) ValidateKey(keyStr string) (*APIKey, error)
ValidateKey validates an API key and returns the key object
type APIKeyStats ¶
type APIKeyStats struct {
// contains filtered or unexported fields
}
APIKeyStats tracks API key usage statistics
func (*APIKeyStats) GetAllStats ¶
func (s *APIKeyStats) GetAllStats() map[string]any
GetAllStats returns stats for all API keys
func (*APIKeyStats) GetStats ¶
func (s *APIKeyStats) GetStats(keyID string) map[string]any
GetStats returns stats for a specific API key
func (*APIKeyStats) RecordRequest ¶
func (s *APIKeyStats) RecordRequest(keyID string, err error)
RecordRequest records a request for an API key
type APIKeyStore ¶
type APIKeyStore interface {
// GetByHash retrieves an API key by its hash
GetByHash(hash string) (*APIKey, error)
// GetByID retrieves an API key by its ID
GetByID(id string) (*APIKey, error)
// GetByPrefix retrieves an API key by its prefix
GetByPrefix(prefix string) (*APIKey, error)
// Save saves or updates an API key
Save(key *APIKey) error
// Delete deletes an API key by ID
Delete(id string) error
// List returns all API keys
List() ([]*APIKey, error)
// UpdateLastUsed updates the last used timestamp
UpdateLastUsed(id string, t time.Time) error
}
APIKeyStore defines the interface for storing and retrieving API keys
type CacheConfig ¶
type CacheConfig struct {
// Store is the cache store
Store CacheStore
// TTL is the default cache time-to-live
TTL time.Duration
// KeyFunc generates cache keys (default: method + URL)
KeyFunc func(*gin.Context) string
// ShouldCache determines if a response should be cached
ShouldCache func(*gin.Context) bool
}
CacheConfig configures response caching
type CacheEntry ¶
type CacheEntry struct {
StatusCode int
Headers http.Header
Body []byte
Timestamp time.Time
TTL time.Duration
}
CacheEntry represents a cached response
func (*CacheEntry) IsExpired ¶
func (e *CacheEntry) IsExpired() bool
IsExpired checks if the cache entry has expired
type CacheStore ¶
type CacheStore interface {
Get(key string) (*CacheEntry, error)
Set(key string, entry *CacheEntry) error
Delete(key string) error
Clear() error
}
CacheStore defines the interface for caching
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker implements the circuit breaker pattern
func NewCircuitBreaker ¶
func NewCircuitBreaker(cfg *CircuitBreakerConfig) *CircuitBreaker
NewCircuitBreaker creates a new circuit breaker
func (*CircuitBreaker) Execute ¶
func (cb *CircuitBreaker) Execute(fn func() error) error
Execute executes a function with circuit breaker protection
func (*CircuitBreaker) State ¶
func (cb *CircuitBreaker) State() CircuitState
State returns the current circuit state
func (*CircuitBreaker) Stats ¶
func (cb *CircuitBreaker) Stats() map[string]any
Stats returns circuit breaker statistics
type CircuitBreakerConfig ¶
type CircuitBreakerConfig struct {
// MaxFailures is the number of failures before opening the circuit
MaxFailures int64
// Timeout is how long to wait before attempting to close the circuit
Timeout time.Duration
// FailureThreshold is the percentage of failures to trigger circuit open (0-100)
FailureThreshold float64
// MinRequests is the minimum number of requests before evaluating failure rate
MinRequests int64
// OnStateChange is called when the circuit state changes
OnStateChange func(from, to CircuitState)
}
CircuitBreakerConfig configures a circuit breaker
type CircuitState ¶
type CircuitState int
CircuitState represents the state of a circuit breaker
const ( // CircuitClosed is the normal operating state: requests are allowed // through and failures/successes are tallied to decide whether to trip // the breaker open. CircuitClosed CircuitState = iota // CircuitOpen means the breaker has tripped: requests are rejected with // ErrCircuitBreakerOpen (via CircuitBreaker.allow) until Timeout has // elapsed since the last recorded failure, at which point the breaker // moves to CircuitHalfOpen. CircuitOpen // CircuitHalfOpen is the trial state entered after Timeout elapses in // CircuitOpen: a single request is allowed through to probe the // downstream; success closes the circuit again, failure reopens it. CircuitHalfOpen )
func (CircuitState) String ¶
func (s CircuitState) String() string
String renders s as one of "closed", "open", "half-open", or "unknown" for a state value outside the three defined constants.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is htpx's HTTP client: a configurable http.RoundTripper wrapper that layers default headers, request/response logging, an optional debug body dump, and a bounded response-body reader on top of an underlying http.RoundTripper. Construct one with NewClient and Option functions (WithTransport, WithRoot, WithHeader, WithBearerToken, WithOAuth2, WithLimiter, WithRetry, WithCircuitBreaker, WithDebugFile, WithMaxResponseBytes, ...); zero-value Client is not usable directly.
func NewClient ¶
NewClient builds a Client bound to ctx, applying opts in order. Defaults are http.DefaultTransport as the underlying RoundTripper, the package's default log.Logger, an empty set of default request headers, and DefaultMaxResponseBytes as the response-body ceiling; any Option (WithTransport, WithHeader, WithRoot, WithMaxResponseBytes, auth helpers like WithBearerToken, etc.) can override those before the Client is returned. Returns the first error an Option produces.
func (*Client) Close ¶
Close releases resources held by c, closing the debug dump file opened by WithDebugFile (if any). It is a no-op when no debug file was configured. Any close error is returned (wrapped with errors.Join if more than one resource is ever added).
func (*Client) Delete ¶
func (c *Client) Delete( uri string, query url.Values, opts ...RequestOption, ) (*http.Response, error)
Delete issues a DELETE request to uri with query as the URL's query string, after applying opts and the client's default headers.
func (*Client) Do ¶
Do sends req through the client's configured underlying transport and, if a response is returned, captures a bounded prefix of its body to the debug file (if WithDebugFile was set) before handing the response back to the caller. Unlike request/Get/Post/Put/Delete, Do does not apply the client's default headers or query normalization — callers using Do directly are responsible for building a complete *http.Request.
func (*Client) Get ¶
func (c *Client) Get( uri string, query url.Values, opts ...RequestOption, ) (*http.Response, error)
Get issues a GET request to uri (resolved against the client's root URL via WithRoot, if any) with query encoded as the URL's query string, after applying opts and the client's default headers. Returns the raw *http.Response for the caller to read/close; pair with To for JSON decoding.
func (*Client) JSONReader ¶
func (c *Client) JSONReader(in any) (io.ReadCloser, error)
JSONReader marshals in to JSON and wraps it in a no-op io.ReadCloser suitable for passing as the body argument to Post/Put (whose signatures require io.ReadCloser).
func (*Client) LogValue ¶
LogValue implements slog.LogValuer so logging a Client (as the "client" attribute used throughout this package) emits only its root URL and default headers rather than the full struct, avoiding the underlying transport, debug file handle, and mutex state. Note this does NOT redact header values: if an Option such as WithBearerToken or WithBasicAuth set a credential into the default headers, that credential is emitted as-is here, so callers should not log this value in contexts where an Authorization header would be sensitive.
func (*Client) Post ¶
func (c *Client) Post( uri string, query url.Values, body io.ReadCloser, opts ...RequestOption, ) (*http.Response, error)
Post issues a POST request to uri with query as the URL's query string and body as the request body (see JSONReader for building one from a Go value), after applying opts and the client's default headers.
func (*Client) Put ¶
func (c *Client) Put( uri string, query url.Values, body io.ReadCloser, opts ...RequestOption, ) (*http.Response, error)
Put issues a PUT request to uri with query as the URL's query string and body as the request body, after applying opts and the client's default headers.
func (*Client) RoundTrip ¶
RoundTrip implements http.RoundTripper by delegating to Do, letting a Client itself be used as another http.Client's Transport.
func (*Client) Transport ¶ added in v0.18.3
func (c *Client) Transport() http.RoundTripper
Transport returns the client's underlying http.RoundTripper. It exists so callers that must build their own *http.Client sharing this client's TLS trust/transport configuration (proxy, mTLS, custom dialer, pinned CA) can do so without duplicating it -- e.g. wrapping it in a *http.Client that carries a policy (redirect refusal, a finite Timeout) this Client itself does not expose a way to set. SEC-0057 (GitLab #311).
type ClientWrapper ¶
ClientWrapper adapts a plain request-handling function into both http.RoundTripper (via RoundTrip) and an ad hoc "Do"-style caller (via Do), letting a *http.Client's Do method (e.g. from clientcredentials.Config.Client, or any HTTPClient) be installed as a Client's transport.
type CompressionConfig ¶
type CompressionConfig struct {
// Level is the compression level (1-9 for gzip)
Level int
// MinSize is the minimum response size to compress (bytes)
MinSize int
// Types are the content types to compress
Types []string
}
CompressionConfig configures CompressionMiddleware's gzip response compression.
type FIDOConfig ¶
type FIDOConfig struct {
// RPDisplayName is the relying party display name (e.g., "My App")
RPDisplayName string
// RPID is the relying party ID (e.g., "example.com")
RPID string
// RPOrigins are the allowed origins for WebAuthn requests
RPOrigins []string
// Timeout is the timeout for WebAuthn ceremonies (default: 60s)
Timeout time.Duration
// Debug enables debug logging
Debug bool
// Logger for WebAuthn operations
Logger *slog.Logger
// Store is the credential store
Store FIDOStore
}
FIDOConfig configures WebAuthn/FIDO2 authentication
type FIDOCredential ¶
type FIDOCredential struct {
ID []byte `json:"id"`
PublicKey []byte `json:"public_key"`
AttestationType string `json:"attestation_type"`
Transport []protocol.AuthenticatorTransport `json:"transport"`
Flags webauthn.CredentialFlags `json:"flags"`
Authenticator webauthn.Authenticator `json:"authenticator"`
BackupEligible bool `json:"backup_eligible"`
BackupState bool `json:"backup_state"`
Created time.Time `json:"created"`
LastUsed time.Time `json:"last_used"`
}
FIDOCredential represents a WebAuthn credential
func FromWebAuthn ¶
func FromWebAuthn(cred *webauthn.Credential) *FIDOCredential
FromWebAuthn creates a FIDOCredential from webauthn.Credential
func (*FIDOCredential) ToWebAuthn ¶
func (c *FIDOCredential) ToWebAuthn() webauthn.Credential
ToWebAuthn converts FIDOCredential to webauthn.Credential
type FIDOServer ¶
type FIDOServer struct {
// contains filtered or unexported fields
}
FIDOServer provides WebAuthn/FIDO2 authentication handlers
func NewFIDOServer ¶
func NewFIDOServer(cfg *FIDOConfig) (*FIDOServer, error)
NewFIDOServer creates a new FIDO server
func (*FIDOServer) BeginLogin ¶
func (s *FIDOServer) BeginLogin(c *gin.Context)
BeginLogin starts the FIDO2 authentication ceremony
func (*FIDOServer) BeginRegistration ¶
func (s *FIDOServer) BeginRegistration(c *gin.Context)
BeginRegistration starts the FIDO2 registration ceremony
func (*FIDOServer) FinishLogin ¶
func (s *FIDOServer) FinishLogin(c *gin.Context)
FinishLogin completes the FIDO2 authentication ceremony
func (*FIDOServer) FinishRegistration ¶
func (s *FIDOServer) FinishRegistration(c *gin.Context)
FinishRegistration completes the FIDO2 registration ceremony
func (*FIDOServer) RegisterRoutes ¶
func (s *FIDOServer) RegisterRoutes(r gin.IRouter)
RegisterRoutes registers FIDO2 routes on a Gin router
type FIDOStore ¶
type FIDOStore interface {
// GetUser retrieves a user by ID
GetUser(userID []byte) (*FIDOUser, error)
// GetUserByName retrieves a user by username
GetUserByName(username string) (*FIDOUser, error)
// SaveUser saves or updates a user
SaveUser(user *FIDOUser) error
// SaveCredential saves a credential for a user
SaveCredential(userID []byte, credential *FIDOCredential) error
// GetCredentials retrieves all credentials for a user
GetCredentials(userID []byte) ([]*FIDOCredential, error)
// UpdateCredential updates a credential (e.g., sign count)
UpdateCredential(credentialID []byte, credential *FIDOCredential) error
}
FIDOStore defines the interface for storing and retrieving WebAuthn credentials
type FIDOUser ¶
type FIDOUser struct {
ID []byte `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
Credentials []*FIDOCredential
}
FIDOUser represents a WebAuthn user
func GetFIDOUser ¶
GetFIDOUser retrieves the authenticated FIDO user from the context
func (*FIDOUser) MarshalJSON ¶
MarshalJSON implements json.Marshaler for FIDOUser
func (*FIDOUser) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler for FIDOUser
func (*FIDOUser) WebAuthnCredentials ¶
func (u *FIDOUser) WebAuthnCredentials() []webauthn.Credential
WebAuthnCredentials returns the user's credentials
func (*FIDOUser) WebAuthnDisplayName ¶
WebAuthnDisplayName returns the display name for WebAuthn
func (*FIDOUser) WebAuthnID ¶
WebAuthnID returns the user ID for WebAuthn
func (*FIDOUser) WebAuthnIcon ¶
WebAuthnIcon returns the user icon URL (deprecated in WebAuthn spec)
func (*FIDOUser) WebAuthnName ¶
WebAuthnName returns the username for WebAuthn
type HTTPClient ¶
HTTPClient is the minimal Do(req) contract WithDoer accepts, satisfied by *http.Client and any compatible wrapper.
type HTTPExchange ¶
type HTTPExchange struct {
Request *ReplayRequest `json:"request"`
Response *ReplayResult `json:"response"`
}
HTTPExchange represents an HTTP request/response pair
type Host ¶
type Host string
Host is a "host" or "host:port" value, used by Rewriter to describe the source and destination endpoints of a rewrite rule.
type ID ¶
type ID string
ID is a string identifier type reserved for typed server/resource identifiers; it is not currently used internally by this package.
type KeyStore ¶
type KeyStore interface {
// GetPublicKey retrieves a public key by key ID
GetPublicKey(keyID string) (any, error)
}
KeyStore provides public keys for signature verification
type MemoryAPIKeyStore ¶
type MemoryAPIKeyStore struct {
// contains filtered or unexported fields
}
MemoryAPIKeyStore is an in-memory implementation of APIKeyStore
func NewMemoryAPIKeyStore ¶
func NewMemoryAPIKeyStore() *MemoryAPIKeyStore
NewMemoryAPIKeyStore creates a new in-memory API key store
func (*MemoryAPIKeyStore) Delete ¶
func (s *MemoryAPIKeyStore) Delete(id string) error
Delete removes the key with the given ID from all three indexes (by ID, hash, and prefix). Returns ErrAPIKeyNotFound if no such key is stored.
func (*MemoryAPIKeyStore) GetByHash ¶
func (s *MemoryAPIKeyStore) GetByHash(hash string) (*APIKey, error)
GetByHash looks up a key by its stored SHA-256 hash (never the raw key material, which is never persisted). Returns ErrAPIKeyNotFound if no key has that hash.
func (*MemoryAPIKeyStore) GetByID ¶
func (s *MemoryAPIKeyStore) GetByID(id string) (*APIKey, error)
GetByID looks up a key by its generated ID. Returns ErrAPIKeyNotFound if no key with that ID is stored.
func (*MemoryAPIKeyStore) GetByPrefix ¶
func (s *MemoryAPIKeyStore) GetByPrefix(prefix string) (*APIKey, error)
GetByPrefix looks up a key by its short (first-8-character) display prefix, the non-secret identifier shown to users alongside a masked key. Returns ErrAPIKeyNotFound if no key with that prefix is stored.
func (*MemoryAPIKeyStore) List ¶
func (s *MemoryAPIKeyStore) List() ([]*APIKey, error)
List returns every stored key in unspecified order. Never returns an error; the signature exists to satisfy APIKeyStore for backing stores whose listing can fail.
func (*MemoryAPIKeyStore) Save ¶
func (s *MemoryAPIKeyStore) Save(key *APIKey) error
Save inserts or overwrites key, indexing it by ID, hash, and prefix so it is reachable through any of GetByID/GetByHash/GetByPrefix. Never returns an error; the signature exists to satisfy APIKeyStore for backing stores that can fail (e.g. a database-backed implementation).
func (*MemoryAPIKeyStore) UpdateLastUsed ¶
func (s *MemoryAPIKeyStore) UpdateLastUsed(id string, t time.Time) error
UpdateLastUsed records t as the key's most recent-use timestamp in place. Returns ErrAPIKeyNotFound if no key with that ID is stored.
type MemoryCacheStore ¶
type MemoryCacheStore struct {
// contains filtered or unexported fields
}
MemoryCacheStore is an in-memory cache store
func NewMemoryCacheStore ¶
func NewMemoryCacheStore() *MemoryCacheStore
NewMemoryCacheStore creates a new in-memory cache store
func (*MemoryCacheStore) Clear ¶
func (s *MemoryCacheStore) Clear() error
Clear discards every cached entry by replacing the underlying map. Never returns an error; the signature exists to satisfy CacheStore for backing stores that can fail.
func (*MemoryCacheStore) Delete ¶
func (s *MemoryCacheStore) Delete(key string) error
Delete removes the cached entry for key, if any. It is a no-op (not an error) when key is not present.
func (*MemoryCacheStore) Get ¶
func (s *MemoryCacheStore) Get(key string) (*CacheEntry, error)
Get looks up the cached entry for key. Returns ErrCacheMiss if no entry is stored for key, or ErrCacheExpired if an entry exists but its TTL has elapsed (the expired entry is left in the map, not evicted, until overwritten by Set).
func (*MemoryCacheStore) Set ¶
func (s *MemoryCacheStore) Set(key string, entry *CacheEntry) error
Set stores entry under key, overwriting any existing entry. Never returns an error; the signature exists to satisfy CacheStore for backing stores that can fail.
type MemoryFIDOStore ¶
type MemoryFIDOStore struct {
// contains filtered or unexported fields
}
MemoryFIDOStore is an in-memory implementation of FIDOStore for development/testing
func NewMemoryFIDOStore ¶
func NewMemoryFIDOStore() *MemoryFIDOStore
NewMemoryFIDOStore creates a new in-memory FIDO store
func (*MemoryFIDOStore) GetCredentials ¶
func (s *MemoryFIDOStore) GetCredentials(userID []byte) ([]*FIDOCredential, error)
GetCredentials returns the credentials registered for the user with the given userID, resolved by intersecting the stored credential map against the user's own in-memory Credentials slice (a real implementation would instead maintain a persisted user->credential index). Returns ErrFIDOUserNotFound if no user with that ID is stored.
func (*MemoryFIDOStore) GetUser ¶
func (s *MemoryFIDOStore) GetUser(userID []byte) (*FIDOUser, error)
GetUser looks up a user by their raw WebAuthn user handle (userID) and populates the returned FIDOUser's Credentials by scanning the credential map for entries whose credential ID base64-encodes to the same key as the user handle (a placeholder linkage — see the comment in GetCredentials for the real per-user mapping this store does not implement). Returns ErrFIDOUserNotFound if no user has that ID.
func (*MemoryFIDOStore) GetUserByName ¶
func (s *MemoryFIDOStore) GetUserByName(username string) (*FIDOUser, error)
GetUserByName looks up a user by their WebAuthn username. Unlike GetUser, it does not populate Credentials from the credential map. Returns ErrFIDOUserNotFound if no user with that name is stored.
func (*MemoryFIDOStore) SaveCredential ¶
func (s *MemoryFIDOStore) SaveCredential(userID []byte, credential *FIDOCredential) error
SaveCredential stores credential, keyed by its own base64-encoded credential ID. The userID parameter is currently unused by this in-memory implementation (see GetCredentials for how it links credentials back to a user via user.Credentials instead). Never returns an error; the signature exists to satisfy FIDOStore for backing stores that can fail. The credential's public key is stored as-is and must be treated as sensitive key material by callers.
func (*MemoryFIDOStore) SaveUser ¶
func (s *MemoryFIDOStore) SaveUser(user *FIDOUser) error
SaveUser inserts or overwrites user, indexing it both by username and by base64-encoded user ID so it is reachable through either GetUserByName or GetUser. Never returns an error; the signature exists to satisfy FIDOStore for backing stores that can fail.
func (*MemoryFIDOStore) UpdateCredential ¶
func (s *MemoryFIDOStore) UpdateCredential(credentialID []byte, credential *FIDOCredential) error
UpdateCredential overwrites the stored credential keyed by credentialID's base64 encoding with credential — used after a successful authentication ceremony to persist the authenticator's updated sign count and last-used time. Never returns an error; the signature exists to satisfy FIDOStore for backing stores that can fail.
type MemoryKeyStore ¶
type MemoryKeyStore struct {
// contains filtered or unexported fields
}
MemoryKeyStore is an in-memory key store
func NewMemoryKeyStore ¶
func NewMemoryKeyStore() *MemoryKeyStore
NewMemoryKeyStore creates a new in-memory key store
func (*MemoryKeyStore) AddKey ¶
func (s *MemoryKeyStore) AddKey(keyID string, key any)
AddKey adds a key to the store
func (*MemoryKeyStore) GetPublicKey ¶
func (s *MemoryKeyStore) GetPublicKey(keyID string) (any, error)
GetPublicKey retrieves a public key by ID
type MockEndpoint ¶
type MockEndpoint struct {
Method string `json:"method"`
Path string `json:"path"`
StatusCode int `json:"status_code"`
Response any `json:"response"`
Headers map[string]string `json:"headers,omitempty"`
Delay time.Duration `json:"delay,omitempty"`
MatchQuery map[string]string `json:"match_query,omitempty"`
MatchBody map[string]any `json:"match_body,omitempty"`
}
MockEndpoint represents a mock API endpoint
func LoadMockEndpointsFromJSON ¶
func LoadMockEndpointsFromJSON(data []byte) ([]*MockEndpoint, error)
LoadMockEndpointsFromJSON loads mock endpoints from JSON
type MockServer ¶
type MockServer struct {
// contains filtered or unexported fields
}
MockServer provides a configurable mock HTTP server
func NewMockServer ¶
func NewMockServer(logger *slog.Logger) *MockServer
NewMockServer creates a new mock server
func (*MockServer) AddEndpoint ¶
func (ms *MockServer) AddEndpoint(endpoint *MockEndpoint)
AddEndpoint adds a mock endpoint
func (*MockServer) AddEndpoints ¶
func (ms *MockServer) AddEndpoints(endpoints []*MockEndpoint)
AddEndpoints adds multiple endpoints
func (*MockServer) Handler ¶
func (ms *MockServer) Handler() http.Handler
Handler returns the HTTP handler
type OIDC ¶
type OIDC struct {
Issuer string `json:"issuer" validate:"required,url,max=255"`
AuthZURL *url.URL
TokenURL *url.URL
UserURL *url.URL
LogoutURL *url.URL
IntrospectURL *url.URL
RevocationURL *url.URL
DeviceAuthzURL *url.URL
JWKsURL *url.URL
// JWKs holds the cold-start key set so legacy callers continue to
// observe a non-nil reference. The authoritative, auto-refreshing
// view lives behind jwkCache and is consulted on every JWT
// validation path so IdP key rotation is picked up without a host
// process restart (PERF-0043, SEC #150).
JWKs jwk.Set
Aud string
ResponseTypes [][]string
ResponseModes []string `json:"response_modes_supported"`
GrantTypes []string `json:"grant_types_supported"`
SigningAlgos []string `json:"id_token_signing_alg_values_supported"`
SubjectTypes []string `json:"subject_types_supported"`
AuthMethods []string `json:"token_endpoint_auth_methods_supported"`
AcrValues []string `json:"acr_values_supported"`
Scopes []string `json:"scopes_supported"`
Claims []string `json:"claims_supported"`
CodeChallengeMethods []string `json:"code_challenge_methods_supported"`
ClaimsParameters bool `json:"claims_parameter_supported"`
RequestParameter bool `json:"request_parameter_supported"`
// contains filtered or unexported fields
}
OIDC holds an OpenID Connect provider's discovery-document configuration (endpoints, supported algorithms/scopes/claims, and the JWKS used to verify tokens) as loaded by LoadOIDC. Its exported endpoint/metadata fields are populated by UnmarshalJSON directly from the discovery document's JSON, while the unexported ctx/client/discoveryURL fields carry the request context, HTTP client, and originating discovery URL needed for issuer validation and JWKS auto-refresh. Use OIDC.JWT as Gin middleware to verify bearer tokens, or OIDC.Authenticate as an openapi3filter.AuthenticationFunc.
func LoadOIDC ¶
LoadOIDC loads the OpenID Connect configuration from the given URL. @client: HTTP client to use for the request. @url: URL of the OpenID Connect configuration endpoint. @aud: Audience of the OpenID Connect configuration endpoint (usually the client ID of the application). @return: OpenID Connect configuration.
func (*OIDC) Authenticate ¶
func (o *OIDC) Authenticate( ctx context.Context, input *oapifilter.AuthenticationInput, ) error
Authenticate is the oapi-codegen AuthenticationFunc for this OIDC instance: it requires a verified JWT on the request and enforces every OAuth2 scope the OpenAPI security scheme declared for the operation. The token itself is verified upstream by the JWT extractor middleware; this function only reads the verified token off the context and checks scopes.
func (*OIDC) ExtractToken ¶
ExtractToken parses the Authorization HTTP header for valid JWT token and validates it with the JWK keys. Also verifies if the audience present in the token matches with the designated audience as per current configuration.
func (*OIDC) JWT ¶
JWT is Gin middleware that extracts and verifies a bearer JWT from the incoming request via ExtractToken and, on success, stashes the verified token both in Gin's per-request key/value store (under ctxTokenKey as a string) and on the request's context.Context (under the typed ctxTokenKey, readable later via AuthToken). If o.JWKs is nil (no keys configured), the request is allowed through unauthenticated UNLESS securex.AllowInsecureDev refuses it (e.g. not running in Gin debug mode), in which case it aborts with 401. A missing Authorization header is treated as anonymous (request continues unauthenticated); any other extraction/verification failure aborts the request with 401.
func (*OIDC) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler for OIDC, decoding an OpenID Connect discovery document (RFC/OIDC Discovery 1.0) into o. Beyond plain field mapping it enforces several security invariants fail-closed: required endpoint fields must be present and well-formed (validated via the package-level validator), every derived endpoint URL (jwks_uri, authorization_endpoint, token_endpoint, userinfo_endpoint) must use https (ErrInvalidScheme otherwise), and — when o.discoveryURL was set by LoadOIDC — the document's advertised issuer must match the discovery URL per OIDC Discovery 1.0 §4.3 (ErrIssuerMismatch otherwise, guarding against a compromised/misconfigured discovery endpoint redirecting trust). On success it also builds an auto-refreshing JWK cache (oidcx.NewJWKCache) bound to o.ctx, sharing o.client's transport when present, and fails with ErrJWKEndpointEmpty if the fetched key set is empty.
type Option ¶
Option configures a Client during NewClient, mutating it in place and returning an error to abort construction (e.g. WithTransport rejecting a nil transport). Options are applied in the order passed.
func WithAutoRefreshToken ¶
func WithAutoRefreshToken(refresher *TokenRefresher) Option
WithAutoRefreshToken creates a client with automatic token refresh
func WithBasicAuth ¶
WithBasicAuth sets a default "Authorization: Basic <base64(user:pass)>" header on every request the client sends (RFC 7617). username and password are base64-encoded, not encrypted; treat them as secrets and only use this option over a trusted transport (e.g. TLS).
func WithBearerToken ¶
WithBearerToken sets a default "Authorization: Bearer <token>" header on every request the client sends. token is bearer-credential material; treat it as a secret (note Client.LogValue does not redact it if it ends up in the default headers) and only use this option over a trusted transport.
func WithCircuitBreaker ¶
func WithCircuitBreaker(cb *CircuitBreaker) Option
WithCircuitBreaker wraps the client's current transport (or http.DefaultTransport if none is set) with cb, so every request goes through cb.Execute and is rejected with ErrCircuitBreakerOpen while the breaker is open, and any 5xx response is recorded as a failure.
func WithDebugFile ¶
WithDebugFile opens (creating/appending as needed) file and sets it as the client's debug dump target: every response body, up to the client's response-byte ceiling, is written to it (see Client.debugWrite). The file is opened owner-only (mode 0600) and with O_NOFOLLOW so a pre-planted symlink at the final path component cannot redirect captured request/response bodies (which may contain sensitive data) into a file an attacker controls (SEC-0013).
func WithDoer ¶
func WithDoer(client HTTPClient) Option
WithDoer installs client's Do method as the Client's transport (via ClientWrapper), letting callers reuse an existing HTTPClient (such as a pre-configured *http.Client) instead of a bare http.RoundTripper. Returns ErrClientNil if client is nil.
func WithHeader ¶
WithHeader sets key to value in the client's default headers, applied to every outgoing request (before any per-request RequestOption, so a RequestOption can still override it). Repeated calls with the same key replace the previous value (http.Header.Set semantics).
func WithLimiter ¶
func WithLimiter( ctx context.Context, delay time.Duration, retries int, concurrency int, requestTimeout time.Duration, ) Option
WithLimiter wraps the client's current transport with a devnw.dev/bk rate-limited/retrying client (bk.New), bound to ctx: delay is the pacing interval between requests, retries the number of retry attempts, concurrency the maximum number of in-flight requests, and requestTimeout the per-request deadline. The bk client's Do method replaces the client's transport.
func WithLogger ¶
WithLogger sets the client's log.Logger, used for request/response tracing and debug output. A nil logger is silently ignored, leaving the client's existing (default) logger in place.
func WithMaxResponseBytes ¶
WithMaxResponseBytes sets the ceiling on response-body bytes buffered by ReadJSON. A value <=0 disables the limit (unbounded; use only for trusted upstreams). The default is DefaultMaxResponseBytes (32 MiB) (PERF-0024).
func WithOAuth2 ¶
WithOAuth2 configures the client to authenticate outgoing requests using the OAuth2 client-credentials grant (golang.org/x/oauth2/clientcredentials): clientID/clientSecret are exchanged with tokenURL for an access token, scoped to scopes, and the resulting token-managing *http.Client (which transparently refreshes the token as needed) replaces the client's transport entirely. clientSecret is credential material handled by the oauth2 library and is never logged by this option itself.
func WithRoot ¶
WithRoot sets the client's root URL, parsed from rootURL, against which relative request paths (e.g. those passed to Client.Get/Post/Put/Delete) are resolved. Returns a parse error if rootURL is not a valid URL.
func WithSignature ¶
WithSignature adds HTTP signature signing to the client
func WithTransport ¶
func WithTransport(transport http.RoundTripper) Option
WithTransport sets the client's underlying http.RoundTripper, replacing http.DefaultTransport (e.g. to install a custom TLS config, proxy, or pinned CA). Returns ErrTransportNil if transport is nil.
type ProxyOption ¶
type ProxyOption func(*proxy) error
ProxyOption configures a proxy during Proxy, mutating it in place and returning an error to abort construction.
func WithCertificate ¶
func WithCertificate(cert, key string) ProxyOption
WithCertificate configures the proxy's TLS certificate loading (via loadCert) from the given cert/key file paths, used as GetCertificate on every handshake so certificate rotation (e.g. a symlink retarget by certbot or a Kubernetes secret volume) is picked up without a restart. If resolution or loading fails, a fresh self-signed certificate is served instead (fail-open) rather than failing the handshake.
func WithClient ¶
func WithClient(c *Client) ProxyOption
WithClient sets the *Client the proxy uses to forward requests upstream, replacing the default client Proxy constructs internally (which carries no special options).
func WithRewriters ¶
func WithRewriters(r ...Rewriter) ProxyOption
WithRewriters appends r to the proxy's list of Rewriters. Proxy collapses the full accumulated list into a single "rewriters" Rule that, for each request, tries each Rewriter's Process method in order and stops at the first one that succeeds (a failing Rewriter is skipped, not fatal).
func WithRule ¶
func WithRule(name string, rule Rule) ProxyOption
WithRule appends rule to the named list of rules run (in registration order) before every proxied request. The name is used only for logging; multiple WithRule calls with the same name accumulate rather than replace.
func WithSubnetFilter ¶
func WithSubnetFilter(subnets ...netip.Prefix) ProxyOption
WithSubnetFilter registers a "subnet-filter" rule that only allows a proxied request through when the client's remote address (parsed from r.RemoteAddr) falls within one of the given subnets; otherwise it returns a "forbidden" error, which the proxy's serve loop turns into a 500 response and aborts forwarding. A remote address that fails to parse as host:port or as an IP writes its own 500 response directly and also returns an error.
func WithTLSConfig ¶
func WithTLSConfig(cfg *tls.Config) ProxyOption
WithTLSConfig sets the *tls.Config the proxy uses when Proxy wraps the listener in a TLS listener. Its GetCertificate field is overwritten by Proxy with the certgen configured via WithCertificate, so callers do not need to (and should not rely on) setting GetCertificate here themselves.
type Register ¶
Register is the callback New invokes with the constructed *gin.Engine so callers can mount their application's routes before the listener starts. A non-nil error aborts server construction.
type ReplayOptions ¶ added in v0.18.3
type ReplayOptions struct {
// MaxParallel bounds the number of concurrent in-flight Replay calls.
// <=0 uses the default (runtime.GOMAXPROCS(0)*2). Before this bound
// existed, a parallel ReplayBatch launched one goroutine and one
// outbound HTTP call per element with no cap, so a large batch could
// open unbounded concurrent connections to the replay target -- the
// same semaphore-bounded dispatch pattern pkg/mcpx's dispatchHTTP and
// pkg/gqlx's batch handler already use.
MaxParallel int
}
ReplayOptions configures ReplayBatchWithOptions's parallel fan-out (PERF-0122).
type ReplayRequest ¶
type ReplayRequest struct {
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers"`
Body []byte `json:"body,omitempty"`
}
ReplayRequest represents a captured HTTP request for replay
func LoadRequestsFromJSON ¶
func LoadRequestsFromJSON(data []byte) ([]*ReplayRequest, error)
LoadRequestsFromJSON loads replay requests from JSON
type ReplayResult ¶
type ReplayResult struct {
Request *ReplayRequest
StatusCode int
Headers http.Header
Body []byte
Duration time.Duration
Error error
Timestamp time.Time
}
ReplayResult contains the result of a replayed request
type Replayer ¶
type Replayer struct {
// contains filtered or unexported fields
}
Replayer replays captured HTTP requests
func NewReplayer ¶
NewReplayer creates a new request replayer
func (*Replayer) Replay ¶
func (r *Replayer) Replay(ctx context.Context, req *ReplayRequest) *ReplayResult
Replay replays a single request
func (*Replayer) ReplayBatch ¶
func (r *Replayer) ReplayBatch(ctx context.Context, requests []*ReplayRequest, parallel bool) []*ReplayResult
ReplayBatch replays multiple requests, sequentially or in parallel. The parallel path is bounded by the default MaxParallel (runtime.GOMAXPROCS(0)*2); use ReplayBatchWithOptions to configure the bound explicitly.
func (*Replayer) ReplayBatchWithOptions ¶ added in v0.18.3
func (r *Replayer) ReplayBatchWithOptions(ctx context.Context, requests []*ReplayRequest, opts ReplayOptions) []*ReplayResult
ReplayBatchWithOptions replays multiple requests in parallel, bounding the number of concurrently in-flight Replay calls to opts.MaxParallel (or its default). Results are written to results[i] by the goroutine handling requests[i], so the returned slice stays ordered to match the input regardless of completion order.
type RequestOption ¶
RequestOption customizes a single outgoing *http.Request, applied after the client's default headers so a RequestOption can override them.
func WithBody ¶
func WithBody(body io.ReadCloser) RequestOption
WithBody sets the request's Body to body (e.g. built via Client.JSONReader). It does not set Content-Length or Content-Type; callers needing those should also apply WithRequestHeader.
func WithMethod ¶
func WithMethod(method string) RequestOption
WithMethod sets the request's HTTP method. The package-level GET, POST, PUT, DELETE, and PATCH vars are pre-built RequestOptions from this function for the common verbs.
func WithQuery ¶
func WithQuery(query url.Values) RequestOption
WithQuery sets the request's URL query string from query, after passing it through the client's queryNorm normalization.
func WithRequestAuth ¶
func WithRequestAuth(auth string) RequestOption
WithRequestAuth sets the Authorization header on this request only to the literal value auth (e.g. "Bearer <token>" or "Basic <base64>"). Treat auth as a credential: it is placed directly on the outgoing request unmodified.
func WithRequestHeader ¶
func WithRequestHeader(key, value string) RequestOption
WithRequestHeader sets key to value on this request only (via http.Header.Set, replacing any existing value for key), overriding the client's default headers for this call.
type RetryConfig ¶
type RetryConfig struct {
// MaxAttempts is the maximum number of retry attempts
MaxAttempts int
// InitialDelay is the delay before the first retry
InitialDelay time.Duration
// MaxDelay is the maximum delay between retries
MaxDelay time.Duration
// Multiplier is the backoff multiplier (default: 2.0)
Multiplier float64
// ShouldRetry determines if a request should be retried
ShouldRetry func(*http.Response, error) bool
}
RetryConfig configures retry behavior
type Rewriter ¶
type Rewriter struct {
From Host
To Host
Paths map[string]string
Headers map[string]http.Header
Query map[string]url.Values
}
Rewriter describes a single host-scoped request rewrite rule: requests whose host (and, if present, port) match From are rewritten to target To, with per-original-path substitutions for the request path (Paths), and per-target-path additions merged into the query string (Query) and headers (Headers). A Rewriter is a value type; Process applies it to a request without mutating the Rewriter itself.
func (Rewriter) Process ¶
Process rewrites in in place according to r: it first checks that in's host (and port, if r.From specifies one) matches r.From, returning ErrNotApplicable if not. On a match it rewrites in.Host/in.URL.Host to r.To (keeping the original port when r.To specifies none), remaps in.URL.Path via r.Paths (falling back to the original path if unmapped), merges any r.Query entries for the resulting path into the existing query string, and merges any r.Headers entries for that path into in.Header. Query and header merges are applied in sorted key order for determinism; existing keys are overwritten with the configured values.
type Rule ¶
type Rule func(w http.ResponseWriter, r *http.Request) error
Rule is a proxy request hook invoked before the request is forwarded upstream: it may inspect/mutate r, write directly to w (e.g. http.Error) to short-circuit the request, and return a non-nil error to abort forwarding (the proxy's handler responds 500 and stops). Rules are registered by name via WithRule (or synthesized, as the "rewriters" rule installed by WithRewriters) and run in the order added.
type Server ¶
Server is the interface returned by New: a running (or ready-to-run) htpx API server whose lifecycle is driven entirely through Serve.
func New ¶
func New(reg Register, opts ...ServerOption) (_ Server, err error)
New builds a Server: it installs the default request-logging/recovery middleware, applies opts in order (host/port/TLS/CORS/validation/etc.), fills in default rate-limit settings and builds the token-bucket rateLimiter, appends the rate-limit/metrics middleware, sets trusted proxies (if configured), mounts a GET /metrics route, invokes reg to register application routes, and finally opens the listener (TLS unless the insecure-dev bypass permits a plaintext loopback listener; see INSECURE_DEV). It returns ErrRegisterFunctionNil if reg is nil, and wraps any trusted-proxy, route-registration, or listener-construction error.
type ServerOption ¶
type ServerOption func(*apiServer) error
ServerOption is a functional option applied to an apiServer during New to configure host/port/TLS/CORS/middleware/validation/etc. It returns an error to abort server construction.
func RateLimit ¶
func RateLimit(limit int, duration time.Duration) ServerOption
RateLimit sets the token-bucket rate limit (limit requests per duration) used to build the server's shared rateLimiter. It returns an error (without modifying the server) if limit falls outside [rateLimitMin, rateLimitMax] or duration falls outside [rateMinDuration, rateMaxDuration].
func WithAPIKeyManager ¶
func WithAPIKeyManager(manager *APIKeyManager, adminAuth gin.HandlerFunc) ServerOption
WithAPIKeyManager adds API key management routes to the server
func WithCORS ¶
func WithCORS(allowedOrigins []string, methods ...string) ServerOption
WithCORS configures gin-contrib/cors on the server. Empty allowedOrigins defaults to the server's own default host:port; an empty methods list defaults to GET/POST/PUT/DELETE. When the insecure-dev bypass is active at startup (see INSECURE_DEV), AllowAllOrigins is forced to true regardless of allowedOrigins -- a dev-only convenience that must never be reachable in a production deployment.
func WithErrorHandler ¶
func WithErrorHandler(handler func(ctx *gin.Context, err error, i int)) ServerOption
WithErrorHandler sets the server's error handler, used to translate an error and status code into an HTTP response. Passing a nil handler logs a warning and installs a default handler that writes {"error": err.Error()} as JSON with the given status (and does nothing when err is nil).
func WithFIDO ¶
func WithFIDO(cfg *FIDOConfig) ServerOption
WithFIDO adds FIDO2 authentication to an API server
func WithHost ¶
func WithHost(host string) ServerOption
WithHost sets the host/interface the server listens on.
func WithImpl ¶
func WithImpl(impl any) ServerOption
WithImpl logs the supplied impl value for diagnostics only; it is currently a placeholder and does not store or wire impl into the server in any other way.
func WithMaxBodyBytes ¶ added in v0.18.3
func WithMaxBodyBytes(n int64) ServerOption
WithMaxBodyBytes overrides the request-body size limit (mirrors api.WithMaxBodyBytes; PERF-0036/QG-095 #271). The default is defaultMaxBodyBytes (10 MiB); pass a larger value for routes that accept big uploads, or 0 to disable bounding entirely (e.g. fully streaming endpoints). The limit is enforced by http.MaxBytesReader before any handler or the OpenAPI request validator reads the body.
func WithMiddleware ¶
func WithMiddleware(middleware ...gin.HandlerFunc) ServerOption
WithMiddleware appends the given gin.HandlerFuncs to the server's middleware chain, in the order supplied, after the built-in request logging/recovery middleware and before the rate-limit middleware New adds.
func WithPerIPRateLimit ¶
func WithPerIPRateLimit() ServerOption
WithPerIPRateLimit enables per-remote-IP token buckets instead of global bucket.
func WithPort ¶
func WithPort(port int) ServerOption
WithPort sets the TCP port the server listens on.
func WithServerLogger ¶
func WithServerLogger(logger log.Logger) ServerOption
WithServerLogger overrides the server's log.Logger (used for both the server's own diagnostic logging and as the default passed to WithValidation's discovery client).
func WithShutdownTimeout ¶ added in v0.18.3
func WithShutdownTimeout(d time.Duration) ServerOption
WithShutdownTimeout overrides the graceful http.Server.Shutdown budget Serve applies once ctx is cancelled (default 30s; mirrors api.WithShutdownTimeout, QG-095/#271). A non-positive d is ignored (the default is kept).
func WithSwaggerSpec ¶
func WithSwaggerSpec(spec *openapi3.T) ServerOption
WithSwaggerSpec attaches the OpenAPI document used for request validation middleware. It must be applied (via an earlier ServerOption) before WithValidation whenever OIDC authentication is configured, since WithValidation refuses (ErrValidationSpecMissing) to build a server with OIDC auth but no spec.
func WithTLS ¶
func WithTLS(cert, key string) ServerOption
WithTLS sets the paths to the TLS certificate and key files newListener uses to build the server's TLS listener. These are ignored (and may be left empty) when the insecure-dev bypass is active at startup (see INSECURE_DEV); otherwise both are required.
func WithTrustedProxies ¶
func WithTrustedProxies(proxies []string) ServerOption
WithTrustedProxies sets the trusted proxy addresses/CIDRs passed to gin.Engine.SetTrustedProxies during New. A nil proxies slice logs a warning and leaves the server's configured proxies untouched (New then skips calling SetTrustedProxies, so Gin's own default applies) rather than explicitly clearing it.
func WithValidation ¶
func WithValidation( ctx context.Context, oidc string, audience string, ) ServerOption
WithValidation wires OpenAPI request validation and, when oidc is non-empty, OIDC JWT authentication onto the server: it builds a discovery client, loads the OIDC configuration via LoadOIDC (setting s.oidc), and prepends s.oidc.JWT to the middleware chain so tokens are extracted before the OpenAPI validator's AuthenticationFunc runs. If oidc is empty, OIDC auth is skipped entirely, but that is only permitted when the insecure-dev bypass is active at startup (securex.AllowInsecureDevAtStartup); otherwise it returns ErrOIDCEndpointEmpty. If OIDC is configured but WithSwaggerSpec was never applied, it returns ErrValidationSpecMissing rather than building a server whose only auth-invoking component (the OpenAPI validator) is missing -- which would otherwise serve every security-declared route unauthenticated. When a spec is present, its Servers list is cleared (so validation does not require the request's host to match a declared server) and the OpenAPI request-validator middleware is appended to the chain.
type SignatureAlgorithm ¶
type SignatureAlgorithm string
SignatureAlgorithm represents the signature algorithm
const ( // AlgoHMAC_SHA256 signs/verifies with HMAC-SHA256 using a shared secret // ([]byte) as both PrivateKey and PublicKey. AlgoHMAC_SHA256 SignatureAlgorithm = "hmac-sha256" // AlgoRSA_SHA256 signs with RSASSA-PKCS1-v1_5 over SHA-256 using an // *rsa.PrivateKey/*rsa.PublicKey pair. AlgoRSA_SHA256 SignatureAlgorithm = "rsa-sha256" // AlgoECDSA_SHA256 signs with ECDSA (ASN.1 signature) over SHA-256 using // an *ecdsa.PrivateKey/*ecdsa.PublicKey pair. AlgoECDSA_SHA256 SignatureAlgorithm = "ecdsa-sha256" // AlgoED25519 signs/verifies with Ed25519 using an // ed25519.PrivateKey/ed25519.PublicKey pair. AlgoED25519 SignatureAlgorithm = "ed25519" )
type SignatureConfig ¶
type SignatureConfig struct {
// KeyID identifies the key used for signing
KeyID string
// Algorithm is the signature algorithm
Algorithm SignatureAlgorithm
// PrivateKey is the private key for signing (crypto.Signer or []byte for HMAC)
PrivateKey any
// PublicKey is the public key for verification (crypto.PublicKey or []byte for HMAC)
PublicKey any
// Headers are the headers to include in the signature (default: date, digest)
Headers []string
// IncludeDigest adds a Digest header with the request body hash
IncludeDigest bool
// MaxClockSkew is the maximum allowed clock skew for date validation (default: 5 minutes)
MaxClockSkew time.Duration
}
SignatureConfig configures HTTP signature signing/verification
type SignatureInfo ¶
type SignatureInfo struct {
KeyID string
Algorithm SignatureAlgorithm
Headers []string
Signature string
}
SignatureInfo contains parsed signature information
type SignatureStats ¶
SignatureStats tracks signature verification statistics
func NewSignatureStats ¶
func NewSignatureStats() *SignatureStats
NewSignatureStats creates a new stats tracker
func (*SignatureStats) Record ¶
func (s *SignatureStats) Record(err error)
Record records a verification result
func (*SignatureStats) Summary ¶
func (s *SignatureStats) Summary() map[string]any
Summary returns a summary of statistics
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer signs HTTP requests
func NewSigner ¶
func NewSigner(cfg *SignatureConfig) (*Signer, error)
NewSigner creates a new HTTP signature signer
type TOKEN ¶
type TOKEN string
TOKEN is the type of the context key used to stash a verified JWT token on a request context (see ctxTokenKey, OIDC.JWT, and AuthToken). It holds no data of its own; it exists only so the key's type is distinct from any plain string key.
type Timeout ¶
Timeout is a time.Duration wrapper used for the proxy's named default timeout constants, giving each a distinct, self-documenting type.
type TokenRefresher ¶
type TokenRefresher struct {
// contains filtered or unexported fields
}
TokenRefresher automatically refreshes OAuth2 tokens
func NewTokenRefresher ¶
func NewTokenRefresher(config *oauth2.Config, token *oauth2.Token, logger *slog.Logger) *TokenRefresher
NewTokenRefresher creates a new token refresher
func (*TokenRefresher) Client ¶
func (tr *TokenRefresher) Client(ctx context.Context) *http.Client
Client returns an HTTP client with automatic token refresh
func (*TokenRefresher) OnRefresh ¶
func (tr *TokenRefresher) OnRefresh(fn func(*oauth2.Token))
OnRefresh sets a callback for when the token is refreshed
func (*TokenRefresher) Start ¶
func (tr *TokenRefresher) Start(ctx context.Context) error
Start starts the automatic token refresh
func (*TokenRefresher) Token ¶
func (tr *TokenRefresher) Token() *oauth2.Token
Token returns the current token
type TrafficDiff ¶
type TrafficDiff struct {
Type string `json:"type"` // "method", "path", "header", "body", "status"
Field string `json:"field,omitempty"`
Expected any `json:"expected"`
Actual any `json:"actual"`
Description string `json:"description"`
}
TrafficDiff represents a difference between two HTTP exchanges
type TrafficDiffer ¶
type TrafficDiffer struct {
// contains filtered or unexported fields
}
TrafficDiffer compares HTTP traffic
func NewTrafficDiffer ¶
func NewTrafficDiffer() *TrafficDiffer
NewTrafficDiffer creates a new traffic differ
func (*TrafficDiffer) Compare ¶
func (td *TrafficDiffer) Compare(expected, actual *HTTPExchange) []*TrafficDiff
Compare compares two HTTP exchanges
func (*TrafficDiffer) IgnoreFields ¶
func (td *TrafficDiffer) IgnoreFields(fields ...string)
IgnoreFields sets JSON fields to ignore in body comparisons
func (*TrafficDiffer) IgnoreHeaders ¶
func (td *TrafficDiffer) IgnoreHeaders(headers ...string)
IgnoreHeaders sets headers to ignore in comparisons
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier verifies HTTP signatures
func NewVerifier ¶
NewVerifier creates a new HTTP signature verifier