Skip to main content

topcoat_session/
config.rs

1use std::time::Duration;
2
3use topcoat_core::context::{Cx, app_context};
4
5use crate::TokenStore;
6
7/// Session configuration, registered on the app context (with the router's
8/// `sessions` extension method).
9///
10/// Assemble one with [`Config::builder`]; `Config::default()` is the
11/// all-defaults configuration, carrying the token in the default cookie
12/// store.
13pub struct Config {
14    pub(crate) token_store: Box<dyn TokenStore>,
15    pub(crate) lifetime: Duration,
16    #[cfg(feature = "router")]
17    pub(crate) verify_origin: bool,
18    #[cfg(feature = "router")]
19    pub(crate) trusted_origins: Vec<String>,
20}
21
22/// How long a session lives without being refreshed, unless overridden with
23/// [`ConfigBuilder::lifetime`]: 30 days.
24pub const DEFAULT_LIFETIME: Duration = Duration::from_hours(24 * 30);
25
26impl Config {
27    /// Creates a builder for a session configuration.
28    #[must_use]
29    pub fn builder() -> ConfigBuilder {
30        ConfigBuilder::default()
31    }
32}
33
34/// Builds the all-defaults configuration, like [`Config::builder`] with an
35/// immediate [`build`](ConfigBuilder::build).
36#[cfg(feature = "cookie")]
37impl Default for Config {
38    fn default() -> Self {
39        Self::builder().build()
40    }
41}
42
43/// Assembles a [`Config`]. Created with [`Config::builder`].
44pub struct ConfigBuilder {
45    token_store: Option<Box<dyn TokenStore>>,
46    lifetime: Duration,
47    #[cfg(feature = "router")]
48    verify_origin: bool,
49    #[cfg(feature = "router")]
50    trusted_origins: Vec<String>,
51}
52
53impl ConfigBuilder {
54    /// Overrides the [`TokenStore`] carrying the session token between the
55    /// client and the server.
56    #[must_use]
57    pub fn token_store(mut self, token_store: impl TokenStore + 'static) -> Self {
58        self.token_store = Some(Box::new(token_store));
59        self
60    }
61
62    /// Overrides how long a session lives without being refreshed.
63    ///
64    /// The lifetime becomes the time to live of every issued token, and
65    /// [`start`](crate::start), [`refresh`](crate::refresh), and
66    /// [`rotate`](crate::rotate) derive the session's `expires_at` from it.
67    #[must_use]
68    pub fn lifetime(mut self, lifetime: Duration) -> Self {
69        self.lifetime = lifetime;
70        self
71    }
72
73    /// Trusts `origin` to send state-changing cross-origin requests, exempting
74    /// it from [`verify_origin`](crate::verify_origin).
75    ///
76    /// The value is compared against the request's `Origin` header, so pass
77    /// the full serialized origin: scheme, host, and any non-default port
78    /// (`"https://accounts.example.com"`), with no trailing slash.
79    #[cfg(feature = "router")]
80    #[must_use]
81    pub fn trust_origin(mut self, origin: impl Into<String>) -> Self {
82        self.trusted_origins.push(origin.into());
83        self
84    }
85
86    /// Disables the [`OriginLayer`](crate::OriginLayer) that the router's
87    /// `sessions` extension method registers.
88    ///
89    /// Without the layer, nothing rejects state-changing cross-origin
90    /// requests; only disable it if the application enforces its own defense
91    /// against cross-site request forgery.
92    #[cfg(feature = "router")]
93    #[must_use]
94    pub fn dangerous_disable_origin_verification(mut self) -> Self {
95        self.verify_origin = false;
96        self
97    }
98
99    /// Consumes the builder, returning the finished [`Config`].
100    ///
101    /// # Panics
102    ///
103    /// Panics when no token store was set and the default cookie store is
104    /// unavailable because the `cookie` feature is disabled.
105    #[must_use]
106    pub fn build(self) -> Config {
107        Config {
108            token_store: self.token_store.unwrap_or_else(default_token_store),
109            lifetime: self.lifetime,
110            #[cfg(feature = "router")]
111            verify_origin: self.verify_origin,
112            #[cfg(feature = "router")]
113            trusted_origins: self.trusted_origins,
114        }
115    }
116}
117
118impl Default for ConfigBuilder {
119    fn default() -> Self {
120        Self {
121            token_store: None,
122            lifetime: DEFAULT_LIFETIME,
123            #[cfg(feature = "router")]
124            verify_origin: true,
125            #[cfg(feature = "router")]
126            trusted_origins: Vec::new(),
127        }
128    }
129}
130
131#[cfg(feature = "cookie")]
132fn default_token_store() -> Box<dyn TokenStore> {
133    Box::new(crate::cookie::CookieTokenStore::new())
134}
135
136#[cfg(not(feature = "cookie"))]
137fn default_token_store() -> Box<dyn TokenStore> {
138    panic!(
139        "no token store configured: set one with `ConfigBuilder::token_store` or enable the `cookie` feature for the default cookie store"
140    )
141}
142
143pub(crate) fn config(cx: &Cx) -> &Config {
144    app_context(cx)
145}