Skip to main content

securitydept_session_context/config/
mod.rs

1use std::time::Duration as StdDuration;
2
3use securitydept_utils::redirect::{RedirectTargetConfig, UriRelativeRedirectTargetResolver};
4use serde::{Deserialize, Serialize};
5use snafu::Snafu;
6use typed_builder::TypedBuilder;
7
8use crate::{SessionContextError, SessionContextResult, SessionCookieSameSite};
9
10pub mod validator;
11
12pub use validator::{
13    NoopSessionContextConfigValidator, SessionContextConfigValidationError,
14    SessionContextConfigValidator, SessionContextFixedPostAuthRedirectValidator,
15};
16
17#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
18pub struct SessionContextConfig {
19    #[builder(default = crate::DEFAULT_COOKIE_NAME.to_string())]
20    #[serde(default = "default_cookie_name")]
21    pub cookie_name: String,
22    #[builder(default = crate::DEFAULT_SESSION_CONTEXT_KEY.to_string())]
23    #[serde(default = "default_session_context_key")]
24    pub session_context_key: String,
25    #[builder(default = "/".to_string())]
26    #[serde(default = "default_cookie_path")]
27    pub cookie_path: String,
28    #[builder(default = true)]
29    #[serde(default = "default_true")]
30    pub http_only: bool,
31    #[builder(default = false)]
32    #[serde(default)]
33    pub secure: bool,
34    #[builder(default)]
35    #[serde(default)]
36    pub same_site: SessionCookieSameSite,
37    #[builder(default = Some(StdDuration::from_secs(86_400)))]
38    #[serde(default = "default_ttl", with = "humantime_serde::option")]
39    pub ttl: Option<StdDuration>,
40    #[builder(default = default_post_auth_redirect())]
41    #[serde(default = "default_post_auth_redirect")]
42    pub post_auth_redirect: RedirectTargetConfig,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct ResolvedSessionContextConfig {
47    pub cookie_name: String,
48    pub session_context_key: String,
49    pub cookie_path: String,
50    pub http_only: bool,
51    pub secure: bool,
52    pub same_site: SessionCookieSameSite,
53    pub ttl: Option<StdDuration>,
54    pub post_auth_redirect: RedirectTargetConfig,
55}
56
57#[derive(Debug, Snafu)]
58pub enum SessionContextConfigValidationFailure {
59    #[snafu(transparent)]
60    Config { source: SessionContextError },
61    #[snafu(transparent)]
62    Validation {
63        source: SessionContextConfigValidationError,
64    },
65}
66
67pub trait SessionContextConfigSource {
68    fn cookie_name_config(&self) -> &str;
69    fn session_context_key_config(&self) -> &str;
70    fn cookie_path_config(&self) -> &str;
71    fn http_only_config(&self) -> bool;
72    fn secure_config(&self) -> bool;
73    fn same_site_config(&self) -> SessionCookieSameSite;
74    fn ttl_config(&self) -> Option<StdDuration>;
75    fn post_auth_redirect_config(&self) -> &RedirectTargetConfig;
76
77    fn resolve_cookie_name(&self) -> String {
78        self.cookie_name_config().to_string()
79    }
80
81    fn resolve_session_context_key(&self) -> String {
82        self.session_context_key_config().to_string()
83    }
84
85    fn resolve_cookie_path(&self) -> String {
86        self.cookie_path_config().to_string()
87    }
88
89    fn resolve_http_only(&self) -> bool {
90        self.http_only_config()
91    }
92
93    fn resolve_secure(&self) -> bool {
94        self.secure_config()
95    }
96
97    fn resolve_same_site(&self) -> SessionCookieSameSite {
98        self.same_site_config()
99    }
100
101    fn resolve_ttl(&self) -> Option<StdDuration> {
102        self.ttl_config()
103    }
104
105    fn resolve_post_auth_redirect_config(&self) -> SessionContextResult<RedirectTargetConfig> {
106        let config = self.post_auth_redirect_config().clone();
107        resolve_session_post_auth_redirect(&config, None)?;
108        Ok(config)
109    }
110
111    fn resolve_all(
112        &self,
113    ) -> Result<ResolvedSessionContextConfig, SessionContextConfigValidationFailure> {
114        let validator = NoopSessionContextConfigValidator;
115        self.resolve_all_with_validator(&validator)
116    }
117
118    fn resolve_all_with_validator<V>(
119        &self,
120        validator: &V,
121    ) -> Result<ResolvedSessionContextConfig, SessionContextConfigValidationFailure>
122    where
123        V: SessionContextConfigValidator,
124    {
125        validator
126            .validate_session_context_config(self)
127            .map_err(|source| SessionContextConfigValidationFailure::Validation { source })?;
128
129        Ok(ResolvedSessionContextConfig {
130            cookie_name: self.resolve_cookie_name(),
131            session_context_key: self.resolve_session_context_key(),
132            cookie_path: self.resolve_cookie_path(),
133            http_only: self.resolve_http_only(),
134            secure: self.resolve_secure(),
135            same_site: self.resolve_same_site(),
136            ttl: self.resolve_ttl(),
137            post_auth_redirect: self
138                .resolve_post_auth_redirect_config()
139                .map_err(|source| SessionContextConfigValidationFailure::Config { source })?,
140        })
141    }
142}
143
144impl SessionContextConfigSource for SessionContextConfig {
145    fn cookie_name_config(&self) -> &str {
146        &self.cookie_name
147    }
148
149    fn session_context_key_config(&self) -> &str {
150        &self.session_context_key
151    }
152
153    fn cookie_path_config(&self) -> &str {
154        &self.cookie_path
155    }
156
157    fn http_only_config(&self) -> bool {
158        self.http_only
159    }
160
161    fn secure_config(&self) -> bool {
162        self.secure
163    }
164
165    fn same_site_config(&self) -> SessionCookieSameSite {
166        self.same_site
167    }
168
169    fn ttl_config(&self) -> Option<StdDuration> {
170        self.ttl
171    }
172
173    fn post_auth_redirect_config(&self) -> &RedirectTargetConfig {
174        &self.post_auth_redirect
175    }
176}
177
178impl Default for SessionContextConfig {
179    fn default() -> Self {
180        Self {
181            cookie_name: default_cookie_name(),
182            session_context_key: default_session_context_key(),
183            cookie_path: default_cookie_path(),
184            http_only: default_true(),
185            secure: false,
186            same_site: SessionCookieSameSite::default(),
187            ttl: default_ttl(),
188            post_auth_redirect: default_post_auth_redirect(),
189        }
190    }
191}
192
193impl Default for ResolvedSessionContextConfig {
194    fn default() -> Self {
195        Self {
196            cookie_name: default_cookie_name(),
197            session_context_key: default_session_context_key(),
198            cookie_path: default_cookie_path(),
199            http_only: default_true(),
200            secure: false,
201            same_site: SessionCookieSameSite::default(),
202            ttl: default_ttl(),
203            post_auth_redirect: default_post_auth_redirect(),
204        }
205    }
206}
207
208impl ResolvedSessionContextConfig {
209    pub fn resolve_post_auth_redirect(
210        &self,
211        requested_post_auth_redirect: Option<&str>,
212    ) -> SessionContextResult<String> {
213        resolve_session_post_auth_redirect(&self.post_auth_redirect, requested_post_auth_redirect)
214    }
215}
216
217pub(crate) fn default_cookie_name() -> String {
218    crate::DEFAULT_COOKIE_NAME.to_string()
219}
220
221pub(crate) fn default_session_context_key() -> String {
222    crate::DEFAULT_SESSION_CONTEXT_KEY.to_string()
223}
224
225pub(crate) fn default_cookie_path() -> String {
226    "/".to_string()
227}
228
229pub(crate) fn default_true() -> bool {
230    true
231}
232
233pub(crate) fn default_ttl() -> Option<StdDuration> {
234    Some(StdDuration::from_secs(86_400))
235}
236
237pub(crate) fn default_post_auth_redirect() -> RedirectTargetConfig {
238    RedirectTargetConfig::strict_default("/")
239}
240
241pub(crate) fn resolve_session_post_auth_redirect(
242    config: &RedirectTargetConfig,
243    requested_post_auth_redirect: Option<&str>,
244) -> SessionContextResult<String> {
245    UriRelativeRedirectTargetResolver::from_config(config.clone())
246        .map_err(|source| SessionContextError::RedirectTarget { source })?
247        .resolve_redirect_target(requested_post_auth_redirect)
248        .map(|value| value.to_string())
249        .map_err(|source| SessionContextError::RedirectTarget { source })
250}