hellhound/config
Hellhound configuration: an opaque Config built from one constructor and
with_* setters. Nothing here opens a connection or starts a process;
hellhound.new validates the configuration and hellhound.start discovers the
provider.
let config =
config.new(
issuer: "https://login.example.com",
client_id: "app",
redirect_uri: "https://app.example.com/auth/callback",
authentication: config.ClientSecretBasic(config.secret(client_secret)),
)
|> config.with_scopes(["profile", "email"])
let assert Ok(client) = hellhound.new(config)
Three constructors cover the three roles:
new: a relying party that signs users in (and may do everything else);service_client: a confidential client with no login: client credentials, introspection and access-token validation;resource_server: access-token validation only (hellhound/resource), with no client identity.
Defaults
| Setting | Default | Setter |
|---|---|---|
| redirect URI | the one given to new | with_allowed_redirect_uris (exact match) |
| scopes | openid | with_scopes |
| ID-token algorithms | RS256, PS256, ES256, EdDSA (none and HMAC unrepresentable) | with_signing_algorithms |
| response mode | query | with_response_mode |
RFC 9207 iss | required when advertised | with_issuer_parameter |
| PKCE | advertised S256 required | with_pkce_advertisement |
| TLS trust | system CA store | with_trust |
| destinations | public addresses only | with_destinations, with_allowed_hosts |
| provider request | 10 s | with_request_timeout |
| provider response body | 1 MiB | with_max_response_bytes |
| startup discovery, keys and previous-process cleanup | 15 s | with_startup_timeout |
| store call | 5 s | with_store_timeout |
complete_login, end to end | 30 s | with_login_timeout |
| pending login | 10 min | with_login_lifetime |
| pending logins in the built-in store | 100 000 | with_max_pending_logins |
| session | 12 h absolute, 1 h idle | with_session_lifetime |
clock tolerance (iat, nbf, auth_time; never exp) | 5 s | with_clock_tolerance |
| refresh before expiry | 30 s | with_refresh_margin |
| wait for another request’s refresh | 5 s | with_refresh_wait |
| session and login storage | in memory | with_custody_store, with_transaction_store |
Secrets (Secret, SigningKey, SealingKey) are held in closures, so
string.inspect of a configuration prints no credential.
Types
How the client authenticates at the token, introspection and revocation endpoints. Hellhound uses exactly this method; it never falls back to another method the provider advertises. May gain variants (mTLS).
pub type ClientAuthentication {
PublicClient
ClientSecretBasic(Secret)
ClientSecretPost(Secret)
ClientSecretJwt(Secret)
PrivateKeyJwt(SigningKey)
}
Constructors
-
PublicClientA public client. PKCE still applies; no client secret is sent.
-
ClientSecretBasic(Secret) -
ClientSecretPost(Secret) -
ClientSecretJwt(Secret)HMAC-signed client assertion (RFC 7523) using the client secret.
-
PrivateKeyJwt(SigningKey)Client assertion signed with the client’s private key.
A Hellhound configuration. Build it with new, service_client or
resource_server and the with_* setters.
pub type Config =
@internal Settings
Why a configuration is invalid. May gain variants.
pub type ConfigError {
InvalidIssuer
InvalidClientId
InvalidRedirectUri
InvalidAllowedRedirectUri(String)
AllowedRedirectUrisNeedLogin
InvalidScope(String)
NoSigningAlgorithms
SigningKeyUnusable
InvalidTrustAnchors
InvalidAllowedHost(String)
InvalidLimit(Limit)
UnadvertisedPkceRequiresConfidentialClient
EmptyClientSecret
ClientSecretTooShort
ServiceClientNeedsCredentials
SealingKeyRequired
}
Constructors
-
InvalidIssuerThe issuer must be an absolute
httpsURI without query or fragment. -
InvalidClientId -
InvalidRedirectUriThe redirect URI must be absolute, without fragment, and
httpsunless its host is a loopback address orlocalhost. -
InvalidAllowedRedirectUri(String)A URI given to
with_allowed_redirect_urisbreaks the redirect URI rule above. -
AllowedRedirectUrisNeedLoginwith_allowed_redirect_urison aservice_clientorresource_server, which have no login. -
InvalidScope(String)A scope token contains a byte outside RFC 6749 §3.3 or is empty.
-
NoSigningAlgorithms -
SigningKeyUnusableThe signing key cannot produce any assertion algorithm.
-
InvalidTrustAnchors -
InvalidAllowedHost(String) -
InvalidLimit(Limit)A numeric setting is out of range;
describe_config_errornames the range and the setter. -
UnadvertisedPkceRequiresConfidentialClientAssumeS256WhenUnadvertisedwithPublicClient. -
EmptyClientSecretA client secret is empty.
-
ClientSecretTooShortA
client_secret_jwtsecret is shorter than 32 bytes, the minimum HMAC key for HS256 (RFC 7518 §3.2). -
ServiceClientNeedsCredentialsservice_clientwithPublicClient: a service authenticates itself. -
SealingKeyRequiredA durable store is configured without
with_sealing_key.
Which network destinations provider requests may reach. Every resolved address must satisfy the policy.
pub type DestinationPolicy {
PublicInternetOnly
AllowLoopbackForTesting
AllowPrivateNetwork
}
Constructors
-
PublicInternetOnlyPublic unicast addresses only.
-
AllowLoopbackForTestingAlso loopback addresses. For local test providers only.
-
AllowPrivateNetworkAlso private (RFC 1918, unique-local, shared) addresses, for providers inside a private network. Loopback stays rejected.
RFC 9207 iss authorization-response parameter policy. A present iss
must always equal the configured issuer.
pub type IssuerParameterPolicy {
RequireIssuerWhenAdvertised
AlwaysRequireIssuer
}
Constructors
-
RequireIssuerWhenAdvertisedRequire
isswhen the provider advertisesauthorization_response_iss_parameter_supported. -
AlwaysRequireIssuerAlways require
iss.
A bounded setting, as InvalidLimit names it.
pub type Limit {
RequestTimeout
MaxResponseBytes
StartupTimeout
StoreTimeout
LoginTimeout
LoginLifetime
MaxPendingLogins
SessionLifetime
ClockTolerance
RefreshMargin
RefreshWait
}
Constructors
-
RequestTimeout -
MaxResponseBytes -
StartupTimeout -
StoreTimeout -
LoginTimeout -
LoginLifetime -
MaxPendingLogins -
SessionLifetime -
ClockTolerance -
RefreshMargin -
RefreshWait
How Hellhound establishes that the provider supports PKCE S256
(RFC 9700 §2.1.1). Hellhound always sends an S256 challenge and its
verifier; this policy only decides which providers it accepts.
pub type PkceAdvertisementPolicy {
RequireAdvertisedS256
AssumeS256WhenUnadvertised
}
Constructors
-
RequireAdvertisedS256The provider must list
S256incode_challenge_methods_supported. -
AssumeS256WhenUnadvertisedAlso accept a provider whose metadata omits
code_challenge_methods_supportedentirely. A provider that lists the field withoutS256is still refused. Refused for public clients.
How the provider returns the authorization response. May gain JARM.
pub type ResponseMode {
Query
FormPost
}
Constructors
-
QueryRedirect with a query string (the OAuth default).
-
FormPostCross-site POST of an HTML form (OAuth 2.0 Form Post Response Mode).
The key that seals records in a durable store (with_sealing_key): 32
random bytes, held in a closure. Generate one with
crypto.strong_random_bytes(32) and keep it in a secret manager.
pub opaque type SealingKey
pub type SealingKeyError {
SealingKeyNot32Bytes
}
Constructors
-
SealingKeyNot32BytesA sealing key must be exactly 32 bytes (AES-256).
A client secret, held in a closure: string.inspect and logs of a
configuration show no secret. This does not protect against VM inspection
or crash dumps; see the design’s Application responsibilities section.
pub opaque type Secret
ID-token and access-token signing algorithms Hellhound accepts. none and
HMAC algorithms are not representable; the provider’s advertised list is
intersected with this allowlist.
pub type SigningAlgorithm {
Rs256
Rs384
Rs512
Ps256
Ps384
Ps512
Es256
Es384
Es512
EdDsa
}
Constructors
-
Rs256 -
Rs384 -
Rs512 -
Ps256 -
Ps384 -
Ps512 -
Es256 -
Es384 -
Es512 -
EdDsa
A private signing key for private_key_jwt, held like Secret.
pub opaque type SigningKey
pub type SigningKeyError {
NotAPrivateSigningJwk
WeakRsaKey
NotForSigning
}
Constructors
-
NotAPrivateSigningJwkThe text is not a JSON Web Key with private signing material.
-
WeakRsaKeyAn RSA key below 2048 bits (RFC 7518 §3.3).
-
NotForSigningThe key’s
useorkey_opsdoes not permit signing.
Values
pub fn describe_config_error(error: ConfigError) -> String
Describe a configuration error for logs, naming the setter to change.
pub fn new(
issuer issuer: String,
client_id client_id: String,
redirect_uri redirect_uri: String,
authentication authentication: ClientAuthentication,
) -> Config
A relying party that signs users in, with Hellhound’s defaults (see the module documentation).
pub fn resource_server(issuer issuer: String) -> Config
A resource server that only validates access tokens locally
(hellhound/resource) against the issuer’s published keys. It has no
client identity: login, client credentials and introspection are not
available.
pub fn sealing_key(
bytes: BitArray,
) -> Result(SealingKey, SealingKeyError)
A sealing key from 32 bytes.
pub fn service_client(
issuer issuer: String,
client_id client_id: String,
authentication authentication: ClientAuthentication,
) -> Config
A confidential client without login: client credentials, introspection
and access-token validation. It needs no redirect URI, and hellhound.start
does not require the provider to support the authorization code flow.
begin_login and complete_login answer LoginNotConfigured. A
PublicClient is refused by validation.
pub fn signing_key_from_jwk(
json: String,
) -> Result(SigningKey, SigningKeyError)
Parse a private JSON Web Key (RSA, EC P-256/384/521 or Ed25519).
pub fn validate(config: Config) -> Result(Nil, List(ConfigError))
Check a configuration without starting anything. Returns every problem
found; hellhound.new runs the same check.
pub fn with_allowed_hosts(
config: Config,
hosts: List(String),
) -> Config
Provider requests may reach only these host names (case-insensitive).
pub fn with_allowed_redirect_uris(
config: Config,
uris: List(String),
) -> Config
Further redirect URIs a login may choose with
hellhound.LoginOptions(redirect_uri: Some(uri), ..), for an application
served under several registered callback addresses. The URI given to
new stays the default and is always allowed.
A login’s URI must equal one of these strings exactly: no prefix,
wildcard, port, case or percent-encoding normalisation. Anything else
fails the login with InvalidLoginOption(RedirectUriNotAllowed). Each
URI must also be registered at the provider. Only a relying party
(new) takes them.
pub fn with_clock_tolerance(
config: Config,
tolerance: duration.Duration,
) -> Config
How far a provider’s clock may run ahead of this node for iat, nbf
and auth_time; exp never gets tolerance. Default 5 s, at most 300 s.
pub fn with_custody_store(
config: Config,
store: store.Store,
) -> Config
Keep sessions in this store instead of memory. Needs with_sealing_key.
pub fn with_destinations(
config: Config,
policy: DestinationPolicy,
) -> Config
pub fn with_issuer_parameter(
config: Config,
policy: IssuerParameterPolicy,
) -> Config
pub fn with_login_lifetime(
config: Config,
lifetime: duration.Duration,
) -> Config
How long a pending login waits for its callback. Between 1 s and 1 day; default 10 minutes.
pub fn with_login_timeout(
config: Config,
timeout: duration.Duration,
) -> Config
One bound on complete_login: store calls, the code exchange, key
refreshes and custody installation together. Between 1 ms and 10
minutes; default 30 s.
pub fn with_max_pending_logins(
config: Config,
count: Int,
) -> Config
Capacity of the built-in in-memory login store, counting pending logins
and recently completed ones (kept to detect replays). A full store
answers TooManyPendingLogins. Between 1 and 10 000 000; default
100 000. A durable store enforces its own capacity (store.StoreFull).
pub fn with_max_response_bytes(
config: Config,
bytes: Int,
) -> Config
Largest provider response body. Between 1 KiB and 64 MiB; default 1 MiB.
pub fn with_pkce_advertisement(
config: Config,
policy: PkceAdvertisementPolicy,
) -> Config
pub fn with_previous_sealing_keys(
config: Config,
keys: List(SealingKey),
) -> Config
Retired sealing keys that may still have sealed stored records.
pub fn with_refresh_margin(
config: Config,
margin: duration.Duration,
) -> Config
hellhound.access_token refreshes a token that expires within this margin.
Between 0 and 1 hour; default 30 s.
pub fn with_refresh_wait(
config: Config,
wait: duration.Duration,
) -> Config
How long a request waits for another request’s refresh of the same
session before answering RefreshWaitTimedOut. Between 0 and 1 minute;
default 5 s.
pub fn with_request_timeout(
config: Config,
timeout: duration.Duration,
) -> Config
Deadline for one provider request, including connection setup. Between 1 ms and 5 minutes; default 10 s.
pub fn with_response_mode(
config: Config,
mode: ResponseMode,
) -> Config
pub fn with_scopes(
config: Config,
scopes: List(String),
) -> Config
Scopes requested at every login, in addition to openid.
pub fn with_sealing_key(
config: Config,
key: SealingKey,
) -> Config
The key that seals records in durable stores. New records are sealed with
it; records sealed with a key passed to with_previous_sealing_keys are
still read, so a key can be rotated without signing users out.
pub fn with_session_lifetime(
config: Config,
absolute absolute: duration.Duration,
idle idle: duration.Duration,
) -> Config
How long a session lasts in custody: absolute after login at most, and
idle without use (restore, access token, userinfo, refresh). Expired
sessions are removed with their tokens. Defaults: 12 hours absolute, 1
hour idle; idle must not exceed absolute, and absolute is at most a year.
The number of sessions per identity is not bounded.
pub fn with_signing_algorithms(
config: Config,
algorithms: List(SigningAlgorithm),
) -> Config
The ID-token and access-token algorithms Hellhound accepts.
pub fn with_startup_timeout(
config: Config,
timeout: duration.Duration,
) -> Config
One deadline for hellhound.start discovery, first keys and joining any
previous client processes. Supervised startup uses the same bound when
joining a previous tree; its background discovery retries independently.
Between 1 ms and 10 minutes; default 15 s.
pub fn with_store_timeout(
config: Config,
timeout: duration.Duration,
) -> Config
Bound on one store call. Between 1 ms and 10 minutes; default 5 s.
pub fn with_transaction_store(
config: Config,
store: store.Store,
) -> Config
Keep pending logins in this store instead of memory, so a callback may
reach another node. Needs with_sealing_key.