use crate::utils::get_env_with_prefix;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum SessionBackend {
#[default]
InMemory,
#[cfg(feature = "sessions")]
Cookie,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SessionConfig {
#[serde(default)]
pub backend: SessionBackend,
#[serde(default = "default_ttl_seconds")]
pub default_ttl_seconds: u64,
#[serde(default = "default_cookie_name")]
pub cookie_name: String,
#[serde(default)]
pub cookie_domain: Option<String>,
#[serde(default = "default_cookie_path")]
pub cookie_path: String,
#[serde(default = "default_secure")]
pub cookie_secure: bool,
#[serde(default = "default_http_only")]
pub cookie_http_only: bool,
#[serde(default)]
pub encryption_key: Option<String>,
#[serde(default)]
pub allow_insecure_key: bool,
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
backend: SessionBackend::default(),
default_ttl_seconds: default_ttl_seconds(),
cookie_name: default_cookie_name(),
cookie_domain: None,
cookie_path: default_cookie_path(),
cookie_secure: default_secure(),
cookie_http_only: default_http_only(),
encryption_key: None,
allow_insecure_key: false,
}
}
}
impl SessionConfig {
pub fn from_env() -> Self {
let mut config = Self::default();
if let Some(backend) = get_env_with_prefix("SESSION_BACKEND") {
config.backend = match backend.to_lowercase().as_str() {
"cookie" => {
#[cfg(feature = "sessions")]
{
SessionBackend::Cookie
}
#[cfg(not(feature = "sessions"))]
{
tracing::warn!(
"Cookie sessions requested but sessions feature not enabled, using in-memory"
);
SessionBackend::InMemory
}
}
_ => SessionBackend::InMemory,
};
}
if let Some(ttl) = get_env_with_prefix("SESSION_TTL_SECONDS") {
match ttl.parse() {
Ok(seconds) => config.default_ttl_seconds = seconds,
Err(_) => tracing::warn!(
value = %ttl,
"Invalid SESSION_TTL_SECONDS, using default ({})",
default_ttl_seconds()
),
}
}
if let Some(name) = get_env_with_prefix("SESSION_COOKIE_NAME") {
config.cookie_name = name;
}
if let Some(domain) = get_env_with_prefix("SESSION_COOKIE_DOMAIN") {
config.cookie_domain = Some(domain);
}
if let Some(path) = get_env_with_prefix("SESSION_COOKIE_PATH") {
config.cookie_path = path;
}
if let Some(secure) = get_env_with_prefix("SESSION_COOKIE_SECURE") {
match secure.parse() {
Ok(value) => config.cookie_secure = value,
Err(_) => {
tracing::warn!(
value = %secure,
"Invalid SESSION_COOKIE_SECURE (expected true/false), defaulting to true for security"
);
config.cookie_secure = true;
}
}
}
if let Some(http_only) = get_env_with_prefix("SESSION_COOKIE_HTTP_ONLY") {
match http_only.parse() {
Ok(value) => config.cookie_http_only = value,
Err(_) => {
tracing::warn!(
value = %http_only,
"Invalid SESSION_COOKIE_HTTP_ONLY (expected true/false), defaulting to true for security"
);
config.cookie_http_only = true;
}
}
}
if let Some(key) = get_env_with_prefix("SESSION_ENCRYPTION_KEY") {
config.encryption_key = Some(key);
}
if let Some(allow) = get_env_with_prefix("SESSION_ALLOW_INSECURE_KEY") {
match allow.parse() {
Ok(value) => config.allow_insecure_key = value,
Err(_) => {
tracing::warn!(
value = %allow,
"Invalid SESSION_ALLOW_INSECURE_KEY (expected true/false), defaulting to false for security"
);
config.allow_insecure_key = false;
}
}
}
config
}
pub fn default_ttl(&self) -> Duration {
Duration::from_secs(self.default_ttl_seconds)
}
}
fn default_ttl_seconds() -> u64 {
3600 * 24 }
fn default_cookie_name() -> String {
"tideway_session".to_string()
}
fn default_cookie_path() -> String {
"/".to_string()
}
fn default_secure() -> bool {
true
}
fn default_http_only() -> bool {
true
}