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