securitydept_session_context/config/
mod.rs1use 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}