Skip to main content

Module revocation

Module revocation 

Source
Expand description

The write side of the revocation split, and the issuer’s signed revocation set (R732-F6).

Edge-verifiable auth (R019) keeps revocation’s two sides physically apart. This crate holds the origin half:

  • RevocationWriter — the cold path, run at the origin on logout / device-revoke / membership removal. Records a Revoked entry and advances the store’s epoch.
  • RevocationPublisher — signs the store’s current contents as a RevocationSet under the issuer key, membership entries tagged under their resource’s revocation key (OwnershipStore::revocation_key; R732-T10), for the publish route and for offline peers to replicate (cheers_verify::ReplicatedRevocations).
  • The hot-path reader (cheers_verify::RevocationReader) lives in cheers-verify — the verify-only edge holds it without the power to revoke anyone else’s sessions.

The set is eventually consistent by design: a revoke here propagates to edge readers asynchronously (KV replication / gossip). For access tokens the access TTL bounds the lag (see SessionPolicy); for standing credentials (membership snapshots, node bindings) revocation is the only bound. Full contract on cheers_verify::RevocationReader.

§The epoch

A store is one issuer’s revocation log: it holds a single persistent epoch, and every write that changes the contents advances it by next_epoch: max(epoch + 1, now). Strictly monotonic either way; the clock floor is there so a store restored from an older backup still issues epochs above the ones replicas already hold — a plain counter would restart below them, and every replica would refuse every set until it caught up. Nothing orders sets by wall-clock time: replicas compare epochs only.

§Revoking a session vs. a whole login

§Identity and bound (R732-T7)

A store holds one row per identity ((kind, subject, resource_kind, resource_id), see revoked_columns) carrying one bound — the jti’s exp, the device’s at_seq, the membership’s at_epoch (see Revoked). Re-revoking an identity keeps the larger bound, and the epoch advances only when a row was inserted or its bound rose. A jti with an exp lapses once exp <= now: store gc drops it, and RevocationPublisher::current omits it even before gc runs.

§Revoking a session vs. a whole login

RevocationWriter::revoke with Revoked::Jti kills one access token immediately (edge-visible within the propagation window). The coarser flows compose it with the refresh cold path:

SessionAuthority is where these calls are composed — it holds both the RevocationWriter and the RefreshStore.

Structs§

MemoryRevocationStore
In-process revocation log — tests, and single-process deployments that do not need the set to survive a restart. Clones share one log, so an authority-side writer and an edge-side reader see the same set.
RevocationPublisher
Signs a store’s current contents as its issuer’s RevocationSet.
RevocationSnapshot
A store’s contents at one epoch, read atomically.
RevokedColumns
One entry as a relational row, shared by the SQL stores (cheers-sqlx, cheers-turso). The key is (kind, subject, resource_kind, resource_id) — the entry’s identity: subject is the jti, the device id, or the member’s principal in wire form (user:alice, key:.., migration 0015); the resource columns are "" unless the entry is a membership. The bound lives in bound (device at_seq, membership at_epoch; NULL for a jti) or expires_at (a jti’s exp; NULL = never lapses).
SignedRevocationSet
A RevocationSet signed under the issuer key.

Traits§

RevocationWriter
Origin-side revocation log. Held by SessionAuthority on the cold path and by RevocationPublisher.

Functions§

epoch_from_sql
An epoch read back from a signed 64-bit SQL column.
next_epoch
The epoch a write advances to: max(current + 1, now). See the module docs.
revoked_columns
The row of entry. A u64 bound past i64::MAX is stored as i64::MAX, which still masks every sequence a store can hand out.
revoked_from_columns
The inverse of revoked_columns.