Skip to main content

coil_core/browser/
csrf.rs

1use super::BrowserSecurityError;
2use super::support::{sign_payload, verify_payload};
3use crate::*;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct CsrfProtection {
7    pub enabled: bool,
8    pub field_name: String,
9    pub header_name: String,
10}
11
12impl CsrfProtection {
13    pub fn from_config(config: &HttpCsrfConfig) -> Self {
14        Self {
15            enabled: config.enabled,
16            field_name: config.field_name.clone(),
17            header_name: config.header_name.clone(),
18        }
19    }
20
21    pub fn issue_token(
22        &self,
23        secret: &[u8],
24        session_id: &str,
25        action: &str,
26    ) -> Result<String, BrowserSecurityError> {
27        if !self.enabled {
28            return Err(BrowserSecurityError::CsrfDisabled);
29        }
30
31        let mut nonce = [0u8; 16];
32        OsRng.fill_bytes(&mut nonce);
33        let nonce = URL_SAFE_NO_PAD.encode(nonce);
34        let payload = format!("{session_id}:{action}:{nonce}");
35        let signature = sign_payload(secret, payload.as_bytes())?;
36        Ok(format!("v1.{nonce}.{signature}"))
37    }
38
39    pub fn verify_token(
40        &self,
41        secret: &[u8],
42        session_id: &str,
43        action: &str,
44        token: &str,
45    ) -> Result<bool, BrowserSecurityError> {
46        if !self.enabled {
47            return Ok(true);
48        }
49
50        let mut parts = token.split('.');
51        let version = parts.next();
52        let nonce = parts.next();
53        let signature = parts.next();
54
55        if version != Some("v1") || parts.next().is_some() {
56            return Err(BrowserSecurityError::InvalidCsrfTokenFormat);
57        }
58
59        let nonce = nonce.ok_or(BrowserSecurityError::InvalidCsrfTokenFormat)?;
60        let signature = signature.ok_or(BrowserSecurityError::InvalidCsrfTokenFormat)?;
61        let payload = format!("{session_id}:{action}:{nonce}");
62
63        Ok(verify_payload(secret, payload.as_bytes(), signature).is_ok())
64    }
65}