systemprompt_api/services/middleware/jwt/
revocation.rs1use std::sync::Arc;
14use systemprompt_identifiers::AccessTokenId;
15use systemprompt_models::execution::context::ContextExtractionError;
16use systemprompt_oauth::repository::{JtiRevocationCache, OAuthRepository};
17use systemprompt_traits::CacheEpochs;
18
19#[derive(Clone)]
20pub struct JtiRevocationChecker {
21 repo: Arc<OAuthRepository>,
22 cache: Arc<JtiRevocationCache>,
23}
24
25impl std::fmt::Debug for JtiRevocationChecker {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("JtiRevocationChecker")
28 .finish_non_exhaustive()
29 }
30}
31
32impl JtiRevocationChecker {
33 pub fn from_repository(repo: OAuthRepository, epochs: CacheEpochs) -> Self {
34 Self {
35 repo: Arc::new(repo),
36 cache: Arc::new(JtiRevocationCache::new(epochs)),
37 }
38 }
39
40 pub async fn ensure_not_revoked(
41 &self,
42 jti: &AccessTokenId,
43 ) -> Result<(), ContextExtractionError> {
44 match self.cache.peek(jti) {
45 Some(true) => return Err(ContextExtractionError::Revoked),
46 Some(false) => return Ok(()),
47 None => {},
48 }
49
50 let epoch = self.cache.epoch();
51 let revoked = self.repo.is_jti_revoked(jti).await.map_err(|e| {
52 ContextExtractionError::DatabaseError {
53 context: "JTI revocation lookup failed".to_owned(),
54 source: e.into(),
55 }
56 })?;
57 self.cache.record(jti, revoked, epoch);
58 if revoked {
59 Err(ContextExtractionError::Revoked)
60 } else {
61 Ok(())
62 }
63 }
64}