chore: sync repository to current working state
Some checks failed
Build and Test / Build Server (Linux) (push) Has been cancelled
Build and Test / Build Agent (Windows) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Build Summary (push) Has been cancelled
Run Tests / Test Server (push) Has been cancelled
Run Tests / Test Agent (push) Has been cancelled
Run Tests / Code Coverage (push) Has been cancelled
Run Tests / Lint and Format Check (push) Has been cancelled
Some checks failed
Build and Test / Build Server (Linux) (push) Has been cancelled
Build and Test / Build Agent (Windows) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Build Summary (push) Has been cancelled
Run Tests / Test Server (push) Has been cancelled
Run Tests / Test Agent (push) Has been cancelled
Run Tests / Code Coverage (push) Has been cancelled
Run Tests / Lint and Format Check (push) Has been cancelled
Brings azcomputerguru/guru-connect up to the authoritative working copy that had been maintained in the claudetools monorepo: Phase 1 security and infrastructure (middleware, metrics, utils, token blacklist, deployment scripts, security audits) plus the native-remote-control integration spec. Preserves the repo .gitignore, .cargo, and server/static/downloads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
164
server/src/auth/token_blacklist.rs
Normal file
164
server/src/auth/token_blacklist.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
//! Token blacklist for JWT revocation
|
||||
//!
|
||||
//! Provides in-memory token blacklist for immediate revocation of JWTs.
|
||||
//! Tokens are automatically cleaned up after expiration.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{info, debug};
|
||||
|
||||
/// Token blacklist for revocation
|
||||
///
|
||||
/// Maintains a set of revoked token signatures. When a token is revoked
|
||||
/// (e.g., on logout or admin action), it's added to this blacklist and
|
||||
/// all subsequent validation attempts will fail.
|
||||
#[derive(Clone)]
|
||||
pub struct TokenBlacklist {
|
||||
/// Set of revoked token strings
|
||||
tokens: Arc<RwLock<HashSet<String>>>,
|
||||
}
|
||||
|
||||
impl TokenBlacklist {
|
||||
/// Create a new empty blacklist
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tokens: Arc::new(RwLock::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a token to the blacklist (revoke it)
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `token` - The full JWT token string to revoke
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// blacklist.revoke("eyJ...").await;
|
||||
/// ```
|
||||
pub async fn revoke(&self, token: &str) {
|
||||
let mut tokens = self.tokens.write().await;
|
||||
let was_new = tokens.insert(token.to_string());
|
||||
|
||||
if was_new {
|
||||
debug!("Token revoked and added to blacklist (length: {})", token.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a token has been revoked
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `token` - The JWT token string to check
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if the token is in the blacklist (revoked), `false` otherwise
|
||||
pub async fn is_revoked(&self, token: &str) -> bool {
|
||||
let tokens = self.tokens.read().await;
|
||||
tokens.contains(token)
|
||||
}
|
||||
|
||||
/// Get the number of tokens currently in the blacklist
|
||||
pub async fn len(&self) -> usize {
|
||||
let tokens = self.tokens.read().await;
|
||||
tokens.len()
|
||||
}
|
||||
|
||||
/// Check if the blacklist is empty
|
||||
pub async fn is_empty(&self) -> bool {
|
||||
let tokens = self.tokens.read().await;
|
||||
tokens.is_empty()
|
||||
}
|
||||
|
||||
/// Remove expired tokens from blacklist (cleanup)
|
||||
///
|
||||
/// This should be called periodically to prevent memory buildup.
|
||||
/// Tokens that can no longer be validated (expired) are removed.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `jwt_config` - JWT configuration for validating token expiration
|
||||
///
|
||||
/// # Returns
|
||||
/// Number of tokens removed from blacklist
|
||||
pub async fn cleanup_expired(&self, jwt_config: &super::JwtConfig) -> usize {
|
||||
let mut tokens = self.tokens.write().await;
|
||||
let original_len = tokens.len();
|
||||
|
||||
// Remove tokens that fail validation (expired)
|
||||
tokens.retain(|token| {
|
||||
// If token is expired (validation fails), remove it from blacklist
|
||||
jwt_config.validate_token(token).is_ok()
|
||||
});
|
||||
|
||||
let removed = original_len - tokens.len();
|
||||
|
||||
if removed > 0 {
|
||||
info!("Cleaned {} expired tokens from blacklist ({} remaining)", removed, tokens.len());
|
||||
}
|
||||
|
||||
removed
|
||||
}
|
||||
|
||||
/// Clear all tokens from the blacklist
|
||||
///
|
||||
/// WARNING: This removes all revoked tokens. Use with caution.
|
||||
pub async fn clear(&self) {
|
||||
let mut tokens = self.tokens.write().await;
|
||||
let count = tokens.len();
|
||||
tokens.clear();
|
||||
info!("Cleared {} tokens from blacklist", count);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TokenBlacklist {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_revoke_and_check() {
|
||||
let blacklist = TokenBlacklist::new();
|
||||
let token = "test.token.here";
|
||||
|
||||
assert!(!blacklist.is_revoked(token).await);
|
||||
|
||||
blacklist.revoke(token).await;
|
||||
|
||||
assert!(blacklist.is_revoked(token).await);
|
||||
assert_eq!(blacklist.len().await, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_revocations() {
|
||||
let blacklist = TokenBlacklist::new();
|
||||
|
||||
blacklist.revoke("token1").await;
|
||||
blacklist.revoke("token2").await;
|
||||
blacklist.revoke("token3").await;
|
||||
|
||||
assert_eq!(blacklist.len().await, 3);
|
||||
assert!(blacklist.is_revoked("token1").await);
|
||||
assert!(blacklist.is_revoked("token2").await);
|
||||
assert!(blacklist.is_revoked("token3").await);
|
||||
assert!(!blacklist.is_revoked("token4").await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_clear() {
|
||||
let blacklist = TokenBlacklist::new();
|
||||
|
||||
blacklist.revoke("token1").await;
|
||||
blacklist.revoke("token2").await;
|
||||
|
||||
assert_eq!(blacklist.len().await, 2);
|
||||
|
||||
blacklist.clear().await;
|
||||
|
||||
assert_eq!(blacklist.len().await, 0);
|
||||
assert!(blacklist.is_empty().await);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user