Skip to main content

rustauth_oauth/oauth2/
utils.rs

1use base64::engine::general_purpose::URL_SAFE_NO_PAD;
2use base64::Engine;
3use sha2::{Digest, Sha256};
4
5use super::error::OAuthError;
6
7pub use super::tokens::{get_oauth2_tokens, get_primary_client_id};
8
9/// Inclusive RFC 7636 §4.1 bounds for a PKCE `code_verifier`.
10const CODE_VERIFIER_MIN_LEN: usize = 43;
11const CODE_VERIFIER_MAX_LEN: usize = 128;
12
13/// Validates a PKCE `code_verifier` against RFC 7636 §4.1 syntax: 43–128
14/// characters drawn only from the unreserved set `A-Z`, `a-z`, `0-9`, `-`,
15/// `.`, `_`, `~`. Empty, too-short, too-long, whitespace, non-ASCII, and
16/// reserved-character values are rejected so RustAuth never advertises `S256`
17/// for a verifier a provider would reject.
18pub fn validate_code_verifier(code_verifier: &str) -> Result<(), OAuthError> {
19    let len = code_verifier.len();
20    if !(CODE_VERIFIER_MIN_LEN..=CODE_VERIFIER_MAX_LEN).contains(&len) {
21        return Err(OAuthError::InvalidCodeVerifier(format!(
22            "length {len} is outside the RFC 7636 range of \
23             {CODE_VERIFIER_MIN_LEN}-{CODE_VERIFIER_MAX_LEN} characters"
24        )));
25    }
26    if !code_verifier
27        .bytes()
28        .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~'))
29    {
30        return Err(OAuthError::InvalidCodeVerifier(
31            "only RFC 7636 unreserved characters (A-Z, a-z, 0-9, -, ., _, ~) are allowed"
32                .to_owned(),
33        ));
34    }
35    Ok(())
36}
37
38pub fn generate_code_challenge(code_verifier: &str) -> Result<String, OAuthError> {
39    validate_code_verifier(code_verifier)?;
40    let hash = Sha256::digest(code_verifier.as_bytes());
41    Ok(URL_SAFE_NO_PAD.encode(hash))
42}