Lockspire. Protocol. DcrPolicy
(lockspire v1.6.1)
Copy Markdown
Resolves the effective DCR policy for an inbound RFC 7591 client registration request as the intersection of:
- ServerPolicy DCR allowlists (the operator-configured envelope)
- InitialAccessToken policy_overrides (when an IAT was redeemed; nil otherwise)
- Inbound RFC 7591 client metadata (what the registrant requested)
Intersection-only: the resolver never widens any allowlist. IAT overrides are assumed
already narrowed to the server allowlist by the IAT mint path. If an out-of-allowlist
override remains (for example, policy was tightened after
IAT mint), MapSet.intersection/2 naturally drops it — never widens.
Returns:
{:ok, %Resolved{}} — the effective policy bound to this request
{:error, :invalid_client_metadata, %{field, reason, allowed}} — first inbound value
not in the server allowlist; field names the offending axis (e.g. `:scope`,
`:grant_types`, `:redirect_uri_scheme`, `:redirect_uri_host`,
`:token_endpoint_auth_method`).Note on redirect_uris data carriage: the Resolved substruct exposes the deduplicated
allowed_redirect_uri_schemes and allowed_redirect_uri_hosts axes (the resolver's
job is bound-checking, not data carriage). The validated redirect_uris list itself is
NOT carried on Resolved.t(). The intake validator and controller take the original
inbound["redirect_uris"] list directly once the resolver returns
:ok.
Mirrors Lockspire.Protocol.ParPolicy shape (the only existing resolver precedent in
the repo).
Empty allowlist semantics (operator UX hazard)
Migration A defaults every dcr_allowed_* axis to [] (Postgres DEFAULT '{}'). A host
app that runs the migration but never runs an operator-mint script will, by default,
reject every DCR request with {:error, :invalid_client_metadata, %{reason: :not_in_allowlist, allowed: []}}. The empty allowed list tells the
registrant "no values are allowed" — not "the operator hasn't configured DCR yet."
This is intended secure-by-default behaviour: the operator must explicitly populate
allowlists. It can be confusing if paired with registration_policy != :disabled. The
intake validator detects "registrationpolicy != :disabled AND every
dcr_allowed* is []" and emit a more informative error (reason: :dcr_unconfigured)
to disambiguate from a legitimate per-axis allowlist rejection. Until then, operators
diagnosing "every DCR request returns :not_in_allowlist with allowed: []" should check
the server-policy DCR fields are populated.
Summary
Types
Functions
@spec resolve(Lockspire.Domain.ServerPolicy.t(), map() | keyword() | nil, map()) :: {:ok, struct()} | {:error, :invalid_client_metadata, error_detail()}