Skip to main content

ferro_rs/session/
config.rs

1//! Session configuration
2
3use std::time::Duration;
4
5/// Session configuration with dual timeout support (idle + absolute).
6///
7/// Idle timeout expires sessions after inactivity. Absolute timeout expires sessions
8/// after a fixed period since creation, regardless of activity. Both are enforced
9/// server-side per OWASP session management guidelines.
10#[derive(Clone, Debug)]
11pub struct SessionConfig {
12    /// Idle timeout: session expires after this duration of inactivity
13    pub idle_lifetime: Duration,
14    /// Absolute timeout: session expires this duration after creation, regardless of activity
15    pub absolute_lifetime: Duration,
16    /// Cookie name for the session ID
17    pub cookie_name: String,
18    /// Cookie path
19    pub cookie_path: String,
20    /// Whether to set Secure flag on cookie (HTTPS only)
21    pub cookie_secure: bool,
22    /// Whether to set HttpOnly flag on cookie
23    pub cookie_http_only: bool,
24    /// SameSite attribute for the cookie
25    pub cookie_same_site: String,
26    /// Database table name for sessions
27    pub table_name: String,
28}
29
30impl Default for SessionConfig {
31    fn default() -> Self {
32        Self {
33            idle_lifetime: Duration::from_secs(120 * 60), // 2 hours (120 minutes)
34            absolute_lifetime: Duration::from_secs(43200 * 60), // 30 days (43200 minutes)
35            cookie_name: "ferro_session".to_string(),
36            cookie_path: "/".to_string(),
37            cookie_secure: true,
38            cookie_http_only: true,
39            cookie_same_site: "Lax".to_string(),
40            table_name: "sessions".to_string(),
41        }
42    }
43}
44
45impl SessionConfig {
46    /// Create a new session config with default values
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Load session configuration from environment variables
52    ///
53    /// Environment variables:
54    /// - `SESSION_LIFETIME`: Idle timeout in minutes (default: 120)
55    /// - `SESSION_ABSOLUTE_LIFETIME`: Absolute timeout in minutes (default: 43200 / 30 days)
56    /// - `SESSION_COOKIE`: Cookie name (default: ferro_session)
57    /// - `SESSION_SECURE`: Set Secure flag (default: true)
58    /// - `SESSION_PATH`: Cookie path (default: /)
59    /// - `SESSION_SAME_SITE`: SameSite attribute (default: Lax)
60    pub fn from_env() -> Self {
61        let idle_lifetime_minutes: u64 = crate::env_optional("SESSION_LIFETIME")
62            .and_then(|s: String| s.parse().ok())
63            .unwrap_or(120);
64
65        let absolute_lifetime_minutes: u64 = crate::env_optional("SESSION_ABSOLUTE_LIFETIME")
66            .and_then(|s: String| s.parse().ok())
67            .unwrap_or(43200);
68
69        let cookie_secure = crate::env_optional("SESSION_SECURE")
70            .map(|s: String| s.to_lowercase() == "true" || s == "1")
71            .unwrap_or(true);
72
73        Self {
74            idle_lifetime: Duration::from_secs(idle_lifetime_minutes * 60),
75            absolute_lifetime: Duration::from_secs(absolute_lifetime_minutes * 60),
76            cookie_name: crate::env_optional("SESSION_COOKIE")
77                .unwrap_or_else(|| "ferro_session".to_string()),
78            cookie_path: crate::env_optional("SESSION_PATH").unwrap_or_else(|| "/".to_string()),
79            cookie_secure,
80            cookie_http_only: true, // Always true for security
81            cookie_same_site: crate::env_optional("SESSION_SAME_SITE")
82                .unwrap_or_else(|| "Lax".to_string()),
83            table_name: "sessions".to_string(),
84        }
85    }
86
87    /// Set the idle timeout duration
88    pub fn idle_lifetime(mut self, duration: Duration) -> Self {
89        self.idle_lifetime = duration;
90        self
91    }
92
93    /// Set the absolute timeout duration
94    pub fn absolute_lifetime(mut self, duration: Duration) -> Self {
95        self.absolute_lifetime = duration;
96        self
97    }
98
99    /// Set the cookie name
100    pub fn cookie_name(mut self, name: impl Into<String>) -> Self {
101        self.cookie_name = name.into();
102        self
103    }
104
105    /// Set whether the cookie should be secure (HTTPS only)
106    pub fn secure(mut self, secure: bool) -> Self {
107        self.cookie_secure = secure;
108        self
109    }
110}