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