灵符lingfu

ling-web

↓ 986 downloads

Sessions, flash, templates, CSRF, static files — Flask-style

Keywords web, flask, sessions, csrf, templates
lingfu install ling-web
LicenseApache-2.0 OR MIT
AuthorsLing Lin, Sanny Lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (15)

src/csrf.rs
use rand::RngCore;
use subtle::ConstantTimeEq;
use tower_sessions::Session;

const CSRF_KEY: &str = "_csrf";

/// Returns this session's CSRF token, generating and storing one on first
/// use. Put it in a hidden form field (or a header for JSON APIs) and
/// check it with [`verify_csrf`] before acting on the request.
pub async fn csrf_token(session: &Session) -> anyhow::Result<String> {
    if let Some(token) = session.get::<String>(CSRF_KEY).await? {
        return Ok(token);
    }
    let mut bytes = [0u8; 32];
    rand::thread_rng().fill_bytes(&mut bytes);
    let token = hex_encode(&bytes);
    session.insert(CSRF_KEY, token.clone()).await?;
    Ok(token)
}

/// Verifies a submitted CSRF token against the one stored in the session,
/// in constant time. Returns `false` (not an error) for a missing session
/// token or a mismatch — both just mean "reject the request."
pub async fn verify_csrf(session: &Session, submitted: &str) -> anyhow::Result<bool> {
    let Some(expected) = session.get::<String>(CSRF_KEY).await? else {
        return Ok(false);
    };
    if expected.len() != submitted.len() {
        return Ok(false);
    }
    Ok(bool::from(expected.as_bytes().ct_eq(submitted.as_bytes())))
}

fn hex_encode(bytes: &[u8]) -> String {
    use std::fmt::Write;
    let mut s = String::with_capacity(bytes.len() * 2);
    for b in bytes {
        let _ = write!(s, "{b:02x}");
    }
    s
}

← Back to index