use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use rand::Rng;
use sha2::{Digest, Sha256};
#[derive(Debug, Clone)]
pub struct Pkce {
code_verifier: String,
code_challenge: String,
}
impl Pkce {
pub fn generate() -> Self {
let mut rng = rand::thread_rng();
let random_bytes: [u8; 32] = rng.gen();
let code_verifier = URL_SAFE_NO_PAD.encode(random_bytes);
let mut hasher = Sha256::new();
hasher.update(code_verifier.as_bytes());
let hash = hasher.finalize();
let code_challenge = URL_SAFE_NO_PAD.encode(hash);
Self {
code_verifier,
code_challenge,
}
}
pub fn code_verifier(&self) -> &str {
&self.code_verifier
}
pub fn code_challenge(&self) -> &str {
&self.code_challenge
}
pub fn code_challenge_method() -> &'static str {
"S256"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pkce_generation() {
let pkce = Pkce::generate();
assert_eq!(pkce.code_verifier().len(), 43);
assert_eq!(pkce.code_challenge().len(), 43);
assert_ne!(pkce.code_verifier(), pkce.code_challenge());
}
#[test]
fn test_pkce_generates_different_values() {
let pkce1 = Pkce::generate();
let pkce2 = Pkce::generate();
assert_ne!(pkce1.code_verifier(), pkce2.code_verifier());
assert_ne!(pkce1.code_challenge(), pkce2.code_challenge());
}
#[test]
fn test_code_challenge_method() {
assert_eq!(Pkce::code_challenge_method(), "S256");
}
}