use std::{pin::Pin, time::Duration};
use topcoat_core::{context::Cx, error::Result};
use crate::{Token, config};
pub type TokenStoreFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
pub trait TokenStore: Send + Sync {
fn read<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, Option<Token>>;
fn write<'a>(&'a self, cx: &'a Cx, token: Token, max_age: Duration)
-> TokenStoreFuture<'a, ()>;
fn delete<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, ()>;
}
pub(crate) fn token_store(cx: &Cx) -> &dyn TokenStore {
&*config(cx).token_store
}
#[cfg(feature = "cookie")]
pub mod cookie {
use std::{borrow::Cow, time::Duration};
use topcoat_cookie::{Cookie, Cookies, SameSite};
use topcoat_core::context::Cx;
use crate::{Token, TokenStore, TokenStoreFuture};
fn cookies(cx: &Cx) -> impl Cookies {
topcoat_cookie::cookies(cx)
.override_same_site(SameSite::Lax)
.override_http_only(true)
.override_secure(true)
.override_path("/")
.override_prefix_host()
}
pub const SESSION_COOKIE_NAME: &str = "session";
pub struct CookieTokenStore {
name: Cow<'static, str>,
}
impl CookieTokenStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.name = name.into();
self
}
}
impl Default for CookieTokenStore {
fn default() -> Self {
Self {
name: Cow::Borrowed(SESSION_COOKIE_NAME),
}
}
}
impl TokenStore for CookieTokenStore {
fn read<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, Option<Token>> {
Box::pin(async move {
let Some(cookie) = cookies(cx).get(&self.name) else {
return Ok(None);
};
Ok(Token::decode(cookie.value_trimmed()).ok())
})
}
fn write<'a>(
&'a self,
cx: &'a Cx,
token: Token,
max_age: Duration,
) -> TokenStoreFuture<'a, ()> {
Box::pin(async move {
let max_age = topcoat_cookie::time::Duration::try_from(max_age)?;
cookies(cx)
.override_max_age(max_age)
.add(Cookie::new(self.name.clone(), token.encode()));
Ok(())
})
}
fn delete<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, ()> {
Box::pin(async move {
cookies(cx).remove(Cookie::new(self.name.clone(), ""));
Ok(())
})
}
}
}