Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "databend-base"
version = "0.2.7"
version = "0.2.8"
edition = "2024"

[features]
Expand All @@ -9,6 +9,7 @@ serde = ["dep:serde"]

[dependencies]
async-trait = "0.1"
jwt-simple = "0.12"
ctrlc = "3.4"
futures = "0.3"
log = "0.4"
Expand Down
100 changes: 100 additions & 0 deletions src/grpc_token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! gRPC authentication token management using [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token).
//!
//! Provides [`GrpcToken`] for creating and verifying JWT-based authentication tokens
//! for gRPC services. Each [`GrpcToken`] instance generates its own HMAC-SHA256 key,
//! so tokens can only be verified by the same instance that created them.
//!
//! # Example
//!
//! ```
//! use databend_base::grpc_token::{GrpcClaim, GrpcToken};
//!
//! let grpc_token = GrpcToken::create();
//!
//! let claim = GrpcClaim { username: "alice".to_string() };
//! let token = grpc_token.try_create_token(claim).unwrap();
//!
//! let verified = grpc_token.try_verify_token(&token).unwrap();
//! assert_eq!(verified.username, "alice");
//! ```

use jwt_simple::prelude::*;

/// Claims embedded in the JWT token payload.
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct GrpcClaim {
/// The authenticated user's identifier.
pub username: String,
}

/// JWT token manager for gRPC authentication.
///
/// Cloning shares the same key, allowing multiple references to create and
/// verify tokens interchangeably.
#[derive(Clone)]
pub struct GrpcToken {
key: HS256Key,
}

impl GrpcToken {
/// Creates a new token manager with a randomly generated HMAC-SHA256 key.
pub fn create() -> Self {
Self {
key: HS256Key::generate(),
}
}

/// Creates a signed JWT token valid for 10 years.
pub fn try_create_token(&self, claim: GrpcClaim) -> Result<String, jwt_simple::Error> {
self.key.authenticate(Claims::with_custom_claims(claim, Duration::from_days(3650)))
}

/// Verifies a token signature and expiration, returning the embedded claim.
pub fn try_verify_token(&self, token: &str) -> Result<GrpcClaim, jwt_simple::Error> {
Ok(self.key.verify_token::<GrpcClaim>(token, None)?.custom)
}
}

#[cfg(test)]
mod tests {
use super::*;

fn claim(name: &str) -> GrpcClaim {
GrpcClaim {
username: name.to_string(),
}
}

#[test]
fn test_create_and_verify() {
let t = GrpcToken::create();
let token = t.try_create_token(claim("alice")).unwrap();

assert_eq!(t.try_verify_token(&token).unwrap().username, "alice");
}

#[test]
fn test_cloned_manager_shares_key() {
let t1 = GrpcToken::create();
let t2 = t1.clone();

let token = t1.try_create_token(claim("bob")).unwrap();
assert_eq!(t2.try_verify_token(&token).unwrap().username, "bob");
}

#[test]
fn test_different_managers_reject() {
let t1 = GrpcToken::create();
let t2 = GrpcToken::create();

let token = t1.try_create_token(claim("alice")).unwrap();
assert!(t2.try_verify_token(&token).is_err());
}

#[test]
fn test_invalid_token() {
let t = GrpcToken::create();
assert!(t.try_verify_token("invalid").is_err());
assert!(t.try_verify_token("").is_err());
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! - [`counter`]: Track the count of active instances with RAII guards.
//! - [`drop_guard`]: RAII guard that executes a closure when dropped.
//! - [`futures`]: Utilities for working with async futures, including elapsed time tracking.
//! - [`grpc_token`]: JWT-based authentication tokens for gRPC services.
//! - [`histogram`]: A histogram with logarithmic bucketing for tracking u64 value distributions.
//! Provides O(1) recording and efficient percentile calculation with bounded memory (~2KB).
//! - [`non_empty`]: Non-empty string types that guarantee the contained string is never empty.
Expand All @@ -16,6 +17,7 @@
pub mod counter;
pub mod drop_guard;
pub mod futures;
pub mod grpc_token;
pub mod histogram;
pub mod non_empty;
pub mod shutdown;
Expand Down