AkurAI Build
Menu

Public repository / 228b5716ba0b7bcc87cce261ea6df891133a35bb

AkurAI-CRM / crates/crm-api/src/auth/pkce.rs

Latest change 201b04d27b0773782f4a1a0558cee777ec8e3462 - Recover deployed web login and MCP group gate by Ólafur Búi Ólafsson

//! Random tokens and RFC 7636 PKCE (S256) for the login flow.

use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
use ring::digest::{digest, SHA256};
use std::io::Read;

/// 32 random bytes from the OS CSPRNG, base64url-encoded (43 chars).
pub fn random_token() -> Result<String, String> {
    let mut bytes = [0_u8; 32];
    std::fs::File::open("/dev/urandom")
        .map_err(|error| format!("csprng unavailable: {error}"))?
        .read_exact(&mut bytes)
        .map_err(|error| format!("csprng read failed: {error}"))?;
    Ok(URL_SAFE_NO_PAD.encode(bytes))
}

/// `BASE64URL(SHA256(verifier))`.
pub fn s256_challenge(verifier: &str) -> String {
    URL_SAFE_NO_PAD.encode(digest(&SHA256, verifier.as_bytes()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn challenge_matches_rfc7636_appendix_b() {
        assert_eq!(
            s256_challenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"),
            "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
        );
    }

    #[test]
    fn random_tokens_are_long_and_distinct() {
        let first = random_token().unwrap();
        assert_eq!(first.len(), 43);
        assert_ne!(first, random_token().unwrap());
    }
}