use sha2::{Digest, Sha256};
pub fn verify_pkce(code_verifier: &str, code_challenge: &str) -> bool {
let mut hasher = Sha256::new();
hasher.update(code_verifier.as_bytes());
let hash = hasher.finalize();
let computed_challenge = base64_url::encode(&hash);
computed_challenge == code_challenge
}
pub fn validate_code_verifier(code_verifier: &str) -> bool {
let len = code_verifier.len();
if !(43..=128).contains(&len) {
return false;
}
code_verifier
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '.' | '_' | '~'))
}
pub fn validate_code_challenge(code_challenge: &str) -> bool {
let len = code_challenge.len();
if !(43..=128).contains(&len) {
return false;
}
code_challenge
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_'))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pkce_verification_success() {
let code_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
let code_challenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
assert!(verify_pkce(code_verifier, code_challenge));
}
#[test]
fn test_pkce_verification_failure() {
let code_verifier = "wrong_verifier_123456789012345678901234567890";
let code_challenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
assert!(!verify_pkce(code_verifier, code_challenge));
}
#[test]
fn test_code_verifier_validation() {
assert!(validate_code_verifier(
"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
));
assert!(!validate_code_verifier(
"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOE"
));
assert!(!validate_code_verifier(&"a".repeat(129)));
assert!(!validate_code_verifier(
"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk="
));
}
#[test]
fn test_code_challenge_validation() {
assert!(validate_code_challenge(
"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
));
assert!(!validate_code_challenge("E9Melhoa2OwvFrEMTJguCHaoeK1t8URW"));
assert!(!validate_code_challenge(
"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw.cM"
));
}
}