use std::{
sync::Arc,
time::{Duration, Instant},
};
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use dashmap::DashMap;
use rand::{TryRng, rngs::SysRng};
use tonic::Status;
const MAX_LIVE_TOKENS: usize = 100_000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Principal {
pub username: String,
}
#[derive(Debug, Clone)]
struct TokenEntry {
principal: Principal,
expires_at: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct TokenStore {
inner: Arc<DashMap<String, TokenEntry>>,
max_live_tokens: usize,
}
impl Default for TokenStore {
fn default() -> Self {
Self { inner: Arc::default(), max_live_tokens: MAX_LIVE_TOKENS }
}
}
impl TokenStore {
pub fn new() -> Self {
Self::default()
}
#[cfg(test)]
pub(crate) fn with_capacity_for_tests(max_live_tokens: usize) -> Self {
Self { max_live_tokens, ..Self::default() }
}
pub fn issue(&self, principal: Principal, ttl: Option<Duration>) -> Result<String, Status> {
if self.inner.len() >= self.max_live_tokens {
self.sweep_expired();
if self.inner.len() >= self.max_live_tokens {
return Err(Status::resource_exhausted("bearer token store is at capacity, retry later"));
}
}
let mut bytes = [0u8; 32];
SysRng.try_fill_bytes(&mut bytes).expect("system random source must be available to issue bearer tokens");
let token = base64_url_no_pad(&bytes);
let expires_at = ttl
.map(|ttl| {
expiry_after(ttl).ok_or_else(|| Status::internal("bearer token TTL exceeds the representable expiry"))
})
.transpose()?;
self.inner.insert(token.clone(), TokenEntry { principal, expires_at });
Ok(token)
}
fn sweep_expired(&self) {
let now = Instant::now();
self.inner.retain(|_, entry| entry.expires_at.is_none_or(|expires_at| now < expires_at));
}
pub fn validate(&self, token: &str) -> Option<Principal> {
let entry = self.inner.get(token)?;
if let Some(expires_at) = entry.expires_at
&& Instant::now() >= expires_at
{
drop(entry);
self.inner.remove(token);
return None;
}
Some(entry.principal.clone())
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.inner.len()
}
}
pub(super) fn expiry_after(ttl: Duration) -> Option<Instant> {
Instant::now().checked_add(ttl)
}
fn base64_url_no_pad(bytes: &[u8]) -> String {
URL_SAFE_NO_PAD.encode(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
fn principal(name: &str) -> Principal {
Principal { username: name.to_string() }
}
#[test]
fn issue_and_validate_round_trip() {
let store = TokenStore::new();
let token = store.issue(principal("alice"), None).expect("issue under capacity");
assert_eq!(token.len(), 43, "32 raw bytes -> 43 base64url chars");
let resolved = store.validate(&token).expect("token must validate");
assert_eq!(resolved.username, "alice");
}
#[test]
fn unknown_token_rejects() {
let store = TokenStore::new();
assert!(store.validate("not-a-token").is_none());
}
#[test]
fn expired_token_rejects_and_is_removed() {
let store = TokenStore::new();
let token = store.issue(principal("alice"), Some(Duration::from_millis(0))).expect("issue under capacity");
std::thread::sleep(Duration::from_millis(1));
assert!(store.validate(&token).is_none(), "expired token must reject");
assert_eq!(store.len(), 0, "expired entry must be removed from the map");
}
#[test]
fn issue_rejects_ttl_beyond_the_instant_range() {
let store = TokenStore::new();
let status = store.issue(principal("alice"), Some(Duration::MAX)).unwrap_err();
assert_eq!(status.code(), tonic::Code::Internal);
assert_eq!(store.len(), 0, "a refused issuance must not insert a token");
}
#[test]
fn tokens_are_unique() {
let store = TokenStore::new();
let a = store.issue(principal("alice"), None).expect("issue under capacity");
let b = store.issue(principal("alice"), None).expect("issue under capacity");
assert_ne!(a, b);
}
#[test]
fn issue_refuses_beyond_capacity() {
const CAPACITY: usize = 8;
let store = TokenStore::with_capacity_for_tests(CAPACITY);
for _ in 0..CAPACITY {
store.issue(principal("alice"), None).expect("issue under capacity");
}
let status = store.issue(principal("alice"), None).unwrap_err();
assert_eq!(status.code(), tonic::Code::ResourceExhausted);
assert_eq!(store.len(), CAPACITY, "refused issuance must not grow the map");
}
#[test]
fn issue_at_capacity_sweeps_expired_entries_to_reclaim_it() {
const CAPACITY: usize = 8;
let store = TokenStore::with_capacity_for_tests(CAPACITY);
for _ in 0..CAPACITY {
store.issue(principal("alice"), Some(Duration::from_millis(0))).expect("issue under capacity");
}
std::thread::sleep(Duration::from_millis(1));
let token = store.issue(principal("bob"), None).expect("sweep reclaims capacity for a fresh issuance");
assert_eq!(store.len(), 1, "the sweep must clear all expired entries, not just enough for one slot");
let resolved = store.validate(&token).expect("token must validate");
assert_eq!(resolved.username, "bob");
}
#[test]
fn live_tokens_at_capacity_are_not_swept() {
const CAPACITY: usize = 8;
let store = TokenStore::with_capacity_for_tests(CAPACITY);
for _ in 0..CAPACITY {
store.issue(principal("alice"), Some(Duration::from_secs(3600))).expect("issue under capacity");
}
let status = store.issue(principal("bob"), None).unwrap_err();
assert_eq!(status.code(), tonic::Code::ResourceExhausted);
assert_eq!(store.len(), CAPACITY, "live tokens must survive the sweep");
}
}