use cookie::time::Duration;
use cookie::{Cookie, CookieBuilder, SameSite};
use std::borrow::Cow;
pub const DEFAULT_COOKIE_NAME: &str = "axum-gate";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CookieTemplate {
name: Cow<'static, str>,
value: Cow<'static, str>,
path: Cow<'static, str>,
domain: Option<Cow<'static, str>>,
secure: bool,
http_only: bool,
same_site: SameSite,
max_age: Option<Duration>,
}
impl Default for CookieTemplate {
fn default() -> Self {
let (secure, same_site) = if cfg!(debug_assertions) {
(false, SameSite::Lax)
} else {
(true, SameSite::Strict)
};
Self {
name: Cow::Borrowed(DEFAULT_COOKIE_NAME),
value: Cow::Borrowed(""),
path: Cow::Borrowed("/"),
domain: None,
secure,
http_only: true,
same_site,
max_age: None, }
}
}
impl CookieTemplate {
#[must_use]
pub fn recommended() -> Self {
Self::default()
}
#[must_use]
pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.name = name.into();
self
}
#[must_use]
pub fn value(mut self, value: impl Into<Cow<'static, str>>) -> Self {
self.value = value.into();
self
}
#[must_use]
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
self.path = path.into();
self
}
#[must_use]
pub fn domain(mut self, domain: impl Into<Cow<'static, str>>) -> Self {
self.domain = Some(domain.into());
self
}
#[must_use]
pub fn clear_domain(mut self) -> Self {
self.domain = None;
self
}
#[must_use]
pub fn secure(mut self, flag: bool) -> Self {
self.secure = flag;
self
}
#[must_use]
#[cfg(debug_assertions)]
pub fn insecure_dev_only(mut self) -> Self {
self.secure = false;
self
}
#[must_use]
pub fn http_only(mut self, flag: bool) -> Self {
self.http_only = flag;
self
}
#[must_use]
pub fn same_site(mut self, same_site: SameSite) -> Self {
self.same_site = same_site;
self
}
#[must_use]
pub fn max_age(mut self, max_age: Duration) -> Self {
self.max_age = Some(max_age);
self
}
#[must_use]
pub fn clear_max_age(mut self) -> Self {
self.max_age = None;
self
}
#[must_use]
pub fn persistent(self, duration: Duration) -> Self {
self.max_age(duration)
}
#[must_use]
pub fn short_lived(self) -> Self {
self.max_age(Duration::minutes(15))
}
pub fn validate(&self) -> Result<(), CookieTemplateBuilderError> {
if self.same_site == SameSite::None && !self.secure {
return Err(CookieTemplateBuilderError::InsecureNoneSameSite);
}
Ok(())
}
#[must_use]
#[inline]
pub fn builder(&self) -> CookieBuilder<'static> {
let mut builder = CookieBuilder::new(self.name.clone(), self.value.clone())
.secure(self.secure)
.http_only(self.http_only)
.same_site(self.same_site)
.path(self.path.clone());
if let Some(ref domain) = self.domain {
builder = builder.domain(domain.clone());
}
if let Some(max_age) = self.max_age {
builder = builder.max_age(max_age);
}
builder
}
pub fn validate_and_build(&self) -> Result<Cookie<'static>, CookieTemplateBuilderError> {
self.validate()?;
Ok(self.builder().build())
}
#[must_use]
#[inline]
pub fn build_with_name_value(&self, name: &str, value: &str) -> Cookie<'static> {
let mut builder = CookieBuilder::new(name.to_owned(), value.to_owned())
.secure(self.secure)
.http_only(self.http_only)
.same_site(self.same_site)
.path(self.path.clone());
if let Some(ref domain) = self.domain {
builder = builder.domain(domain.clone());
}
if let Some(max_age) = self.max_age {
builder = builder.max_age(max_age);
}
builder.build()
}
#[must_use]
#[inline]
pub fn build_with_value(&self, value: &str) -> Cookie<'static> {
self.build_with_name_value(self.name.as_ref(), value)
}
#[must_use]
#[inline]
pub fn build_with_name(&self, name: &str) -> Cookie<'static> {
self.build_with_name_value(name, self.value.as_ref())
}
#[must_use]
pub fn build_removal(&self) -> Cookie<'static> {
let mut cookie = self.builder().build();
cookie.make_removal();
cookie
}
#[must_use]
#[inline]
pub fn cookie_name_ref(&self) -> &str {
self.name.as_ref()
}
}
#[derive(Debug, thiserror::Error)]
pub enum CookieTemplateBuilderError {
#[error("SameSite=None requires Secure=true (browser enforcement & CSRF protection)")]
InsecureNoneSameSite,
}