1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3use rand::RngExt;
4use sha2::{Digest, Sha256};
5
6#[must_use]
10pub fn generate_code_verifier() -> String {
11 let random_bytes: [u8; 48] = rand::rng().random();
12 URL_SAFE_NO_PAD.encode(random_bytes)
13}
14
15#[must_use]
19pub fn generate_code_challenge(verifier: &str) -> String {
20 let hash = Sha256::digest(verifier.as_bytes());
21 URL_SAFE_NO_PAD.encode(hash)
22}
23
24#[must_use]
28pub fn generate_state() -> String {
29 let random_bytes: [u8; 16] = rand::rng().random();
30 URL_SAFE_NO_PAD.encode(random_bytes)
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn test_code_verifier_length() {
39 let verifier = generate_code_verifier();
40 assert_eq!(verifier.len(), 64);
41 }
42
43 #[test]
44 fn test_code_verifier_url_safe() {
45 let verifier = generate_code_verifier();
46 assert!(
47 verifier
48 .chars()
49 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
50 "verifier should be URL-safe: {}",
51 verifier
52 );
53 }
54
55 #[test]
56 fn test_code_verifier_uniqueness() {
57 let v1 = generate_code_verifier();
58 let v2 = generate_code_verifier();
59 assert_ne!(v1, v2, "verifiers should be unique");
60 }
61
62 #[test]
63 fn test_code_challenge_deterministic() {
64 let verifier = "test_verifier_string";
65 let c1 = generate_code_challenge(verifier);
66 let c2 = generate_code_challenge(verifier);
67 assert_eq!(c1, c2, "challenge should be deterministic");
68 }
69
70 #[test]
71 fn test_code_challenge_different_for_different_verifiers() {
72 let c1 = generate_code_challenge("verifier_1");
73 let c2 = generate_code_challenge("verifier_2");
74 assert_ne!(c1, c2);
75 }
76
77 #[test]
78 fn test_state_length() {
79 let state = generate_state();
80 assert_eq!(state.len(), 22);
81 }
82
83 #[test]
84 fn test_state_uniqueness() {
85 let s1 = generate_state();
86 let s2 = generate_state();
87 assert_ne!(s1, s2, "states should be unique");
88 }
89}