use std::fmt;
use subtle::ConstantTimeEq;
#[derive(Clone)]
pub struct CsrfToken(String);
impl CsrfToken {
pub(crate) fn new(token: String) -> Self {
Self(token)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl From<String> for CsrfToken {
fn from(token: String) -> Self {
Self(token)
}
}
fn ct_eq(a: &str, b: &str) -> bool {
a.as_bytes().ct_eq(b.as_bytes()).into()
}
impl PartialEq for CsrfToken {
fn eq(&self, other: &Self) -> bool {
ct_eq(&self.0, &other.0)
}
}
impl Eq for CsrfToken {}
impl PartialEq<str> for CsrfToken {
fn eq(&self, other: &str) -> bool {
ct_eq(&self.0, other)
}
}
impl PartialEq<&str> for CsrfToken {
fn eq(&self, other: &&str) -> bool {
ct_eq(&self.0, other)
}
}
impl PartialEq<String> for CsrfToken {
fn eq(&self, other: &String) -> bool {
ct_eq(&self.0, other)
}
}
impl PartialEq<CsrfToken> for str {
fn eq(&self, other: &CsrfToken) -> bool {
ct_eq(self, &other.0)
}
}
impl PartialEq<CsrfToken> for String {
fn eq(&self, other: &CsrfToken) -> bool {
ct_eq(self, &other.0)
}
}
impl fmt::Debug for CsrfToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CsrfToken([redacted])")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constant_time_eq_matches_value() {
let token = CsrfToken::new("abc123".to_string());
assert_eq!(token, *"abc123");
assert_eq!(token, "abc123");
assert_eq!(token, "abc123".to_string());
assert_eq!(token, CsrfToken::new("abc123".to_string()));
assert_ne!(token, "abc124");
assert_ne!(token, "abc12"); assert_ne!(token, CsrfToken::new("nope".to_string()));
}
#[test]
fn debug_redacts_the_token() {
let debug = format!("{:?}", CsrfToken::new("super-secret".to_string()));
assert!(!debug.contains("super-secret"), "{debug}");
}
}