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:

Defaults

SettingDefaultSetter
redirect URIthe one given to newwith_allowed_redirect_uris (exact match)
scopesopenidwith_scopes
ID-token algorithmsRS256, PS256, ES256, EdDSA (none and HMAC unrepresentable)with_signing_algorithms
response modequerywith_response_mode
RFC 9207 issrequired when advertisedwith_issuer_parameter
PKCEadvertised S256 requiredwith_pkce_advertisement
TLS trustsystem CA storewith_trust
destinationspublic addresses onlywith_destinations, with_allowed_hosts
provider request10 swith_request_timeout
provider response body1 MiBwith_max_response_bytes
startup discovery, keys and previous-process cleanup15 swith_startup_timeout
store call5 swith_store_timeout
complete_login, end to end30 swith_login_timeout
pending login10 minwith_login_lifetime
pending logins in the built-in store100 000with_max_pending_logins
session12 h absolute, 1 h idlewith_session_lifetime
clock tolerance (iat, nbf, auth_time; never exp)5 swith_clock_tolerance
refresh before expiry30 swith_refresh_margin
wait for another request’s refresh5 swith_refresh_wait
session and login storagein memorywith_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

  • PublicClient

    A 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

  • InvalidIssuer

    The issuer must be an absolute https URI without query or fragment.

  • InvalidClientId
  • InvalidRedirectUri

    The redirect URI must be absolute, without fragment, and https unless its host is a loopback address or localhost.

  • InvalidAllowedRedirectUri(String)

    A URI given to with_allowed_redirect_uris breaks the redirect URI rule above.

  • AllowedRedirectUrisNeedLogin

    with_allowed_redirect_uris on a service_client or resource_server, which have no login.

  • InvalidScope(String)

    A scope token contains a byte outside RFC 6749 §3.3 or is empty.

  • NoSigningAlgorithms
  • SigningKeyUnusable

    The signing key cannot produce any assertion algorithm.

  • InvalidTrustAnchors
  • InvalidAllowedHost(String)
  • InvalidLimit(Limit)

    A numeric setting is out of range; describe_config_error names the range and the setter.

  • UnadvertisedPkceRequiresConfidentialClient

    AssumeS256WhenUnadvertised with PublicClient.

  • EmptyClientSecret

    A client secret is empty.

  • ClientSecretTooShort

    A client_secret_jwt secret is shorter than 32 bytes, the minimum HMAC key for HS256 (RFC 7518 §3.2).

  • ServiceClientNeedsCredentials

    service_client with PublicClient: a service authenticates itself.

  • SealingKeyRequired

    A 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

  • PublicInternetOnly

    Public unicast addresses only.

  • AllowLoopbackForTesting

    Also loopback addresses. For local test providers only.

  • AllowPrivateNetwork

    Also 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

  • RequireIssuerWhenAdvertised

    Require iss when the provider advertises authorization_response_iss_parameter_supported.

  • AlwaysRequireIssuer

    Always 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

  • RequireAdvertisedS256

    The provider must list S256 in code_challenge_methods_supported.

  • AssumeS256WhenUnadvertised

    Also accept a provider whose metadata omits code_challenge_methods_supported entirely. A provider that lists the field without S256 is still refused. Refused for public clients.

How the provider returns the authorization response. May gain JARM.

pub type ResponseMode {
  Query
  FormPost
}

Constructors

  • Query

    Redirect with a query string (the OAuth default).

  • FormPost

    Cross-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

  • SealingKeyNot32Bytes

    A 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

  • NotAPrivateSigningJwk

    The text is not a JSON Web Key with private signing material.

  • WeakRsaKey

    An RSA key below 2048 bits (RFC 7518 §3.3).

  • NotForSigning

    The key’s use or key_ops does not permit signing.

Trust anchors for provider TLS.

pub type Trust {
  SystemTrust
  TrustAnchorsPem(String)
}

Constructors

  • SystemTrust

    The operating system’s trust store.

  • TrustAnchorsPem(String)

    Only the certificates in this PEM text.

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 secret(value: String) -> Secret

Wrap a client secret.

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.

pub fn with_trust(config: Config, trust: Trust) -> Config
✨ Search Document