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