1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use crate::{async_trait, Result, Session, SessionStore};

/// A session store that serializes the entire session into a Cookie.
///
/// # ***This is not recommended for most production deployments.***
///
/// This implementation uses [`bincode`](::bincode) to serialize the
/// Session to decrease the size of the cookie. Note: There is a
/// maximum of 4093 cookie bytes allowed _per domain_, so the cookie
/// store is limited in capacity.
///
/// **Note:** Currently, the data in the cookie is only signed, but *not
/// encrypted*. If the contained session data is sensitive and
/// should not be read by a user, the cookie store is not an
/// appropriate choice.
///
/// Expiry: `SessionStore::destroy_session` and
/// `SessionStore::clear_store` are not meaningful for the
/// CookieStore, and noop. Destroying a session must be done at the
/// cookie setting level, which is outside of the scope of this crate.

#[derive(Debug, Clone, Copy)]
pub struct CookieStore;

impl CookieStore {
    /// constructs a new CookieStore
    pub fn new() -> Self {
        Self
    }
}

#[async_trait]
impl SessionStore for CookieStore {
    async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
        let serialized = base64::decode(&cookie_value)?;
        let session: Session = bincode::deserialize(&serialized)?;
        Ok(session.validate())
    }

    async fn store_session(&self, session: Session) -> Result<Option<String>> {
        let serialized = bincode::serialize(&session)?;
        Ok(Some(base64::encode(serialized)))
    }

    async fn destroy_session(&self, _session: Session) -> Result {
        Ok(())
    }

    async fn clear_store(&self) -> Result {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use async_std::task;
    use std::time::Duration;
    #[async_std::test]
    async fn creating_a_new_session_with_no_expiry() -> Result {
        let store = CookieStore::new();
        let mut session = Session::new();
        session.insert("key", "Hello")?;
        let cloned = session.clone();
        let cookie_value = store.store_session(session).await?.unwrap();
        let loaded_session = store.load_session(cookie_value).await?.unwrap();
        assert_eq!(cloned.id(), loaded_session.id());
        assert_eq!("Hello", &loaded_session.get::<String>("key").unwrap());
        assert!(!loaded_session.is_expired());
        assert!(loaded_session.validate().is_some());
        Ok(())
    }

    #[async_std::test]
    async fn updating_a_session() -> Result {
        let store = CookieStore::new();
        let mut session = Session::new();

        session.insert("key", "value")?;
        let cookie_value = store.store_session(session).await?.unwrap();

        let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
        session.insert("key", "other value")?;

        let new_cookie_value = store.store_session(session).await?.unwrap();
        let session = store.load_session(new_cookie_value).await?.unwrap();
        assert_eq!(&session.get::<String>("key").unwrap(), "other value");

        Ok(())
    }

    #[async_std::test]
    async fn updating_a_session_extending_expiry() -> Result {
        let store = CookieStore::new();
        let mut session = Session::new();
        session.expire_in(Duration::from_secs(1));
        let original_expires = session.expiry().unwrap().clone();
        let cookie_value = store.store_session(session).await?.unwrap();

        let mut session = store.load_session(cookie_value.clone()).await?.unwrap();

        assert_eq!(session.expiry().unwrap(), &original_expires);
        session.expire_in(Duration::from_secs(3));
        let new_expires = session.expiry().unwrap().clone();
        let cookie_value = store.store_session(session).await?.unwrap();

        let session = store.load_session(cookie_value.clone()).await?.unwrap();
        assert_eq!(session.expiry().unwrap(), &new_expires);

        task::sleep(Duration::from_secs(3)).await;
        assert_eq!(None, store.load_session(cookie_value).await?);

        Ok(())
    }

    #[async_std::test]
    async fn creating_a_new_session_with_expiry() -> Result {
        let store = CookieStore::new();
        let mut session = Session::new();
        session.expire_in(Duration::from_secs(3));
        session.insert("key", "value")?;
        let cloned = session.clone();

        let cookie_value = store.store_session(session).await?.unwrap();

        let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap();
        assert_eq!(cloned.id(), loaded_session.id());
        assert_eq!("value", &*loaded_session.get::<String>("key").unwrap());

        assert!(!loaded_session.is_expired());

        task::sleep(Duration::from_secs(3)).await;
        assert_eq!(None, store.load_session(cookie_value).await?);

        Ok(())
    }
}