refactor(tests): reformat code and update dependency management
- Reorganized import statements in adapter tests for better readability - Replaced or_insert_with(Vec::new) with or_default() in test closures - Updated Cargo.lock with new dependency versions and checksums - Added TLS features to tonic dependency configuration - Included sqlx, chrono, and uuid dependencies with specific features - Added jsonwebtoken and arc-swap as project dependencies - Reformatted assertion statements to comply with line length limits - Adjusted base64 import order in engine codec module - Updated protobuf include statement formatting
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
//! Dual-mode JWT authenticator — the public-facing entry point.
|
||||
//!
|
||||
//! Composes `SigningKeyStore` (local cache) + `jwt_decoder` (HS256 logic)
|
||||
//! + `TokenServiceClient` (RPC fallback) into a single `Authenticator`.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tonic::transport::Channel;
|
||||
|
||||
use crate::pb::core::VerifyTokenRequest;
|
||||
use crate::pb::core::token_service_client::TokenServiceClient;
|
||||
use crate::{ImksError, ImksResult};
|
||||
|
||||
use super::claims::TokenClaims;
|
||||
use super::jwt_decoder;
|
||||
use super::key_store::SigningKeyStore;
|
||||
|
||||
/// Dual-mode JWT authenticator.
|
||||
///
|
||||
/// - **Local mode** (`verify_local`): HS256 verification against cached
|
||||
/// signing keys. Zero network latency. Suitable for high-frequency
|
||||
/// operations like message send/receive.
|
||||
///
|
||||
/// - **RPC mode** (`verify_rpc`): forwards the token to appks
|
||||
/// `VerifyToken()`. Real-time revocation awareness. Use for
|
||||
/// sensitive operations like kick/ban/permission changes.
|
||||
#[derive(Clone)]
|
||||
pub struct Authenticator {
|
||||
key_store: Arc<SigningKeyStore>,
|
||||
token_client: TokenServiceClient<Channel>,
|
||||
}
|
||||
|
||||
impl Authenticator {
|
||||
/// Create a new authenticator. Initializes the signing key cache from appks.
|
||||
pub async fn new(token_client: TokenServiceClient<Channel>) -> ImksResult<Self> {
|
||||
let key_store = SigningKeyStore::init(token_client.clone()).await?;
|
||||
Ok(Self {
|
||||
key_store: Arc::new(key_store),
|
||||
token_client,
|
||||
})
|
||||
}
|
||||
|
||||
/// Fast-path verification using locally cached signing keys.
|
||||
///
|
||||
/// Extracts `kid` from the JWT header, looks up the key, and verifies
|
||||
/// the HS256 signature. Cannot detect token revocation within the
|
||||
/// current key rotation window (~3 hours).
|
||||
pub fn verify_local(&self, token: &str) -> ImksResult<TokenClaims> {
|
||||
let kid = jwt_decoder::extract_kid(token)?;
|
||||
|
||||
let key = self
|
||||
.key_store
|
||||
.get_key(&kid)
|
||||
.ok_or_else(|| ImksError::Auth(format!("Unknown signing key kid: {kid}")))?;
|
||||
|
||||
jwt_decoder::verify_and_decode(token, &key)
|
||||
}
|
||||
|
||||
/// Authoritative verification via appks `VerifyToken` RPC.
|
||||
///
|
||||
/// Detects token revocation in real-time. Adds one RPC round-trip.
|
||||
pub async fn verify_rpc(&self, token: &str) -> ImksResult<TokenClaims> {
|
||||
let mut client = self.token_client.clone();
|
||||
let resp = client
|
||||
.verify_token(VerifyTokenRequest {
|
||||
token: token.to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let inner = resp.into_inner();
|
||||
if !inner.valid {
|
||||
return Err(ImksError::Auth(inner.reason));
|
||||
}
|
||||
|
||||
let proto_claims = inner.claims.ok_or_else(|| {
|
||||
ImksError::Auth("VerifyToken returned valid=true but no claims".into())
|
||||
})?;
|
||||
|
||||
Ok(TokenClaims::from_proto(proto_claims))
|
||||
}
|
||||
|
||||
/// Extract the Bearer token value from an `Authorization` header.
|
||||
///
|
||||
/// Expects format: `"Bearer <token>"`. Returns the token part.
|
||||
pub fn extract_bearer(auth_header: &str) -> ImksResult<&str> {
|
||||
auth_header
|
||||
.strip_prefix("Bearer ")
|
||||
.ok_or_else(|| ImksError::Auth("Missing or malformed Authorization header".into()))
|
||||
}
|
||||
|
||||
/// Shut down the background key refresh task.
|
||||
pub async fn shutdown(self) {
|
||||
// Unwrap the Arc — if there are other clones, the store lives on.
|
||||
if let Ok(store) = Arc::try_unwrap(self.key_store) {
|
||||
store.shutdown().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user