sa_token_core/oauth2/
pkce.rs1use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::error::{SaTokenError, SaTokenResult};
10use crate::http_basic::ct_eq;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum CodeChallengeMethod {
16 S256,
19 Plain,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PkceChallenge {
28 pub code_challenge: String,
31 pub code_challenge_method: CodeChallengeMethod,
34}
35
36impl PkceChallenge {
37 pub fn from_verifier_s256(code_verifier: &str) -> SaTokenResult<Self> {
40 Self::validate_verifier_len(code_verifier)?;
41 let digest = Sha256::digest(code_verifier.as_bytes());
42 Ok(Self {
43 code_challenge: URL_SAFE_NO_PAD.encode(digest),
44 code_challenge_method: CodeChallengeMethod::S256,
45 })
46 }
47
48 fn validate_verifier_len(code_verifier: &str) -> SaTokenResult<()> {
49 if !(43..=128).contains(&code_verifier.len()) {
50 return Err(SaTokenError::OAuth2PkceMismatch);
51 }
52 Ok(())
53 }
54
55 pub fn verify(&self, code_verifier: &str) -> SaTokenResult<()> {
58 Self::validate_verifier_len(code_verifier)?;
59 let computed = match self.code_challenge_method {
60 CodeChallengeMethod::S256 => {
61 let digest = Sha256::digest(code_verifier.as_bytes());
62 URL_SAFE_NO_PAD.encode(digest)
63 }
64 CodeChallengeMethod::Plain => code_verifier.to_string(),
65 };
66 if ct_eq(computed.as_bytes(), self.code_challenge.as_bytes()) {
67 Ok(())
68 } else {
69 Err(SaTokenError::OAuth2PkceMismatch)
70 }
71 }
72}