Skip to main content

ito_core/
token.rs

1//! Cryptographic token generation for backend server authentication.
2//!
3//! Produces URL-safe base64-encoded random strings suitable for use as admin
4//! tokens and HMAC token seeds.
5
6use base64::Engine;
7use base64::engine::general_purpose::URL_SAFE_NO_PAD;
8use rand::Rng;
9
10/// Minimum entropy in bytes for generated tokens.
11const TOKEN_ENTROPY_BYTES: usize = 32;
12
13/// Generate a cryptographically random URL-safe base64 token.
14///
15/// The token contains at least 32 bytes of entropy from the OS CSPRNG,
16/// encoded as URL-safe base64 (no padding). This produces a 43-character
17/// string.
18pub fn generate_token() -> String {
19    let mut bytes = [0u8; TOKEN_ENTROPY_BYTES];
20    rand::rng().fill(&mut bytes);
21    URL_SAFE_NO_PAD.encode(bytes)
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27
28    #[test]
29    fn generated_token_has_expected_length() {
30        let token = generate_token();
31        // 32 bytes -> ceil(32 * 4 / 3) = 43 base64 chars (no padding)
32        assert_eq!(token.len(), 43);
33    }
34
35    #[test]
36    fn generated_token_is_url_safe() {
37        let token = generate_token();
38        for ch in token.chars() {
39            assert!(
40                ch.is_ascii_alphanumeric() || ch == '-' || ch == '_',
41                "unexpected character in token: {ch}"
42            );
43        }
44    }
45
46    #[test]
47    fn two_tokens_are_distinct() {
48        let a = generate_token();
49        let b = generate_token();
50        assert_ne!(a, b, "two generated tokens should differ");
51    }
52
53    #[test]
54    fn url_safe_base64_roundtrip_known_value() {
55        // All-zero bytes should produce all 'A's
56        let zeros = [0u8; 32];
57        let encoded = URL_SAFE_NO_PAD.encode(zeros);
58        assert_eq!(encoded.len(), 43);
59        assert!(encoded.chars().all(|c| c == 'A'));
60    }
61
62    #[test]
63    fn url_safe_base64_encode_known_vector() {
64        // "Hello" -> base64 "SGVsbG8" (URL-safe, no padding)
65        let encoded = URL_SAFE_NO_PAD.encode(b"Hello");
66        assert_eq!(encoded, "SGVsbG8");
67    }
68}