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}