Skip to main content

yt/auth/
pkce.rs

1use base64::Engine as _;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3use secrecy::SecretString;
4use sha2::{Digest as _, Sha256};
5
6pub struct Pkce {
7    pub verifier: SecretString,
8    pub challenge: String,
9}
10
11impl Pkce {
12    pub fn generate() -> Self {
13        let verifier = random_token(32);
14        Self {
15            challenge: challenge_for(&verifier),
16            verifier: SecretString::from(verifier),
17        }
18    }
19}
20
21pub fn challenge_for(verifier: &str) -> String {
22    URL_SAFE_NO_PAD.encode(Sha256::digest(verifier.as_bytes()))
23}
24
25/// `bytes` random bytes, base64url-encoded without padding.
26pub fn random_token(bytes: usize) -> String {
27    let mut buffer = vec![0u8; bytes];
28    rand::fill(buffer.as_mut_slice());
29    URL_SAFE_NO_PAD.encode(buffer)
30}
31
32#[cfg(test)]
33mod tests {
34    use secrecy::ExposeSecret as _;
35
36    use super::*;
37
38    #[test]
39    fn rfc_7636_example() {
40        assert_eq!(
41            challenge_for("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"),
42            "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
43        );
44    }
45
46    #[test]
47    fn generated_verifiers_are_long_random_and_match_their_challenge() {
48        let a = Pkce::generate();
49        let b = Pkce::generate();
50        assert_eq!(a.verifier.expose_secret().len(), 43);
51        assert_ne!(a.verifier.expose_secret(), b.verifier.expose_secret());
52        assert_eq!(a.challenge, challenge_for(a.verifier.expose_secret()));
53    }
54}