In-process cache of this node's access tokens, one per application environment.
A token is cached under the canonical base URL intake resolved the request to -- not under the URL the caller supplied. A caller asks for the URL it is about to call; intake answers with the base URL of the environment that URL belongs to, and subsequent calls anywhere under that base URL reuse the entry. A node that calls several targets therefore holds several tokens.
Lookup is a plain exact-or-path-prefix comparison, with the longest match winning. The SDK deliberately does not normalize: intake owns that rule, and a miss costs one extra request rather than a wrong answer.
All reads and writes go through this GenServer's mailbox, so they are fully serialized -- unlike the Python and Ruby ports, there is no lock-free fast path here for a concurrent write to race, so this holds a plain map and mutates it directly. Do not add copy-on-write; there is nothing here for it to protect.
Tokens are proactively refreshed when they are within two minutes of expiry to avoid serving one that dies in flight -- an expired token can never be revived, only replaced.
Why a mint failed
token/1 answers nil for every failure; token_result/1 answers
{:error, reason} with the reason for that very mint, and is what
EndPointBlank.Authorization.header/1 uses to refuse an outbound call with a
message that says why. The detail matters:
intake answers 401 for a credential it has rejected, and that is permanent
until a human re-issues the credential, where a 5xx or a refused connection
will likely be gone on the next call. last_failure/1 reports the last one
for a given URL, so a caller that wants to stop hammering intake -- or
alarm -- can tell those apart. See EndPointBlank.Commands.GenerateAccessToken
for the full taxonomy.
Summary
Types
Why the last mint for a URL failed, or nil if the last one succeeded (or
none has been attempted).
Functions
Returns a specification to start this module under a supervisor.
Discards every held token, and every recorded failure.
Returns true if a token covering base_url is held and not about to expire.
Discards a held token, but only if it is still the one the caller had.
Returns why the last mint attempted for base_url failed, or nil if the
last one succeeded or none has been made.
Returns a valid access token for base_url, minting one if no usable entry
covers it.
Like token/1, but says why no token could be produced.
Types
@type failure() :: EndPointBlank.Commands.GenerateAccessToken.failure()
Why the last mint for a URL failed, or nil if the last one succeeded (or
none has been attempted).
Exactly EndPointBlank.Commands.GenerateAccessToken.failure/0, so every
SDK reports the same set: :credential_rejected and
{:request_rejected, status} are permanent -- retrying changes nothing
until the credential is re-issued or the environment is registered --
while {:server_error, status} and {:transport_error, reason} are
transient.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Discards every held token, and every recorded failure.
Returns true if a token covering base_url is held and not about to expire.
Userinfo, query and fragment are removed first, as for token/1.
Discards a held token, but only if it is still the one the caller had.
Every request in flight when a token is rejected reports the same stale value. Only the first of them should cause a mint — the rest are holding a token that has already been replaced, and clearing on their behalf would discard a good token and stampede intake.
The lookup is by token value because a rejected caller has a token, not a base_url.
Returns why the last mint attempted for base_url failed, or nil if the
last one succeeded or none has been made.
Pass the same URL you passed to token/1: the record is kept under the URL
the caller asked about (with its userinfo, query and fragment removed), not
under the canonical base URL intake resolved it to, because on a failure
there is no resolved base URL to key it under.
Only the 64 most recently failed URLs are kept, so
this answers nil for a URL whose failure has since been pushed out by
newer ones. Ask about a URL you just called and it will be there.
case AccessTokens.token(url) do
nil ->
case AccessTokens.last_failure(url) do
:credential_rejected -> alarm("re-issue the EndPointBlank credential")
{:request_rejected, status} -> alarm("intake refused the request: #{status}")
_transient -> :ok
end
token ->
{:ok, token}
endSee failure/0 for the shapes and which of them are permanent. Answers
nil -- never raises -- for a URL nothing was recorded under, including any
argument that is not a binary.
A successful mint clears the record for the URL it was asked about and for every URL the resolved base URL covers, so this cannot go on reporting a failure that has since been fixed.
Returns a valid access token for base_url, minting one if no usable entry
covers it.
base_url is the URL you are about to call. Its userinfo, query and
fragment are removed first (EndPointBlank.OutboundUrl.strip/1): they are
never sent to intake, logged, or used as a cache or failure key. The scheme
and host are lowercased and a default or empty port is dropped; the path is
sent as written. intake normalizes it and matches it against registered
base URLs by longest path prefix. A URL that is not a non-empty string, or
that OutboundUrl.strip/1 refuses (not an absolute http or https URL
with a host, or a port outside 1..65535), is refused without asking intake.
Returns nil rather than raising if a token cannot be produced -- which
includes a response that carried a token but no base_url (nothing to
cache it under) as well as the cache failing to answer in time. A nil
means the call must not be made: there is no fallback credential for a
provider (sc-1469).
Use token_result/1 to get the reason for this mint along with it, or
last_failure/1 to find out afterwards which kind of failure a nil was.
@spec token_result(term()) :: {:ok, String.t()} | {:error, failure() | :token_cache_unavailable | :missing_base_url | :invalid_base_url | :missing_credentials | {:unexpected, term()}}
Like token/1, but says why no token could be produced.
Returns {:ok, token} or {:error, reason}, where reason is a
failure/0 from this very mint -- not whatever last_failure/1 holds by
the time the caller asks, which a concurrent caller's mint may already have
replaced or cleared -- or :token_cache_unavailable when this cache did not
answer at all (it was not running, or a mint against a hung intake outlasted
the 20000 ms the call allows). A base_url that is not a
non-empty string answers {:error, :missing_base_url}, and one that
OutboundUrl.strip/1 refuses (not an absolute http or https URL with a
host, or a port outside 1..65535) {:error, :invalid_base_url}, without
asking intake.
Two more answers are not a mint intake failed, so neither is recorded for
last_failure/1 nor drops a held entry: {:error, :missing_credentials}
when client_id or client_secret is not configured (nothing is sent),
and {:error, {:unexpected, reason}} when minting raised, threw, or the
HTTP client answered something that is not a transport failure. reason
is what was raised, {kind, term} for a throw or exit, or the client's
error term. Never raises.