1#![allow(deprecated)] #[cfg(any(feature = "sessions", feature = "jwt", feature = "token"))]
12use serde::{Deserialize, Serialize};
13
14#[cfg(feature = "sessions")]
17mod session_settings {
18 use super::*;
19 use crate::sessions::config::{SameSite, SessionConfig};
20 use reinhardt_core::macros::settings;
21 use std::time::Duration;
22
23 fn default_cookie_name() -> String {
24 "sessionid".to_string()
25 }
26
27 fn default_cookie_path() -> String {
28 "/".to_string()
29 }
30
31 fn default_cookie_secure() -> bool {
32 true
33 }
34
35 fn default_cookie_httponly() -> bool {
36 true
37 }
38
39 fn default_cookie_samesite() -> String {
40 "lax".to_string()
41 }
42
43 fn parse_samesite(value: &str) -> SameSite {
47 match value.to_ascii_lowercase().as_str() {
48 "strict" => SameSite::Strict,
49 "none" => SameSite::None,
50 _ => SameSite::Lax,
51 }
52 }
53
54 #[settings(fragment = true, section = "auth_session")]
64 #[derive(Clone, Debug, Serialize, Deserialize)]
65 pub struct SessionSettings {
66 #[serde(default = "default_cookie_name")]
68 pub cookie_name: String,
69 #[serde(default)]
71 pub cookie_age_secs: Option<u64>,
72 #[serde(default = "default_cookie_path")]
74 pub cookie_path: String,
75 #[serde(default)]
77 pub cookie_domain: Option<String>,
78 #[serde(default = "default_cookie_secure")]
80 pub cookie_secure: bool,
81 #[serde(default = "default_cookie_httponly")]
83 pub cookie_httponly: bool,
84 #[serde(default = "default_cookie_samesite")]
86 pub cookie_samesite: String,
87 #[serde(default)]
89 pub save_every_request: bool,
90 }
91
92 impl Default for SessionSettings {
93 fn default() -> Self {
94 Self {
95 cookie_name: default_cookie_name(),
96 cookie_age_secs: None,
97 cookie_path: default_cookie_path(),
98 cookie_domain: None,
99 cookie_secure: default_cookie_secure(),
100 cookie_httponly: default_cookie_httponly(),
101 cookie_samesite: default_cookie_samesite(),
102 save_every_request: false,
103 }
104 }
105 }
106
107 impl SessionSettings {
108 pub fn to_config(&self) -> SessionConfig {
113 let mut builder = SessionConfig::builder()
114 .cookie_name(self.cookie_name.clone())
115 .cookie_path(self.cookie_path.clone())
116 .cookie_secure(self.cookie_secure)
117 .cookie_httponly(self.cookie_httponly)
118 .cookie_samesite(parse_samesite(&self.cookie_samesite))
119 .save_every_request(self.save_every_request);
120 if let Some(secs) = self.cookie_age_secs {
121 builder = builder.cookie_age(Duration::from_secs(secs));
122 }
123 if let Some(domain) = &self.cookie_domain {
124 builder = builder.cookie_domain(domain.clone());
125 }
126 builder.build()
127 }
128 }
129
130 impl From<&SessionSettings> for SessionConfig {
131 fn from(settings: &SessionSettings) -> Self {
132 settings.to_config()
133 }
134 }
135}
136
137#[cfg(feature = "sessions")]
138pub use session_settings::SessionSettings;
139
140#[cfg(feature = "jwt")]
143mod jwt_session_settings {
144 use super::*;
145 use crate::sessions::backends::jwt::{JwtConfig, JwtSessionBackend, JwtSessionError};
146 use jsonwebtoken::Algorithm;
147 use reinhardt_core::macros::settings;
148 use serde::ser::SerializeStruct;
149 use std::fmt;
150
151 fn default_algorithm() -> String {
152 "HS256".to_string()
153 }
154
155 fn default_expiration() -> u64 {
156 3600 }
158
159 fn parse_algorithm(value: &str) -> Algorithm {
165 match value.to_ascii_uppercase().as_str() {
166 "HS384" => Algorithm::HS384,
167 "HS512" => Algorithm::HS512,
168 _ => Algorithm::HS256,
169 }
170 }
171
172 #[settings(fragment = true, section = "auth_jwt_session")]
185 #[derive(Clone, Deserialize)]
186 pub struct JwtSessionSettings {
187 #[serde(default)]
189 pub secret: String,
190 #[serde(default = "default_algorithm")]
192 pub algorithm: String,
193 #[serde(default = "default_expiration")]
195 pub expiration: u64,
196 #[serde(default)]
198 pub issuer: Option<String>,
199 #[serde(default)]
201 pub audience: Option<String>,
202 }
203
204 impl Default for JwtSessionSettings {
205 fn default() -> Self {
206 Self {
207 secret: String::new(),
208 algorithm: default_algorithm(),
209 expiration: default_expiration(),
210 issuer: None,
211 audience: None,
212 }
213 }
214 }
215
216 impl JwtSessionSettings {
217 pub fn to_config(&self) -> JwtConfig {
219 JwtConfig {
220 secret: self.secret.clone(),
221 algorithm: parse_algorithm(&self.algorithm),
222 expiration: self.expiration,
223 issuer: self.issuer.clone(),
224 audience: self.audience.clone(),
225 }
226 }
227 }
228
229 impl fmt::Debug for JwtSessionSettings {
230 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
231 formatter
232 .debug_struct("JwtSessionSettings")
233 .field("secret", &"[REDACTED]")
234 .field("algorithm", &self.algorithm)
235 .field("expiration", &self.expiration)
236 .field("issuer", &self.issuer)
237 .field("audience", &self.audience)
238 .finish()
239 }
240 }
241
242 impl Serialize for JwtSessionSettings {
243 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244 where
245 S: serde::Serializer,
246 {
247 let mut state = serializer.serialize_struct("JwtSessionSettings", 5)?;
248 state.serialize_field("secret", "[REDACTED]")?;
249 state.serialize_field("algorithm", &self.algorithm)?;
250 state.serialize_field("expiration", &self.expiration)?;
251 state.serialize_field("issuer", &self.issuer)?;
252 state.serialize_field("audience", &self.audience)?;
253 state.end()
254 }
255 }
256
257 impl From<&JwtSessionSettings> for JwtConfig {
258 fn from(settings: &JwtSessionSettings) -> Self {
259 settings.to_config()
260 }
261 }
262
263 pub fn create_jwt_session_backend_from_settings(
269 settings: &JwtSessionSettings,
270 ) -> Result<JwtSessionBackend, JwtSessionError> {
271 JwtSessionBackend::new(settings.to_config())
272 }
273}
274
275#[cfg(feature = "jwt")]
276pub use jwt_session_settings::{JwtSessionSettings, create_jwt_session_backend_from_settings};
277
278#[cfg(feature = "token")]
281mod token_rotation_settings {
282 use super::*;
283 use crate::token_rotation::{AutoTokenRotationManager, TokenRotationConfig};
284 use reinhardt_core::macros::settings;
285
286 fn default_rotation_interval() -> i64 {
287 3600 }
289
290 fn default_grace_period() -> i64 {
291 300 }
293
294 fn default_max_active_tokens() -> usize {
295 5
296 }
297
298 #[settings(fragment = true, section = "auth_token_rotation")]
306 #[derive(Clone, Debug, Serialize, Deserialize)]
307 pub struct TokenRotationSettings {
308 #[serde(default = "default_rotation_interval")]
310 pub rotation_interval: i64,
311 #[serde(default = "default_grace_period")]
313 pub grace_period: i64,
314 #[serde(default = "default_max_active_tokens")]
316 pub max_active_tokens: usize,
317 #[serde(default)]
319 pub rotate_on_use: bool,
320 }
321
322 impl Default for TokenRotationSettings {
323 fn default() -> Self {
324 Self {
325 rotation_interval: default_rotation_interval(),
326 grace_period: default_grace_period(),
327 max_active_tokens: default_max_active_tokens(),
328 rotate_on_use: false,
329 }
330 }
331 }
332
333 impl TokenRotationSettings {
334 pub fn to_config(&self) -> TokenRotationConfig {
336 TokenRotationConfig {
337 rotation_interval: self.rotation_interval,
338 grace_period: self.grace_period,
339 max_active_tokens: self.max_active_tokens,
340 rotate_on_use: self.rotate_on_use,
341 }
342 }
343 }
344
345 impl From<&TokenRotationSettings> for TokenRotationConfig {
346 fn from(settings: &TokenRotationSettings) -> Self {
347 settings.to_config()
348 }
349 }
350
351 pub fn create_token_rotation_manager_from_settings(
354 settings: &TokenRotationSettings,
355 ) -> AutoTokenRotationManager {
356 AutoTokenRotationManager::new(settings.to_config())
357 }
358}
359
360#[cfg(feature = "token")]
361pub use token_rotation_settings::{
362 TokenRotationSettings, create_token_rotation_manager_from_settings,
363};
364
365#[cfg(test)]
366mod tests {
367 #[cfg(feature = "sessions")]
368 #[test]
369 fn session_settings_default_matches_legacy_config() {
370 use super::SessionSettings;
371 use crate::sessions::config::SessionConfig;
372
373 let from_settings = SessionSettings::default().to_config();
374 let legacy = SessionConfig::default();
375
376 assert_eq!(from_settings.cookie_name(), legacy.cookie_name());
377 assert_eq!(from_settings.cookie_age(), legacy.cookie_age());
378 assert_eq!(from_settings.cookie_path(), legacy.cookie_path());
379 assert_eq!(from_settings.cookie_domain(), legacy.cookie_domain());
380 assert_eq!(from_settings.cookie_secure(), legacy.cookie_secure());
381 assert_eq!(from_settings.cookie_httponly(), legacy.cookie_httponly());
382 assert_eq!(from_settings.cookie_samesite(), legacy.cookie_samesite());
383 assert_eq!(
384 from_settings.save_every_request(),
385 legacy.save_every_request()
386 );
387 }
388
389 #[cfg(feature = "sessions")]
390 #[test]
391 fn session_settings_round_trips_samesite_and_age() {
392 use super::SessionSettings;
393 use crate::sessions::config::SameSite;
394 use std::time::Duration;
395
396 let settings = SessionSettings {
397 cookie_samesite: "strict".to_string(),
398 cookie_age_secs: Some(7200),
399 cookie_domain: Some("example.com".to_string()),
400 ..SessionSettings::default()
401 };
402
403 let config = settings.to_config();
404 assert_eq!(config.cookie_samesite(), SameSite::Strict);
405 assert_eq!(config.cookie_age(), Some(Duration::from_secs(7200)));
406 assert_eq!(config.cookie_domain(), Some("example.com"));
407 }
408
409 #[cfg(feature = "jwt")]
410 #[test]
411 fn jwt_session_settings_default_matches_legacy_config() {
412 use super::JwtSessionSettings;
413 use crate::sessions::backends::jwt::JwtConfig;
414 use jsonwebtoken::Algorithm;
415
416 let settings = JwtSessionSettings::default();
417 let config = settings.to_config();
418 let legacy = JwtConfig::new(String::new());
421
422 assert_eq!(config.secret, settings.secret);
423 assert_eq!(config.algorithm, Algorithm::HS256);
424 assert_eq!(config.algorithm, legacy.algorithm);
425 assert_eq!(config.expiration, legacy.expiration);
426 assert_eq!(config.issuer, legacy.issuer);
427 assert_eq!(config.audience, legacy.audience);
428 }
429
430 #[cfg(feature = "jwt")]
431 #[test]
432 fn jwt_session_settings_redacts_secret_in_debug_and_json() {
433 use super::JwtSessionSettings;
434
435 let raw_secret = "super-secret-signing-key";
436 let settings = JwtSessionSettings {
437 secret: raw_secret.to_string(),
438 ..JwtSessionSettings::default()
439 };
440
441 let debug = format!("{settings:?}");
442 let json = serde_json::to_string(&settings).expect("settings should serialize");
443
444 assert_eq!(settings.to_config().secret, raw_secret);
445 assert!(!debug.contains(raw_secret));
446 assert!(debug.contains("[REDACTED]"));
447 assert!(!json.contains(raw_secret));
448 assert!(json.contains("[REDACTED]"));
449 }
450
451 #[cfg(feature = "token")]
452 #[test]
453 fn token_rotation_settings_default_matches_legacy_config() {
454 use super::TokenRotationSettings;
455 use crate::token_rotation::TokenRotationConfig;
456
457 let from_settings = TokenRotationSettings::default().to_config();
458 let legacy = TokenRotationConfig::default();
459
460 assert_eq!(from_settings.rotation_interval, legacy.rotation_interval);
461 assert_eq!(from_settings.grace_period, legacy.grace_period);
462 assert_eq!(from_settings.max_active_tokens, legacy.max_active_tokens);
463 assert_eq!(from_settings.rotate_on_use, legacy.rotate_on_use);
464 }
465}