voauth
OAuth2 access-token vault with proactive refresh and bounded retry.
The vault holds the current access token, refreshes it before it
expires, retries transient failures, and gives up with a typed
error when the refresh token has been revoked. It does not perform
the OAuth flow itself; you provide a Refresh callback that talks
to your provider’s token endpoint.
One vault per service. To handle multiple providers, start one
Vault per provider and supervise them with your application’s
own supervisor.
A vault always starts without a token. Install one with set_token
after the user completes the OAuth flow, or after rehydrating from
durable storage at boot.
See the README for a quickstart.
Types
Configuration for one vault.
Construct with config(refresh:) and tweak with the with_*
setters. The type is opaque so that future settings stay
non-breaking.
pub opaque type Config
Hook fired after every successful refresh, typically to persist the new token. Errors are logged and otherwise ignored; the vault keeps running.
pub type OnRefresh =
fn(Token) -> Result(Nil, String)
Provider-specific refresh-token grant. The vault calls this when the cached token expires or a proactive refresh fires.
pub type Refresh =
fn(String) -> Result(RefreshResponse, RefreshError)
Returned by a Refresh callback. The variant tells the vault
whether to retry the failure or give up. Application code can
pattern-match on RefreshUnauthorized to drive a “please
reconnect” UI.
pub type RefreshError {
RefreshRetryable(reason: String)
RefreshUnauthorized(reason: String)
}
Constructors
-
RefreshRetryable(reason: String)Transient failure — the vault will retry with backoff. Network drop, 5xx, timeout, parse error.
-
RefreshUnauthorized(reason: String)The refresh token has been rejected. The user must reauthorise. OAuth2
invalid_grant/invalid_token, revoked credentials.
What an OAuth2 refresh endpoint may return. Per RFC 6749 §5.1/§6
only access_token is required; the rest are optional and carry
forward from the previous token via merge_response.
pub type RefreshResponse {
RefreshResponse(
access_token: String,
expires_in: option.Option(Int),
refresh_token: option.Option(String),
scope: option.Option(String),
token_type: option.Option(String),
)
}
Constructors
-
RefreshResponse( access_token: String, expires_in: option.Option(Int), refresh_token: option.Option(String), scope: option.Option(String), token_type: option.Option(String), )
An OAuth2 token as returned by an authorisation or refresh
endpoint. expires_in is in seconds, as on the wire.
pub type Token {
Token(
access_token: String,
expires_in: Int,
refresh_token: option.Option(String),
scope: String,
token_type: String,
)
}
Constructors
-
Token( access_token: String, expires_in: Int, refresh_token: option.Option(String), scope: String, token_type: String, )
A long-lived process holding the access token for one OAuth service.
Returned by start. Pass it through your application’s context.
pub opaque type Vault
Returned by the public vault API.
pub type VaultError {
RefreshFailed(RefreshError)
NoRefreshToken
StartError(reason: String)
}
Constructors
-
RefreshFailed(RefreshError)The provider’s
Refreshcallback returned an error. -
NoRefreshTokenNo token to refresh. Call
set_tokento install one, or check that your provider issued arefresh_token(some require anoffline_accessscope). -
StartError(reason: String)The vault’s actor failed to start.
reasonis the underlying failure description.
Values
pub fn config(
refresh refresh: fn(String) -> Result(
RefreshResponse,
RefreshError,
),
) -> Config
Build a Config with defaults. The provider-specific Refresh
callback is the only required argument.
Defaults:
on_refresh:Nonecall_timeout_ms: 30_000 — must exceed worst-caseRefreshHTTP latency.init_timeout_ms: 1_000refresh_at_percent: 80 — proactive refresh at 80% ofexpires_in.min_refresh_delay_ms: 60_000 — floor on the proactive delay.retry_backoff_ms:[30_000, 60_000, 120_000](30s/1m/2m).
pub fn get_token(vault: Vault) -> Result(String, VaultError)
Return the current valid access token. Refreshes synchronously if
the cached token has expired. Returns NoRefreshToken if no token
has been installed yet.
pub fn merge_response(
previous: Token,
resp: RefreshResponse,
) -> Token
Merge a refresh response onto the previous token, carrying forward any field the server omitted.
pub fn refresh_now(vault: Vault) -> Result(Nil, VaultError)
Force a refresh now, regardless of remaining validity. Returns
NoRefreshToken if no token has been installed yet.
pub fn refresh_response_decoder() -> decode.Decoder(
RefreshResponse,
)
Decoder for a refresh response. Fields other than access_token
are optional per RFC 6749 §6 and decode to None when absent.
pub fn set_token(vault: Vault, token: Token) -> Nil
Install or replace the vault’s token. Schedules a fresh proactive refresh. Use it after the OAuth flow completes, or after a re-authorisation hands you a new token.
pub fn start(config: Config) -> Result(Vault, VaultError)
Start a vault. The vault begins without a token; install one with
set_token before calling get_token or refresh_now.
pub fn supervised(
config: Config,
) -> supervision.ChildSpecification(Vault)
Build a child specification for a gleam_otp supervisor. On
restart the supervisor calls start with the same config. To
rehydrate a persisted token on restart, write your own
supervision.worker that reads from durable storage and calls
start and set_token.
pub fn token_decoder() -> decode.Decoder(Token)
Decoder for an OAuth2 token JSON object.
pub fn with_call_timeout_ms(config: Config, ms: Int) -> Config
Timeout (ms) for synchronous calls into the vault. Must exceed
worst-case Refresh HTTP latency, because get_token blocks on
a refresh when the cached token has expired.
pub fn with_init_timeout_ms(config: Config, ms: Int) -> Config
Timeout (ms) for the actor’s initialise phase.
pub fn with_min_refresh_delay_ms(
config: Config,
ms: Int,
) -> Config
Floor (ms) on the proactive delay. Guards against providers that
hand out very short expires_in values.
pub fn with_on_refresh(
config: Config,
callback: fn(Token) -> Result(Nil, String),
) -> Config
Persist tokens after every successful refresh. Runs inside the vault’s mailbox; keep it fast.
pub fn with_refresh_at_percent(
config: Config,
percent: Int,
) -> Config
Proactive refresh fires at this percent of expires_in. Default 80.