axum_security/cookie/
mod.rs1mod builder;
32mod expiry;
33mod id;
34mod service;
35mod session;
36mod store;
37
38use std::{borrow::Cow, convert::Infallible, sync::Arc};
39
40use axum::{
41 extract::{FromRef, FromRequestParts},
42 http::{HeaderMap, request::Parts},
43 response::Response,
44};
45
46pub use builder::CookieSessionBuilder;
47pub use id::SessionId;
48pub use session::CookieSession;
49pub use store::{CookieStore, MemStore};
50
51pub use cookie_monster::{Cookie, CookieBuilder, CookieJar, Expires, SameSite};
52use tokio::task::JoinHandle;
53
54use crate::{cookie::store::ErasedStore, utils::utc_now};
55
56pub struct CookieContext<S>(Arc<CookieContextInner<S>>);
64
65struct CookieContextInner<S> {
66 store: ErasedStore<S>,
67 cookie_opts: CookieBuilder,
68 handle: Option<JoinHandle<()>>,
69}
70
71impl CookieContext<()> {
72 pub fn builder() -> CookieSessionBuilder<()> {
74 CookieSessionBuilder::new()
75 }
76}
77
78impl<S: 'static> CookieContext<S> {
79 pub fn get_cookie(&self, session_id: SessionId) -> Cookie {
81 self.0.cookie_opts.clone().value(session_id).build()
82 }
83
84 pub async fn create_session(&self, state: S) -> Result<Cookie, Response> {
86 let session_id = SessionId::new();
87 crate::debug!("Storing {session_id:?} in cookie store");
88 let now = utc_now().as_secs();
89 let session = CookieSession::new(session_id.clone(), now, state);
90 self.0.store.store_session(session).await?;
91
92 Ok(self.get_cookie(session_id))
93 }
94
95 pub async fn remove_session_jar(
97 &self,
98 jar: &CookieJar,
99 ) -> Result<Option<CookieSession<S>>, Response> {
100 let Some(session_id) = self.session_id_from_jar(jar) else {
101 return Ok(None);
102 };
103
104 self.0.store.remove_session(&session_id).await
105 }
106
107 pub async fn remove_session_cookie(
109 &self,
110 cookie: &Cookie,
111 ) -> Result<Option<CookieSession<S>>, Response> {
112 let session_id = SessionId::from_cookie(cookie);
113 self.remove_session(&session_id).await
114 }
115
116 pub async fn remove_session(
118 &self,
119 session_id: &SessionId,
120 ) -> Result<Option<CookieSession<S>>, Response> {
121 self.0.store.remove_session(session_id).await
122 }
123
124 pub fn build_cookie(&self, name: impl Into<Cow<'static, str>>) -> CookieBuilder {
126 self.0.cookie_opts.clone().name(name)
127 }
128
129 pub fn cookie_builder(&self) -> &CookieBuilder {
131 &self.0.cookie_opts
132 }
133
134 pub async fn remove_before(&self, deadline: u64) -> Result<(), Response> {
136 self.0.store.remove_before(deadline).await
137 }
138
139 pub(crate) async fn load_from_headers(
140 &self,
141 headers: &HeaderMap,
142 ) -> Result<Option<CookieSession<S>>, Response> {
143 let cookies = CookieJar::from_headers(headers);
144
145 self.load_from_jar(&cookies).await
146 }
147
148 pub(crate) async fn load_from_jar(
149 &self,
150 cookies: &CookieJar,
151 ) -> Result<Option<CookieSession<S>>, Response> {
152 let Some(session_id) = self.session_id_from_jar(cookies) else {
153 return Ok(None);
154 };
155
156 self.0.store.load_session(&session_id).await
157 }
158
159 pub(crate) fn session_id_from_jar(&self, jar: &CookieJar) -> Option<SessionId> {
160 let cookie = jar.get(self.0.cookie_opts.get_name())?;
161
162 Some(SessionId::from_cookie(cookie))
163 }
164
165 pub async fn load_from_cookie(
167 &self,
168 cookie: &Cookie,
169 ) -> Result<Option<CookieSession<S>>, Response> {
170 let session_id = SessionId::from_cookie(cookie);
171
172 self.0.store.load_session(&session_id).await
173 }
174}
175
176impl<S, U> FromRequestParts<S> for CookieContext<U>
177where
178 CookieContext<U>: FromRef<S>,
179 S: Send + Sync,
180{
181 type Rejection = Infallible;
182
183 async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
184 Ok(Self::from_ref(state))
185 }
186}
187
188impl<S> Drop for CookieContextInner<S> {
189 fn drop(&mut self) {
190 if let Some(handle) = &self.handle {
194 handle.abort();
195 }
196 }
197}
198impl<S> Clone for CookieContext<S> {
199 fn clone(&self) -> Self {
200 CookieContext(self.0.clone())
201 }
202}