//! [`CsrfToken`], [`Key`] and functions to encode & decode expiring claims.
use time::OffsetDateTime;
use thiserror::Error;
pub use crate::signed::Key;
/// A cookie-based CSRF token to be used with [`crate::request::SputnikBody::into_form_csrf`].
pub struct CsrfToken {
pub(crate) token: String,
pub(crate) from_client: bool,
}
#[derive(Error, Debug)]
pub enum CsrfError {
#[error("expected csrf cookie")]
NoCookie,
#[error("csrf parameter doesn't match csrf cookie")]
Mismatch,
}
impl CsrfToken {
/// Wraps the token in a hidden HTML input.
pub fn html_input(&self) -> String {
format!("", self.token)
}
pub(crate) fn matches(&self, str: String) -> Result<(), CsrfError> {
if !self.from_client {
return Err(CsrfError::NoCookie)
}
if self.token != str {
return Err(CsrfError::Mismatch)
}
Ok(())
}
}
/// Join a string and an expiry date together into a string.
pub fn encode_expiring_claim(claim: &str, expiry_date: OffsetDateTime) -> String {
format!("{}:{}", claim, expiry_date.unix_timestamp())
}
/// Extract the string, failing if the expiry date is in the past.
pub fn decode_expiring_claim(value: String) -> Result {
let mut parts = value.splitn(2, ':');
let claim = parts.next().ok_or("expected colon")?;
let expiry_date = parts.next().ok_or("expected colon")?;
let expiry_date: i64 = expiry_date.parse().map_err(|_| "failed to parse timestamp")?;
if expiry_date > OffsetDateTime::now_utc().unix_timestamp() {
Ok(claim.to_string())
} else {
Err("token is expired")
}
}