#![allow(unused_imports)]
use crate::snowflake::{epoch_time, EPOCH_MILLIS};
#[cfg(feature = "auth")]
use argon2_async::{set_config, Config};
use base64::{
alphabet::URL_SAFE,
decode_engine, encode_engine,
engine::fast_portable::{FastPortable, NO_PAD},
};
#[cfg(feature = "auth")]
use std::sync::OnceLock;
use std::time::{Duration, UNIX_EPOCH};
#[cfg(feature = "auth")]
pub use argon2_async::{hash as hash_password, verify as verify_password};
#[cfg(feature = "auth")]
pub use ring::rand::{SecureRandom, SystemRandom};
#[cfg(feature = "auth")]
pub static RNG: OnceLock<SystemRandom> = OnceLock::new();
#[cfg(feature = "auth")]
pub async fn configure_hasher(secret_key: &'static [u8]) {
let mut config = Config::new();
config
.set_secret_key(Some(secret_key))
.set_memory_cost(4096)
.set_iterations(64);
set_config(config).await;
}
#[inline]
#[cfg(feature = "auth")]
pub fn get_system_rng() -> &'static SystemRandom {
RNG.get_or_init(SystemRandom::new)
}
const ENGINE: FastPortable = FastPortable::from(&URL_SAFE, NO_PAD);
#[must_use]
#[cfg(feature = "auth")]
pub fn generate_token(user_id: u64) -> String {
let mut token = encode_engine(user_id.to_string().as_bytes(), &ENGINE);
token.push('.');
token.push_str(&encode_engine(epoch_time().to_string().as_bytes(), &ENGINE));
token.push('.');
token.push_str(&{
let dest = &mut [0_u8; 32];
get_system_rng().fill(dest).expect("could not fill bytes");
encode_engine(dest, &ENGINE)
});
token
}
#[derive(Copy, Clone)]
pub struct TokenReader<'a>(&'a str, &'a str);
impl<'a> TokenReader<'a> {
#[inline]
#[must_use]
pub fn new(token: &'a str) -> Option<Self> {
let mut split = token.splitn(3, '.');
Some(Self(split.next()?, split.next()?))
}
#[inline]
#[must_use]
pub fn user_id(&self) -> Option<u64> {
decode_engine(self.0, &ENGINE)
.ok()
.and_then(|b| String::from_utf8(b).ok())
.and_then(|s| s.parse().ok())
}
#[inline]
#[must_use]
pub fn timestamp_millis(&self) -> Option<u64> {
decode_engine(self.1, &ENGINE)
.ok()
.and_then(|b| String::from_utf8(b).ok())
.and_then(|s| s.parse().ok())
.map(|t: u64| t + EPOCH_MILLIS)
}
#[inline]
#[must_use]
pub fn timestamp_secs(&self) -> Option<u64> {
self.timestamp_millis().map(|t| t / 1000)
}
#[inline]
#[must_use]
pub fn timestamp(&self) -> Option<std::time::SystemTime> {
self.timestamp_millis()
.map(Duration::from_millis)
.map(|t| UNIX_EPOCH + t)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_token() {
let token = generate_token(39_113_435_127_808);
assert!(token.starts_with("MzkxMTM0MzUxMjc4MDg."));
}
#[test]
fn test_parse_token() {
let token = "MzkxMTM0MzUxMjc4MDg.MTg0NjAzMTg2.khHChSMQuhJ8hqj3QVp1HZjqjVlBRbXuxdsh7ri7FHU";
let reader = TokenReader::new(token).unwrap();
assert_eq!(reader.user_id(), Some(39_113_435_127_808));
assert_eq!(reader.timestamp_millis(), Some(184_603_186));
}
}