    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

    use actix_web::cookie::time::Duration;
    use anyhow::Error;

    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 so 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::config::CookieContentSecurity::Private
    #[derive(Default)]
    #[non_exhaustive]
    pub struct CookieSessionStore;

    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)?;

            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 update_ttl(&self, _session_key: &SessionKey, _ttl: &Duration) -> Result<(), Error> {
            Ok(())
        }

        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(_),
            ));
        }
    }