use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::RwLock;
#[async_trait::async_trait]
pub trait TokenRevocationStore: Send + Sync {
async fn revoke(&self, jti: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
async fn is_revoked(&self, jti: &str)
-> Result<bool, Box<dyn std::error::Error + Send + Sync>>;
}
pub struct InMemoryRevocationStore {
revoked: Arc<RwLock<HashSet<String>>>,
}
impl InMemoryRevocationStore {
pub fn new() -> Self {
Self {
revoked: Arc::new(RwLock::new(HashSet::new())),
}
}
}
impl Default for InMemoryRevocationStore {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl TokenRevocationStore for InMemoryRevocationStore {
async fn revoke(&self, jti: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.revoked.write().await.insert(jti.to_string());
Ok(())
}
async fn is_revoked(
&self,
jti: &str,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
Ok(self.revoked.read().await.contains(jti))
}
}
#[cfg(feature = "revocation-redis")]
pub struct RedisRevocationStore {
client: redis::Client,
ttl: u64,
}
#[cfg(feature = "revocation-redis")]
impl RedisRevocationStore {
pub fn new(redis_url: &str, ttl: u64) -> Result<Self, redis::RedisError> {
let client = redis::Client::open(redis_url)?;
Ok(Self { client, ttl })
}
fn connection(&self) -> Result<redis::Connection, redis::RedisError> {
self.client.get_connection()
}
}
#[cfg(feature = "revocation-redis")]
#[async_trait::async_trait]
impl TokenRevocationStore for RedisRevocationStore {
async fn revoke(&self, jti: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut conn = self.connection()?;
redis::cmd("SETEX")
.arg(jti)
.arg(self.ttl)
.arg("1")
.execute(&mut conn);
Ok(())
}
async fn is_revoked(
&self,
jti: &str,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
let mut conn = self.connection()?;
let exists: bool = redis::cmd("EXISTS").arg(jti).query(&mut conn)?;
Ok(exists)
}
}