Files
imks/auth/verifier.rs
T
zhenyi 821537186e 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
2026-06-11 12:11:05 +08:00

99 lines
3.4 KiB
Rust

//! 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;
}
}
}