cheers_verify/edge.rs
1//! The [`EdgeVerifier`] facade — what a CF Worker holds.
2
3use cheers_core::{Claims, Error, TokenVerifier};
4
5use crate::revocation::RevocationReader;
6
7/// Edge facade: verify a token, then check it hasn't been revoked.
8///
9/// Holds a [`TokenVerifier`] and a [`RevocationReader`] — and nothing else.
10/// There is **no `TokenMinter` in this type**, so an edge built on it cannot
11/// forge sessions even if compromised; that absence is the whole point of the
12/// asymmetric codec split. Generic (not `dyn`) so the "no minter" property is a
13/// fact about the type, not a runtime convention. Pair a
14/// [`PasetoV4PublicVerifier`](crate::PasetoV4PublicVerifier) with a CF-KV-backed
15/// `RevocationReader`.
16pub struct EdgeVerifier<V, Rd> {
17 verifier: V,
18 revoked: Rd,
19}
20
21impl<V, Rd> EdgeVerifier<V, Rd>
22where
23 V: TokenVerifier + Send + Sync,
24 Rd: RevocationReader,
25{
26 pub fn new(verifier: V, revoked: Rd) -> Self {
27 Self { verifier, revoked }
28 }
29
30 /// Verify `token` against `now`, then reject it if its `jti` is revoked.
31 ///
32 /// Two stages, cheapest first: cryptographic verification (stateless, local
33 /// to the edge) gates the revocation read, so a forged or expired token
34 /// never reaches the revocation set. A token that verifies but whose `jti`
35 /// is revoked returns [`Error::Revoked`].
36 pub async fn verify_at(&self, token: &str, now: i64) -> Result<Claims, Error> {
37 let claims = self.verifier.verify_at(token, now)?;
38 if self.revoked.is_revoked(&claims.jti).await? {
39 return Err(Error::Revoked);
40 }
41 Ok(claims)
42 }
43}