Packages
attesto_phoenix
2.2.0
2.3.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.2
0.14.1
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
Phoenix/Ecto OAuth 2.0 / OIDC authorization server layer over attesto: authorization, token, PAR, revocation, discovery, JWKS, UserInfo, protected-resource plugs, and Ecto-backed token stores.
Current section
Files
Jump to
Current section
Files
lib/attesto_phoenix/client_id_metadata/cache.ex
defmodule AttestoPhoenix.ClientIdMetadata.Cache do
@moduledoc """
Behaviour for caching a validated Client ID Metadata Document - CIMD
(`draft-ietf-oauth-client-id-metadata-document-01`, IETF OAuth WG).
CIMD lets a client identify itself with no prior registration by using an
HTTPS URL as its `client_id`; the authorization server dereferences that URL
(`AttestoPhoenix.ClientIdMetadata.Fetcher`) and validates the returned
document (`Attesto.ClientIdMetadata.validate_document/2`). This behaviour is
the seam through which the resolver remembers a *successfully validated*
document so that not every authorization request reaches out to the network.
## What may be cached
Only a document that has passed validation is stored, and only with an
`expires_at` the resolver derives from the response's HTTP freshness
directives (`Cache-Control: max-age` / `Expires`, RFC 9111), clamped to the
host's configured bounds. The draft (§6) and RFC 9111 forbid caching error
responses or invalid/malformed documents, so an implementation of this
behaviour is only ever handed metadata the caller already accepted; it does
no validation of its own.
## Cache key and value
The key is the CIMD `client_id` URL (the same string the client presented and
the document's `client_id` equals). The value is the validated, string-keyed
metadata map together with its `expires_at`. `get/1` MUST treat an expired
entry as a miss - freshness is re-checked on read, never honored past
`expires_at` - so an implementation that cannot cheaply evict still cannot
serve a stale document.
## Default and the opt-out
The default implementation is `AttestoPhoenix.ClientIdMetadata.Cache.Ecto`,
which persists the entry to Postgres (table `attesto_client_id_metadata`,
swept by `AttestoPhoenix.Store.Sweeper`) so the cache is coherent across a
cluster and the outbound fetch fan-out is bounded under load. A single-node
deployment may opt into the per-node
`AttestoPhoenix.ClientIdMetadata.Cache.ETS` instead - a per-node cache is
correct here because a miss simply re-fetches - by configuring the `:cache`
module under `AttestoPhoenix.Config`'s `:client_id_metadata` key.
"""
@typedoc """
A validated, string-keyed CIMD metadata map - the document
`Attesto.ClientIdMetadata.validate_document/2` returned and the caller
accepted. Only such a map is ever stored or returned.
"""
@type metadata :: map()
@doc """
Looks up the cached metadata for a CIMD `client_id` URL.
Returns `{:ok, metadata}` only for an entry that is present AND still fresh
(`expires_at` strictly in the future); an absent or expired entry is a
`:miss`. Expiry MUST be re-checked here, so an implementation never serves a
document past the `expires_at` it was stored with - an unswept expired row is
a miss, not a stale hit.
"""
@callback get(url :: String.t()) :: {:ok, metadata()} | :miss
@doc """
Stores validated `metadata` for a CIMD `client_id` URL until `expires_at`.
The caller passes this only after `Attesto.ClientIdMetadata.validate_document/2`
succeeds and after deriving `expires_at` from the response's HTTP freshness
directives clamped to the configured bounds; an implementation MUST NOT be
asked to cache an error or an invalid document (draft §6 / RFC 9111). A
re-fetched document legitimately supersedes a stale one, so `put/3` replaces
any existing entry for the same `url` rather than failing on conflict.
"""
@callback put(url :: String.t(), metadata :: metadata(), expires_at :: DateTime.t()) :: :ok
end