pub struct SessionAuthority<M, R, U, W> { /* private fields */ }Expand description
Origin facade: everything that can create or destroy sessions.
Holds the four origin-tier capabilities — a TokenMinter, a
RefreshStore, a UserStore, and a RevocationWriter — plus a
SessionPolicy. Generic (not dyn) so the assembled capability set is
visible in the type; a consumer wires concrete impls (e.g.
PasetoV4SecretMinter + Yubaba-backed
stores).
Implementations§
Source§impl<M, R, U, W> SessionAuthority<M, R, U, W>
impl<M, R, U, W> SessionAuthority<M, R, U, W>
Sourcepub fn new(minter: M, refresh: R, users: U, revoke: W) -> Self
pub fn new(minter: M, refresh: R, users: U, revoke: W) -> Self
Assemble an authority with the default policy.
Sourcepub fn with_policy(self, policy: SessionPolicy) -> Self
pub fn with_policy(self, policy: SessionPolicy) -> Self
Override the TTL policy.
Sourcepub fn with_standing_binder(self, binder: StandingBinder) -> Self
pub fn with_standing_binder(self, binder: StandingBinder) -> Self
Mint standing node bindings with binder (R732-F5). Required before
establish_bound /
rotate_bound will serve a
DeviceBinding::LanPair session.
pub fn policy(&self) -> &SessionPolicy
Sourcepub fn users(&self) -> &U
pub fn users(&self) -> &U
The held UserStore, for device-management reads
(list_devices).
Sourcepub async fn establish(
&self,
sub: UserId,
device: DeviceId,
binding: DeviceBinding,
now: i64,
) -> Result<NewSession, Error>
pub async fn establish( &self, sub: UserId, device: DeviceId, binding: DeviceBinding, now: i64, ) -> Result<NewSession, Error>
Establish a fresh session: mint a short-TTL access token (with a fresh
jti) and start a homed refresh chain for (sub, device).
Sourcepub async fn establish_bound(
&self,
sub: UserId,
device: DeviceId,
binding: DeviceBinding,
peer_key: PeerKey,
now: i64,
) -> Result<NewSession, Error>
pub async fn establish_bound( &self, sub: UserId, device: DeviceId, binding: DeviceBinding, peer_key: PeerKey, now: i64, ) -> Result<NewSession, Error>
Establish a session whose access token is bound to peer_key — the
mint half of R515.
Identical to establish except the minted claims
carry Claims::peer_key, so a verifier that has already authenticated
the connecting peer under that key can prove token holder == connecting
peer (see EdgeVerifier::verify_bound_at). A token stolen from this
client is then useless to a thief connecting under their own key.
cheers does not establish that the caller possesses peer_key —
that proof belongs to the enrollment ceremony in the consuming service
(the app proves possession of the key, presents its existing session,
and then calls this). cheers’s own HTTP ceremonies can’t do it: a
browser passkey or magic-link sign-in has no node key at all.
The refresh chain is unchanged, and deliberately does not record the
key: like binding, the peer key is a fact about this access token,
not about the chain — so rotate_bound takes it
from the caller again.
LanPair (R732-F5). For DeviceBinding::LanPair the session also
carries a standing binding — user to node key,
no expiry, ended only by revocation or a newer binding for device —
minted by the standing binder. Without
one this refuses with Error::NoStandingBinder before anything is
written.
Sourcepub async fn rotate<B: BindingResolver + ?Sized>(
&self,
presented_refresh: &str,
binding: &B,
now: i64,
) -> Result<NewSession, Error>
pub async fn rotate<B: BindingResolver + ?Sized>( &self, presented_refresh: &str, binding: &B, now: i64, ) -> Result<NewSession, Error>
Rotate presented_refresh → fresh access token + successor refresh
token. Replay detection / chain revocation are the rotator’s
(see RefreshRotator::rotate).
The binding comes from the caller’s BindingResolver because the
refresh record doesn’t carry it — guide by omission, the refresh chain
is about which session, not how it authenticated. A caller that
already knows the binding passes &DeviceBinding::… (a fixed binding is
a resolver); a generic door that only holds the refresh token passes
the product’s session rows, keyed by the chain’s (user, device).
Ordering (R730). The token is checked, then the binding resolved,
then the token spent and the access token minted. A resolver that
knows no binding yields RefreshError::Unbound with the token still
unspent and the chain untouched — never a guessed or unbound mint, and
never a half-rotated chain whose successor the client was not handed.
Replay detection still runs first, so a replayed token revokes its
chain whether or not a binding exists.
Sourcepub async fn rotate_bound<B: BindingResolver + ?Sized>(
&self,
presented_refresh: &str,
binding: &B,
peer_key: PeerKey,
now: i64,
) -> Result<NewSession, Error>
pub async fn rotate_bound<B: BindingResolver + ?Sized>( &self, presented_refresh: &str, binding: &B, peer_key: PeerKey, now: i64, ) -> Result<NewSession, Error>
Rotate into a fresh access token that is bound to peer_key (R515).
The peer key is supplied by the caller for the same reason binding is:
the refresh record is about which session, not about how this
particular access token is being presented. A node re-binds on every
rotation, which is what keeps the binding honest if the node key
changes — for a DeviceBinding::LanPair session that includes a fresh
standing binding whose higher sequence
supersedes the previous one (see establish_bound).
Sourcepub async fn revoke_session(&self, jti: &str, exp: i64) -> Result<(), Error>
pub async fn revoke_session(&self, jti: &str, exp: i64) -> Result<(), Error>
Revoke a single access token by its jti — the immediate, edge-visible
kill (within the propagation window bounded by the access TTL). exp
is the token’s own expiry: past it the entry lapses and gc drops it.
Sourcepub async fn revoke_standing_binding(&self, jti: &str) -> Result<(), Error>
pub async fn revoke_standing_binding(&self, jti: &str) -> Result<(), Error>
Revoke one standing node binding by its jti. A standing binding has
no exp, so the entry never lapses.
Sourcepub async fn revoke_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
now: i64,
) -> Result<(), Error>
pub async fn revoke_device( &self, user_id: &UserId, device_id: &DeviceId, now: i64, ) -> Result<(), Error>
Records device-level revocation intent via the UserStore, and
Revoked::Device in the revocation set — the entry that ends a
standing node binding for this device at offline replicas (R732-F6).
The entry’s at_seq is the device’s next binding sequence, consumed
from the standing binder at now: every
binding minted before it is masked, and re-enrolling the device later
mints above it and is admitted (R732-T7). With no binder configured
this authority has minted no standing bindings, so there is nothing
for a device entry to end and none is recorded.
Blocking new sessions also means revoking that device’s refresh
chains via the RefreshStore; killing an in-flight access token is
revoke_session. The product owns the
device→chain index that drives the first, so this facade exposes the
store call and the per-jti kill, not a magic “log out everywhere”.