      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

    use std::convert::TryInto;

    use time::Duration;

    use super::SessionKey;
    use crate::storage::{
        interface::{LoadError, SaveError, SessionState, UpdateError},
        SessionStore,
    };

    /// Use the session key, stored in the session cookie, as storage backend for the session state.
    ///
    /// ```no_run
    /// use actix_web::{cookie::Key, web, App, HttpServer, HttpResponse, Error};
    /// use actix_session::{SessionMiddleware, storage::CookieSessionStore};
    ///
    /// // The secret key would usually be read from a configuration file/environment variables.
    /// fn get_secret_key() -> Key {
    ///     # todo!()
    ///     // [...]
    /// }
    ///
    /// #[actix_web::main]
    /// async fn main() -> std::io::Result<()> {
    ///     let secret_key = get_secret_key();
    ///     HttpServer::new(move ||
    ///             App::new()
    ///             .wrap(SessionMiddleware::new(CookieSessionStore::default(), secret_key.clone()))
    ///             .default_service(web::to(|| HttpResponse::Ok())))
    ///         .bind(("127.0.0.1", 8080))?
    ///         .run()
    ///         .await
    /// }
    /// ```
    ///
    /// # Limitations
    /// Cookies are subject to size limits - we require session keys to be shorter than 4096 bytes. This
    /// translates into a limit on the maximum size of the session state when using cookies as storage
    /// backend.
    ///
    /// The session cookie can always be inspected by end users via the developer tools exposed by their
    /// browsers. We strongly recommend setting the policy to [`CookieContentSecurity::Private`] when
    /// using cookies as storage backend.
    ///
    /// There is no way to invalidate a session before its natural expiry when using cookies as the
    /// storage backend.
    ///
    /// [`CookieContentSecurity::Private`]: crate::CookieContentSecurity::Private
    #[cfg_attr(docsrs, doc(cfg(feature = "cookie-session")))]
    #[derive(Default)]
    #[non_exhaustive]
    pub struct CookieSessionStore;

    #[async_trait::async_trait(?Send)]
    impl SessionStore for CookieSessionStore {
        async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
            serde_json::from_str(session_key.as_ref())
                .map(Some)
                .map_err(anyhow::Error::new)
                .map_err(LoadError::Deserialization)
        }

        async fn save(
            &self,
            session_state: SessionState,
            _ttl: &Duration,
        ) -> Result<SessionKey, SaveError> {
            let session_key = serde_json::to_string(&session_state)
                .map_err(anyhow::Error::new)
                .map_err(SaveError::Serialization)?;

            Ok(session_key
                .try_into()
                .map_err(Into::into)
                .map_err(SaveError::Other)?)
        }

        async fn update(
            &self,
            _session_key: SessionKey,
            session_state: SessionState,
            ttl: &Duration,
        ) -> Result<SessionKey, UpdateError> {
            self.save(session_state, ttl)
                .await
                .map_err(|err| match err {
                    SaveError::Serialization(err) => UpdateError::Serialization(err),
                    SaveError::Other(err) => UpdateError::Other(err),
                })
        }

        async fn delete(&self, _session_key: &SessionKey) -> Result<(), anyhow::Error> {
            Ok(())
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;
        use crate::{storage::utils::generate_session_key, test_helpers::acceptance_test_suite};

        #[actix_web::test]
        async fn test_session_workflow() {
            acceptance_test_suite(CookieSessionStore::default, false).await;
        }

        #[actix_web::test]
        async fn loading_a_random_session_key_returns_deserialization_error() {
            let store = CookieSessionStore::default();
            let session_key = generate_session_key();
            assert!(matches!(
                store.load(&session_key).await.unwrap_err(),
                LoadError::Deserialization(_),
            ));
        }
    }