Skip to main content

rainier_session/
driver.rs

1//! Where session state lives — [`SessionDriver`].
2
3use rainier_support::setting_enum;
4
5setting_enum! {
6    /// Which [`SessionStore`](crate::SessionStore) to build.
7    ///
8    /// ```
9    /// use rainier_session::SessionDriver;
10    /// use rainier_support::Setting;
11    ///
12    /// assert_eq!(SessionDriver::parse("cookie").unwrap(), SessionDriver::Cookie);
13    /// assert!(SessionDriver::parse("redis").is_err(), "sessions go in the cache, not a driver of their own");
14    /// ```
15    ///
16    /// Note what is *not* here: `redis`. Sessions in Redis are the `cache`
17    /// driver pointed at Redis, which is one store to configure rather than
18    /// two, and one connection pool rather than two.
19    pub enum SessionDriver: "session driver" {
20        /// This process only. Sessions vanish on restart and are not shared
21        /// between instances, so a second instance logs everyone out.
22        ///
23        /// The default because it needs nothing, and wrong for anything with
24        /// more than one process.
25        #[default]
26        Memory = "memory",
27
28        /// A table in the application's own database.
29        ///
30        /// Shared, durable, and revocable. Needs the migration —
31        /// `DatabaseSessionStore::migrations()` — and something to call
32        /// `prune()` periodically.
33        Database = "database",
34
35        /// Whatever the cache is pointed at: Redis, a Redis Cluster, or
36        /// Memcached, all behind one port.
37        ///
38        /// The cache expires them itself, so nothing has to sweep.
39        Cache = "cache",
40
41        /// The whole session, encrypted, in the cookie.
42        ///
43        /// No server state at all — and therefore **no way to revoke a
44        /// session** short of rotating the key, and a hard size limit. Read
45        /// the docs before choosing it.
46        Cookie = "cookie",
47    }
48}
49
50impl SessionDriver {
51    /// Whether sessions survive a restart of the process that made them.
52    pub fn is_durable(&self) -> bool {
53        matches!(self, Self::Database | Self::Cookie)
54    }
55
56    /// Whether the server can end a session it has already issued.
57    ///
58    /// `false` for [`Cookie`](Self::Cookie), and that is the property that
59    /// decides whether it is usable: a logout, a password change and a stolen
60    /// cookie all need this.
61    pub fn is_revocable(&self) -> bool {
62        !matches!(self, Self::Cookie)
63    }
64
65    /// Whether every instance of the application sees the same sessions.
66    pub fn is_shared(&self) -> bool {
67        !matches!(self, Self::Memory)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use rainier_support::Setting;
75
76    #[test]
77    fn the_cookie_driver_is_the_one_that_cannot_be_revoked() {
78        assert!(!SessionDriver::Cookie.is_revocable());
79        for driver in SessionDriver::ALL.iter().filter(|d| **d != SessionDriver::Cookie) {
80            assert!(driver.is_revocable(), "{driver} should be revocable");
81        }
82    }
83
84    #[test]
85    fn memory_is_the_only_unshared_driver() {
86        assert!(!SessionDriver::Memory.is_shared());
87        assert!(SessionDriver::Cache.is_shared());
88        assert!(SessionDriver::Database.is_shared());
89        assert!(SessionDriver::Cookie.is_shared());
90    }
91
92    #[test]
93    fn cache_sessions_are_shared_but_not_durable() {
94        // A cache is allowed to evict. That is the distinction the two
95        // predicates exist to keep apart.
96        assert!(SessionDriver::Cache.is_shared());
97        assert!(!SessionDriver::Cache.is_durable());
98    }
99}