asobi_ops_auth (asobi v0.72.5)
View SourceOps-plane identity: one actor per request, one membership check (ADR 0007).
Every /api/v1/ops request resolves to an actor -
#{id, display, source, caps, attested} - and is admitted only when the
route's capability class is in the actor's caps. Nothing else in the plane
authorises. The actor ships from the first release because it is the one part
of an identity design that is expensive to retrofit: every audit row and every
token consumer would have to be rewritten.
Two sources are built, and they are the two transports the same operator credential arrives on:
static_secret- the operator secret in theops_secretapplication env, presented as a bearer token and compared in constant time. CI, the CLI and any server-side caller. There is no default credential: a deployment that has not configured one rejects every bearer request, so an install that never reads the docs is closed rather than wide open.local_user- the console's session cookie plus itsx-csrf-tokenheader (asobi_console_session). A browser exchanges the secret for a session once and then never holds it again, so an XSS on the console cannot exfiltrate a credential that outlives the page.
A bearer token wins when both are present, because a caller that sent one meant to use it and silently answering as somebody else's session would be worse than a 403.
A player bearer token is not a credential here. This module never consults
asobi_auth_cache, so no player - and no guest - can reach the plane, which
is the whole point of taking these routes off the player-scoped check.
A session stands on its own once minted: it resolves without re-reading
ops_secret, and it is revoked by logging out or by the node restarting.
That is deliberate - a session has its own expiry and its own revocation, so
tying it to a value that can change underneath it would end sessions at
surprising moments without ending the bearer access that matters.
cloud- a short-lived, env-scoped token minted byasobi_saasafter its own ownership check, presented as a bearer token and verified byasobi_ops_token. The tenant's role is mapped onto capability classes at mint time, so the string "owner" never reaches this plane, and the token carries only the classes it was minted with rather than every class.
A bearer token is tried as the operator secret first and as a minted token
second. The two cannot be confused: a minted token is three dot-separated
parts beginning v1., and the secret comparison is over a hash of the whole
value, so neither can be mistaken for the other.
Every rejection is 403 with the same body whatever the cause, so a caller cannot tell "no secret configured" from "wrong secret" from "not authorised for this class".
Summary
Functions
The operator label from x-asobi-operator, or the default display name.
Resolve a request to an ops actor.
Nova security callback for the ops route group.
Whether Presented is the configured operator secret.
Types
-type actor() :: #{id := binary(), display := binary(), source := source(), caps := [asobi_ops_caps:class()], attested := boolean()}.
-type source() :: static_secret | cloud | local_user | shell.
Functions
-spec display(cowboy_req:req()) -> binary().
The operator label from x-asobi-operator, or the default display name.
Attribution, never authority: it is read only here, after the credential has
already been accepted, and it never reaches the capability check. Spoofing it
buys a wrong name in the audit trail and nothing else, which is why the actor
carries it with attested => false.
Dropped rather than trusted when it is multi-valued (cowboy joins repeated headers with a comma), empty, over 64 bytes, or carries anything outside printable ASCII - the value lands in audit rows and logs.
-spec resolve(cowboy_req:req()) -> {ok, actor()} | {error, atom()}.
Resolve a request to an ops actor.
Bearer first, then the console session cookie. Both fail closed: an unset or
empty ops_secret rejects every bearer request, and a cookie without a
matching x-csrf-token is not a credential.
-spec verify(cowboy_req:req()) -> {true, #{ops_actor := actor()}} | {false, 403, #{binary() => binary()}, binary()}.
Nova security callback for the ops route group.
Admits with the actor in auth_data, or rejects with 403 and a flat error
body.
Whether Presented is the configured operator secret.
The one entry point the console login uses, so the constant-time comparison
and the no-default rule are stated in exactly one place. false when nothing
is configured, never true.