use base64::prelude::*;
use ring::rand::{SecureRandom, SystemRandom};
const TOKEN_BYTES: usize = 32;
#[must_use]
pub fn random_bytes<const N: usize>() -> [u8; N] {
let mut bytes = [0u8; N];
SystemRandom::new()
.fill(&mut bytes)
.expect("system RNG unavailable");
bytes
}
#[must_use]
pub fn random_token() -> String {
BASE64_URL_SAFE_NO_PAD.encode(random_bytes::<TOKEN_BYTES>())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn random_bytes_fills_the_whole_buffer() {
assert_ne!(random_bytes::<32>(), [0u8; 32]);
assert_ne!(random_bytes::<32>(), random_bytes::<32>());
assert_eq!(random_bytes::<16>().len(), 16);
}
#[test]
fn random_token_is_43_base64url_characters_over_32_bytes() {
let token = random_token();
assert_eq!(token.len(), 43, "43 characters encode 32 bytes unpadded");
assert!(
token
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_'),
"the base64url alphabet only: {token}"
);
assert_eq!(
BASE64_URL_SAFE_NO_PAD.decode(&token).unwrap().len(),
TOKEN_BYTES
);
}
#[test]
fn random_token_does_not_repeat() {
assert_ne!(random_token(), random_token());
}
}