coil-core 0.1.1

Core runtime contracts and composition primitives for the Coil framework.
Documentation
use super::BrowserSecurityError;
use super::support::{sign_payload, verify_payload};
use crate::*;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CsrfProtection {
    pub enabled: bool,
    pub field_name: String,
    pub header_name: String,
}

impl CsrfProtection {
    pub fn from_config(config: &HttpCsrfConfig) -> Self {
        Self {
            enabled: config.enabled,
            field_name: config.field_name.clone(),
            header_name: config.header_name.clone(),
        }
    }

    pub fn issue_token(
        &self,
        secret: &[u8],
        session_id: &str,
        action: &str,
    ) -> Result<String, BrowserSecurityError> {
        if !self.enabled {
            return Err(BrowserSecurityError::CsrfDisabled);
        }

        let mut nonce = [0u8; 16];
        OsRng.fill_bytes(&mut nonce);
        let nonce = URL_SAFE_NO_PAD.encode(nonce);
        let payload = format!("{session_id}:{action}:{nonce}");
        let signature = sign_payload(secret, payload.as_bytes())?;
        Ok(format!("v1.{nonce}.{signature}"))
    }

    pub fn verify_token(
        &self,
        secret: &[u8],
        session_id: &str,
        action: &str,
        token: &str,
    ) -> Result<bool, BrowserSecurityError> {
        if !self.enabled {
            return Ok(true);
        }

        let mut parts = token.split('.');
        let version = parts.next();
        let nonce = parts.next();
        let signature = parts.next();

        if version != Some("v1") || parts.next().is_some() {
            return Err(BrowserSecurityError::InvalidCsrfTokenFormat);
        }

        let nonce = nonce.ok_or(BrowserSecurityError::InvalidCsrfTokenFormat)?;
        let signature = signature.ok_or(BrowserSecurityError::InvalidCsrfTokenFormat)?;
        let payload = format!("{session_id}:{action}:{nonce}");

        Ok(verify_payload(secret, payload.as_bytes(), signature).is_ok())
    }
}