Skip to main content

synaps_cli/core/auth/
pkce.rs

1use rand::Rng;
2use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
3use sha2::{Sha256, Digest};
4
5use super::{AUTHORIZE_URL, CLIENT_ID, SCOPES};
6
7/// Generate a cryptographically random code verifier (43-128 chars, base64url).
8pub fn generate_code_verifier() -> String {
9    let mut bytes = [0u8; 32];
10    rand::rng().fill(&mut bytes);
11    URL_SAFE_NO_PAD.encode(bytes)
12}
13
14/// Compute S256 code challenge from verifier.
15pub fn generate_code_challenge(verifier: &str) -> String {
16    let mut hasher = Sha256::new();
17    hasher.update(verifier.as_bytes());
18    let hash = hasher.finalize();
19    URL_SAFE_NO_PAD.encode(hash)
20}
21
22/// Generate a random state parameter.
23pub fn generate_state() -> String {
24    let mut bytes = [0u8; 32];
25    rand::rng().fill(&mut bytes);
26    URL_SAFE_NO_PAD.encode(bytes)
27}
28
29/// Build the full authorize URL for the browser.
30pub fn build_auth_url(challenge: &str, state: &str, port: u16) -> String {
31    let redirect_uri = format!("http://localhost:{}/callback", port);
32    let params = [
33        ("code", "true"),
34        ("client_id", CLIENT_ID),
35        ("response_type", "code"),
36        ("redirect_uri", &redirect_uri),
37        ("scope", SCOPES),
38        ("code_challenge", challenge),
39        ("code_challenge_method", "S256"),
40        ("state", state),
41    ];
42
43    let query: String = params
44        .iter()
45        .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
46        .collect::<Vec<_>>()
47        .join("&");
48
49    format!("{}?{}", AUTHORIZE_URL, query)
50}