1use aws_lc_rs::digest::{SHA256, digest};
5use base64::Engine as _;
6use base64::engine::general_purpose::URL_SAFE_NO_PAD;
7
8use super::AuthError;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Pkce {
13 pub verifier: String,
15 pub challenge: String,
17}
18
19pub fn random_base64url() -> Result<String, AuthError> {
21 yapi_types::time::try_random_bytes::<32>()
22 .map(|bytes| URL_SAFE_NO_PAD.encode(bytes))
23 .map_err(AuthError::random)
24}
25
26pub fn generate() -> Result<Pkce, AuthError> {
28 let verifier = random_base64url()?;
29 let challenge = challenge(&verifier);
30 Ok(Pkce {
31 verifier,
32 challenge,
33 })
34}
35
36pub fn challenge(verifier: &str) -> String {
38 URL_SAFE_NO_PAD.encode(digest(&SHA256, verifier.as_bytes()))
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn matches_rfc_7636_example() {
47 assert_eq!(
48 challenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"),
49 "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
50 );
51 let pkce = generate().unwrap();
52 assert_eq!(pkce.verifier.len(), 43);
53 assert_eq!(pkce.challenge, challenge(&pkce.verifier));
54 }
55}