use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use rand::Rng;
use sha2::{Digest, Sha256};
#[derive(Debug, Clone)]
pub struct PkceChallenge {
pub verifier: String,
pub challenge: String,
pub method: String,
}
impl PkceChallenge {
#[must_use]
pub fn generate() -> Self {
let verifier = Self::generate_verifier();
let challenge = Self::compute_challenge(&verifier);
Self {
verifier,
challenge,
method: "S256".to_string(),
}
}
fn generate_verifier() -> String {
let random_bytes: Vec<u8> = (0..32).map(|_| rand::thread_rng().r#gen::<u8>()).collect();
URL_SAFE_NO_PAD.encode(random_bytes)
}
fn compute_challenge(verifier: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(verifier.as_bytes());
let hash = hasher.finalize();
URL_SAFE_NO_PAD.encode(hash)
}
#[must_use]
pub fn verifier(&self) -> &str {
&self.verifier
}
#[must_use]
pub fn challenge(&self) -> &str {
&self.challenge
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::redundant_clone, clippy::manual_string_new, clippy::needless_collect, clippy::unreadable_literal, clippy::used_underscore_items, clippy::similar_names)]
mod tests {
use super::*;
#[test]
fn test_pkce_generation() {
let pkce = PkceChallenge::generate();
assert!(!pkce.verifier.is_empty());
assert!(!pkce.challenge.is_empty());
assert_eq!(pkce.method, "S256");
assert_ne!(pkce.verifier, pkce.challenge);
}
#[test]
fn test_verifier_length() {
let pkce = PkceChallenge::generate();
assert!(pkce.verifier.len() >= 43);
assert!(pkce.verifier.len() <= 128);
}
#[test]
fn test_challenge_computation() {
let verifier = "test_verifier_string";
let challenge = PkceChallenge::compute_challenge(verifier);
assert!(!challenge.is_empty());
let challenge2 = PkceChallenge::compute_challenge(verifier);
assert_eq!(challenge, challenge2);
}
#[test]
fn test_multiple_generations_unique() {
let pkce1 = PkceChallenge::generate();
let pkce2 = PkceChallenge::generate();
assert_ne!(pkce1.verifier, pkce2.verifier);
assert_ne!(pkce1.challenge, pkce2.challenge);
}
}