oxidite_auth/
session_middleware.rs1use oxidite_core::{OxiditeRequest, OxiditeResponse, Error as CoreError};
2use tower::{Service, Layer};
3use std::task::{Context, Poll};
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7use cookie::{Cookie, SameSite};
8use crate::session::SessionStore;
9
10const SESSION_COOKIE_NAME: &str = "oxidite_session";
11
12#[derive(Clone)]
14pub struct SessionMiddleware<S> {
15 inner: S,
16 store: Arc<dyn SessionStore>,
17 cookie_secure: bool,
18 cookie_http_only: bool,
19 session_ttl_secs: u64,
20}
21
22impl<S> SessionMiddleware<S> {
23 pub fn new(
24 inner: S,
25 store: Arc<dyn SessionStore>,
26 cookie_secure: bool,
27 cookie_http_only: bool,
28 session_ttl_secs: u64,
29 ) -> Self {
30 Self {
31 inner,
32 store,
33 cookie_secure,
34 cookie_http_only,
35 session_ttl_secs,
36 }
37 }
38}
39
40impl<S> Service<OxiditeRequest> for SessionMiddleware<S>
41where
42 S: Service<OxiditeRequest, Response = OxiditeResponse, Error = CoreError> + Clone + Send + 'static,
43 S::Future: Send + 'static,
44{
45 type Response = S::Response;
46 type Error = S::Error;
47 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
48
49 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
50 self.inner.poll_ready(cx)
51 }
52
53 fn call(&mut self, req: OxiditeRequest) -> Self::Future {
54 let session_id = req
56 .headers()
57 .get("cookie")
58 .and_then(|h| h.to_str().ok())
59 .and_then(|cookies| {
60 for cookie_str in cookies.split(';') {
61 if let Ok(cookie) = Cookie::parse(cookie_str.trim()) {
62 if cookie.name() == SESSION_COOKIE_NAME {
63 return Some(cookie.value().to_string());
64 }
65 }
66 }
67 None
68 });
69
70 let store = self.store.clone();
71 let cookie_secure = self.cookie_secure;
72 let cookie_http_only = self.cookie_http_only;
73 let session_ttl_secs = self.session_ttl_secs;
74 let mut inner = self.inner.clone();
75
76 Box::pin(async move {
77 let session = if let Some(sid) = session_id {
79 store.get(&sid).await.ok().flatten()
80 } else {
81 None
82 };
83
84 let mut response = inner.call(req).await?;
89
90 if let Some(sess) = session {
92 if !sess.is_expired() {
93 let cookie = Cookie::build((SESSION_COOKIE_NAME, sess.id.clone()))
94 .secure(cookie_secure)
95 .http_only(cookie_http_only)
96 .same_site(SameSite::Lax)
97 .max_age(cookie::time::Duration::seconds(session_ttl_secs as i64))
98 .path("/")
99 .build();
100
101 if let Ok(cookie_val) = cookie.to_string().parse() {
102 response.headers_mut().insert("set-cookie", cookie_val);
103 }
104 }
105 }
106
107 Ok(response)
108 })
109 }
110}
111
112pub struct SessionLayer {
114 store: Arc<dyn SessionStore>,
115 cookie_secure: bool,
116 cookie_http_only: bool,
117 session_ttl_secs: u64,
118}
119
120impl SessionLayer {
121 pub fn new(
122 store: Arc<dyn SessionStore>,
123 cookie_secure: bool,
124 cookie_http_only: bool,
125 session_ttl_secs: u64,
126 ) -> Self {
127 Self {
128 store,
129 cookie_secure,
130 cookie_http_only,
131 session_ttl_secs,
132 }
133 }
134
135 pub fn with_defaults(store: Arc<dyn SessionStore>) -> Self {
136 Self::new(store, true, true, 3600)
137 }
138}
139
140impl<S> Layer<S> for SessionLayer {
141 type Service = SessionMiddleware<S>;
142
143 fn layer(&self, inner: S) -> Self::Service {
144 SessionMiddleware::new(
145 inner,
146 self.store.clone(),
147 self.cookie_secure,
148 self.cookie_http_only,
149 self.session_ttl_secs,
150 )
151 }
152}