topcoat_session/token/
store.rs1use std::{pin::Pin, time::Duration};
2
3use topcoat_core::{context::Cx, error::Result};
4
5use crate::{Token, config};
6
7pub type TokenStoreFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
10
11pub trait TokenStore: Send + Sync {
19 fn read<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, Option<Token>>;
22
23 fn write<'a>(&'a self, cx: &'a Cx, token: Token, max_age: Duration)
26 -> TokenStoreFuture<'a, ()>;
27
28 fn delete<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, ()>;
30}
31
32pub(crate) fn token_store(cx: &Cx) -> &dyn TokenStore {
33 &*config(cx).token_store
34}
35
36#[cfg(feature = "cookie")]
37pub mod cookie {
38 use std::{borrow::Cow, time::Duration};
39
40 use topcoat_cookie::{Cookie, Cookies, SameSite};
41 use topcoat_core::context::Cx;
42
43 use crate::{Token, TokenStore, TokenStoreFuture};
44
45 fn cookies(cx: &Cx) -> impl Cookies {
46 topcoat_cookie::cookies(cx)
47 .override_same_site(SameSite::Lax)
48 .override_http_only(true)
49 .override_secure(true)
50 .override_path("/")
51 .override_prefix_host()
52 }
53
54 pub const SESSION_COOKIE_NAME: &str = "session";
56
57 pub struct CookieTokenStore {
63 name: Cow<'static, str>,
64 }
65
66 impl CookieTokenStore {
67 #[must_use]
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 #[must_use]
75 pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
76 self.name = name.into();
77 self
78 }
79 }
80
81 impl Default for CookieTokenStore {
82 fn default() -> Self {
83 Self {
84 name: Cow::Borrowed(SESSION_COOKIE_NAME),
85 }
86 }
87 }
88
89 impl TokenStore for CookieTokenStore {
90 fn read<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, Option<Token>> {
91 Box::pin(async move {
92 let Some(cookie) = cookies(cx).get(&self.name) else {
93 return Ok(None);
94 };
95 Ok(Token::decode(cookie.value_trimmed()).ok())
96 })
97 }
98
99 fn write<'a>(
100 &'a self,
101 cx: &'a Cx,
102 token: Token,
103 max_age: Duration,
104 ) -> TokenStoreFuture<'a, ()> {
105 Box::pin(async move {
106 let max_age = topcoat_cookie::time::Duration::try_from(max_age)?;
107 cookies(cx)
108 .override_max_age(max_age)
109 .add(Cookie::new(self.name.clone(), token.encode()));
110 Ok(())
111 })
112 }
113
114 fn delete<'a>(&'a self, cx: &'a Cx) -> TokenStoreFuture<'a, ()> {
115 Box::pin(async move {
116 cookies(cx).remove(Cookie::new(self.name.clone(), ""));
117 Ok(())
118 })
119 }
120 }
121}