async_session/session.rs
1use chrono::{DateTime, Duration, Utc};
2use rand::RngCore;
3use serde::{Deserialize, Serialize};
4use std::{
5 collections::HashMap,
6 sync::{
7 atomic::{AtomicBool, Ordering},
8 Arc, RwLock,
9 },
10};
11
12/// # The main session type.
13///
14/// ## Cloning and Serialization
15///
16/// The `cookie_value` field is not cloned or serialized, and it can
17/// only be read through `into_cookie_value`. The intent of this field
18/// is that it is set either by initialization or by a session store,
19/// and read exactly once in order to set the cookie value.
20///
21/// ## Change tracking session tracks whether any of its inner data
22/// was changed since it was last serialized. Any sessoin store that
23/// does not undergo a serialization-deserialization cycle must call
24/// [`Session::reset_data_changed`] in order to reset the change tracker on
25/// an individual record.
26///
27/// ### Change tracking example
28/// ```rust
29/// # use async_session::Session;
30/// # fn main() -> async_session::Result { async_std::task::block_on(async {
31/// let mut session = Session::new();
32/// assert!(!session.data_changed());
33///
34/// session.insert("key", 1)?;
35/// assert!(session.data_changed());
36///
37/// session.reset_data_changed();
38/// assert_eq!(session.get::<usize>("key").unwrap(), 1);
39/// assert!(!session.data_changed());
40///
41/// session.insert("key", 2)?;
42/// assert!(session.data_changed());
43/// assert_eq!(session.get::<usize>("key").unwrap(), 2);
44///
45/// session.insert("key", 1)?;
46/// assert!(session.data_changed(), "reverting the data still counts as a change");
47///
48/// session.reset_data_changed();
49/// assert!(!session.data_changed());
50/// session.remove("nonexistent key");
51/// assert!(!session.data_changed());
52/// session.remove("key");
53/// assert!(session.data_changed());
54/// # Ok(()) }) }
55/// ```
56#[derive(Debug, Serialize, Deserialize)]
57pub struct Session {
58 id: String,
59 expiry: Option<DateTime<Utc>>,
60 data: Arc<RwLock<HashMap<String, String>>>,
61
62 #[serde(skip)]
63 cookie_value: Option<String>,
64 #[serde(skip)]
65 data_changed: Arc<AtomicBool>,
66 #[serde(skip)]
67 destroy: Arc<AtomicBool>,
68}
69
70impl Clone for Session {
71 fn clone(&self) -> Self {
72 Self {
73 cookie_value: None,
74 id: self.id.clone(),
75 data: self.data.clone(),
76 expiry: self.expiry,
77 destroy: self.destroy.clone(),
78 data_changed: self.data_changed.clone(),
79 }
80 }
81}
82
83impl Default for Session {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89/// generates a random cookie value
90fn generate_cookie(len: usize) -> String {
91 let mut key = vec![0u8; len];
92 rand::thread_rng().fill_bytes(&mut key);
93 base64::encode(key)
94}
95
96impl Session {
97 /// Create a new session. Generates a random id and matching
98 /// cookie value. Does not set an expiry by default
99 ///
100 /// # Example
101 ///
102 /// ```rust
103 /// # use async_session::Session;
104 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
105 /// let session = Session::new();
106 /// assert_eq!(None, session.expiry());
107 /// assert!(session.into_cookie_value().is_some());
108 /// # Ok(()) }) }
109 pub fn new() -> Self {
110 let cookie_value = generate_cookie(64);
111 let id = Session::id_from_cookie_value(&cookie_value).unwrap();
112
113 Self {
114 data_changed: Arc::new(AtomicBool::new(false)),
115 expiry: None,
116 data: Arc::new(RwLock::new(HashMap::default())),
117 cookie_value: Some(cookie_value),
118 id,
119 destroy: Arc::new(AtomicBool::new(false)),
120 }
121 }
122
123 /// applies a cryptographic hash function on a cookie value
124 /// returned by [`Session::into_cookie_value`] to obtain the
125 /// session id for that cookie. Returns an error if the cookie
126 /// format is not recognized
127 ///
128 /// # Example
129 ///
130 /// ```rust
131 /// # use async_session::Session;
132 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
133 /// let session = Session::new();
134 /// let id = session.id().to_string();
135 /// let cookie_value = session.into_cookie_value().unwrap();
136 /// assert_eq!(id, Session::id_from_cookie_value(&cookie_value)?);
137 /// # Ok(()) }) }
138 /// ```
139 pub fn id_from_cookie_value(string: &str) -> Result<String, base64::DecodeError> {
140 let decoded = base64::decode(string)?;
141 let hash = blake3::hash(&decoded);
142 Ok(base64::encode(&hash.as_bytes()))
143 }
144
145 /// mark this session for destruction. the actual session record
146 /// is not destroyed until the end of this response cycle.
147 ///
148 /// # Example
149 ///
150 /// ```rust
151 /// # use async_session::Session;
152 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
153 /// let mut session = Session::new();
154 /// assert!(!session.is_destroyed());
155 /// session.destroy();
156 /// assert!(session.is_destroyed());
157 /// # Ok(()) }) }
158 pub fn destroy(&mut self) {
159 self.destroy.store(true, Ordering::Relaxed);
160 }
161
162 /// returns true if this session is marked for destruction
163 ///
164 /// # Example
165 ///
166 /// ```rust
167 /// # use async_session::Session;
168 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
169 /// let mut session = Session::new();
170 /// assert!(!session.is_destroyed());
171 /// session.destroy();
172 /// assert!(session.is_destroyed());
173 /// # Ok(()) }) }
174
175 pub fn is_destroyed(&self) -> bool {
176 self.destroy.load(Ordering::Relaxed)
177 }
178
179 /// Gets the session id
180 ///
181 /// # Example
182 ///
183 /// ```rust
184 /// # use async_session::Session;
185 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
186 /// let session = Session::new();
187 /// let id = session.id().to_owned();
188 /// let cookie_value = session.into_cookie_value().unwrap();
189 /// assert_eq!(id, Session::id_from_cookie_value(&cookie_value)?);
190 /// # Ok(()) }) }
191 pub fn id(&self) -> &str {
192 &self.id
193 }
194
195 /// inserts a serializable value into the session hashmap. returns
196 /// an error if the serialization was unsuccessful.
197 ///
198 /// # Example
199 ///
200 /// ```rust
201 /// # use serde::{Serialize, Deserialize};
202 /// # use async_session::Session;
203 /// #[derive(Serialize, Deserialize)]
204 /// struct User {
205 /// name: String,
206 /// legs: u8
207 /// }
208 /// let mut session = Session::new();
209 /// session.insert("user", User { name: "chashu".into(), legs: 4 }).expect("serializable");
210 /// assert_eq!(r#"{"name":"chashu","legs":4}"#, session.get_raw("user").unwrap());
211 /// ```
212 pub fn insert(&mut self, key: &str, value: impl Serialize) -> Result<(), serde_json::Error> {
213 self.insert_raw(key, serde_json::to_string(&value)?);
214 Ok(())
215 }
216
217 /// inserts a string into the session hashmap
218 ///
219 /// # Example
220 ///
221 /// ```rust
222 /// # use async_session::Session;
223 /// let mut session = Session::new();
224 /// session.insert_raw("ten", "10".to_string());
225 /// let ten: usize = session.get("ten").unwrap();
226 /// assert_eq!(ten, 10);
227 /// ```
228 pub fn insert_raw(&mut self, key: &str, value: String) {
229 let mut data = self.data.write().unwrap();
230 if data.get(key) != Some(&value) {
231 data.insert(key.to_string(), value);
232 self.data_changed.store(true, Ordering::Relaxed);
233 }
234 }
235
236 /// deserializes a type T out of the session hashmap
237 ///
238 /// # Example
239 ///
240 /// ```rust
241 /// # use async_session::Session;
242 /// let mut session = Session::new();
243 /// session.insert("key", vec![1, 2, 3]);
244 /// let numbers: Vec<usize> = session.get("key").unwrap();
245 /// assert_eq!(vec![1, 2, 3], numbers);
246 /// ```
247 pub fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
248 let data = self.data.read().unwrap();
249 let string = data.get(key)?;
250 serde_json::from_str(string).ok()
251 }
252
253 /// returns the String value contained in the session hashmap
254 ///
255 /// # Example
256 ///
257 /// ```rust
258 /// # use async_session::Session;
259 /// let mut session = Session::new();
260 /// session.insert("key", vec![1, 2, 3]);
261 /// assert_eq!("[1,2,3]", session.get_raw("key").unwrap());
262 /// ```
263 pub fn get_raw(&self, key: &str) -> Option<String> {
264 let data = self.data.read().unwrap();
265 data.get(key).cloned()
266 }
267
268 /// removes an entry from the session hashmap
269 ///
270 /// # Example
271 ///
272 /// ```rust
273 /// # use async_session::Session;
274 /// let mut session = Session::new();
275 /// session.insert("key", "value");
276 /// session.remove("key");
277 /// assert!(session.get_raw("key").is_none());
278 /// assert_eq!(session.len(), 0);
279 /// ```
280 pub fn remove(&mut self, key: &str) {
281 let mut data = self.data.write().unwrap();
282 if data.remove(key).is_some() {
283 self.data_changed.store(true, Ordering::Relaxed);
284 }
285 }
286
287 /// returns the number of elements in the session hashmap
288 ///
289 /// # Example
290 ///
291 /// ```rust
292 /// # use async_session::Session;
293 /// let mut session = Session::new();
294 /// assert_eq!(session.len(), 0);
295 /// session.insert("key", 0);
296 /// assert_eq!(session.len(), 1);
297 /// ```
298 pub fn len(&self) -> usize {
299 let data = self.data.read().unwrap();
300 data.len()
301 }
302
303 /// Generates a new id and cookie for this session
304 ///
305 /// # Example
306 ///
307 /// ```rust
308 /// # use async_session::Session;
309 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
310 /// let mut session = Session::new();
311 /// let old_id = session.id().to_string();
312 /// session.regenerate();
313 /// assert!(session.id() != &old_id);
314 /// let new_id = session.id().to_string();
315 /// let cookie_value = session.into_cookie_value().unwrap();
316 /// assert_eq!(new_id, Session::id_from_cookie_value(&cookie_value)?);
317 /// # Ok(()) }) }
318 /// ```
319 pub fn regenerate(&mut self) {
320 let cookie_value = generate_cookie(64);
321 self.id = Session::id_from_cookie_value(&cookie_value).unwrap();
322 self.cookie_value = Some(cookie_value);
323 }
324
325 /// sets the cookie value that this session will use to serialize
326 /// itself. this should only be called by cookie stores. any other
327 /// uses of this method will result in the cookie not getting
328 /// correctly deserialized on subsequent requests.
329 ///
330 /// # Example
331 ///
332 /// ```rust
333 /// # use async_session::Session;
334 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
335 /// let mut session = Session::new();
336 /// session.set_cookie_value("hello".to_owned());
337 /// let cookie_value = session.into_cookie_value().unwrap();
338 /// assert_eq!(cookie_value, "hello".to_owned());
339 /// # Ok(()) }) }
340 /// ```
341 pub fn set_cookie_value(&mut self, cookie_value: String) {
342 self.cookie_value = Some(cookie_value)
343 }
344
345 /// returns the expiry timestamp of this session, if there is one
346 ///
347 /// # Example
348 ///
349 /// ```rust
350 /// # use async_session::Session;
351 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
352 /// let mut session = Session::new();
353 /// assert_eq!(None, session.expiry());
354 /// session.expire_in(std::time::Duration::from_secs(1));
355 /// assert!(session.expiry().is_some());
356 /// # Ok(()) }) }
357 /// ```
358 pub fn expiry(&self) -> Option<&DateTime<Utc>> {
359 self.expiry.as_ref()
360 }
361
362 /// assigns an expiry timestamp to this session
363 ///
364 /// # Example
365 ///
366 /// ```rust
367 /// # use async_session::Session;
368 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
369 /// let mut session = Session::new();
370 /// assert_eq!(None, session.expiry());
371 /// session.set_expiry(chrono::Utc::now());
372 /// assert!(session.expiry().is_some());
373 /// # Ok(()) }) }
374 /// ```
375 pub fn set_expiry(&mut self, expiry: DateTime<Utc>) {
376 self.expiry = Some(expiry);
377 }
378
379 /// assigns the expiry timestamp to a duration from the current time.
380 ///
381 /// # Example
382 ///
383 /// ```rust
384 /// # use async_session::Session;
385 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
386 /// let mut session = Session::new();
387 /// assert_eq!(None, session.expiry());
388 /// session.expire_in(std::time::Duration::from_secs(1));
389 /// assert!(session.expiry().is_some());
390 /// # Ok(()) }) }
391 /// ```
392 pub fn expire_in(&mut self, ttl: std::time::Duration) {
393 self.expiry = Some(Utc::now() + Duration::from_std(ttl).unwrap());
394 }
395
396 /// predicate function to determine if this session is
397 /// expired. returns false if there is no expiry set, or if it is
398 /// in the past.
399 ///
400 /// # Example
401 ///
402 /// ```rust
403 /// # use async_session::Session;
404 /// # use std::time::Duration;
405 /// # use async_std::task;
406 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
407 /// let mut session = Session::new();
408 /// assert_eq!(None, session.expiry());
409 /// assert!(!session.is_expired());
410 /// session.expire_in(Duration::from_secs(1));
411 /// assert!(!session.is_expired());
412 /// task::sleep(Duration::from_secs(2)).await;
413 /// assert!(session.is_expired());
414 /// # Ok(()) }) }
415 /// ```
416 pub fn is_expired(&self) -> bool {
417 match self.expiry {
418 Some(expiry) => expiry < Utc::now(),
419 None => false,
420 }
421 }
422
423 /// Ensures that this session is not expired. Returns None if it is expired
424 ///
425 /// # Example
426 ///
427 /// ```rust
428 /// # use async_session::Session;
429 /// # use std::time::Duration;
430 /// # use async_std::task;
431 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
432 /// let session = Session::new();
433 /// let mut session = session.validate().unwrap();
434 /// session.expire_in(Duration::from_secs(1));
435 /// let session = session.validate().unwrap();
436 /// task::sleep(Duration::from_secs(2)).await;
437 /// assert_eq!(None, session.validate());
438 /// # Ok(()) }) }
439 /// ```
440 pub fn validate(self) -> Option<Self> {
441 if self.is_expired() {
442 None
443 } else {
444 Some(self)
445 }
446 }
447
448 /// Checks if the data has been modified. This is based on the
449 /// implementation of [`PartialEq`] for the inner data type.
450 ///
451 /// # Example
452 ///
453 /// ```rust
454 /// # use async_session::Session;
455 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
456 /// let mut session = Session::new();
457 /// assert!(!session.data_changed(), "new session is not changed");
458 /// session.insert("key", 1);
459 /// assert!(session.data_changed());
460 ///
461 /// session.reset_data_changed();
462 /// assert!(!session.data_changed());
463 /// session.remove("key");
464 /// assert!(session.data_changed());
465 /// # Ok(()) }) }
466 /// ```
467 pub fn data_changed(&self) -> bool {
468 self.data_changed.load(Ordering::Relaxed)
469 }
470
471 /// Resets `data_changed` dirty tracking. This is unnecessary for
472 /// any session store that serializes the data to a string on
473 /// storage.
474 ///
475 /// # Example
476 ///
477 /// ```rust
478 /// # use async_session::Session;
479 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
480 /// let mut session = Session::new();
481 /// assert!(!session.data_changed(), "new session is not changed");
482 /// session.insert("key", 1);
483 /// assert!(session.data_changed());
484 ///
485 /// session.reset_data_changed();
486 /// assert!(!session.data_changed());
487 /// session.remove("key");
488 /// assert!(session.data_changed());
489 /// # Ok(()) }) }
490 /// ```
491 pub fn reset_data_changed(&self) {
492 self.data_changed.store(false, Ordering::Relaxed);
493 }
494
495 /// Ensures that this session is not expired. Returns None if it is expired
496 ///
497 /// # Example
498 ///
499 /// ```rust
500 /// # use async_session::Session;
501 /// # use std::time::Duration;
502 /// # use async_std::task;
503 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
504 /// let mut session = Session::new();
505 /// session.expire_in(Duration::from_secs(123));
506 /// let expires_in = session.expires_in().unwrap();
507 /// assert!(123 - expires_in.as_secs() < 2);
508 /// # Ok(()) }) }
509 /// ```
510 /// Duration from now to the expiry time of this session
511 pub fn expires_in(&self) -> Option<std::time::Duration> {
512 self.expiry?.signed_duration_since(Utc::now()).to_std().ok()
513 }
514
515 /// takes the cookie value and consume this session.
516 /// this is generally only performed by the session store
517 ///
518 /// # Example
519 ///
520 /// ```rust
521 /// # use async_session::Session;
522 /// # fn main() -> async_session::Result { async_std::task::block_on(async {
523 /// let mut session = Session::new();
524 /// session.set_cookie_value("hello".to_owned());
525 /// let cookie_value = session.into_cookie_value().unwrap();
526 /// assert_eq!(cookie_value, "hello".to_owned());
527 /// # Ok(()) }) }
528 /// ```
529 pub fn into_cookie_value(mut self) -> Option<String> {
530 self.cookie_value.take()
531 }
532}
533
534impl PartialEq for Session {
535 fn eq(&self, other: &Self) -> bool {
536 other.id == self.id
537 }
538}