Skip to main content

rustauth_core/
env.rs

1//! Environment helpers for RustAuth core.
2
3pub mod logger;
4
5use crate::options::{DeploymentMode, RustAuthOptions};
6
7/// Reads `RUSTAUTH_{suffix}` from the process environment.
8///
9/// Empty values are treated as unset.
10pub fn env_var(suffix: &str) -> Option<String> {
11    let key = format!("RUSTAUTH_{suffix}");
12    std::env::var(&key).ok().filter(|value| !value.is_empty())
13}
14
15/// Returns true when RustAuth is running in a production environment.
16pub fn is_production() -> bool {
17    std::env::var("RUST_ENV").is_ok_and(|value| value == "production")
18}
19
20/// Returns true when the process is running under a development-oriented environment.
21fn is_development_env() -> bool {
22    match std::env::var("RUST_ENV") {
23        Ok(value) => value == "development" || value == "test",
24        Err(_) => is_test_runtime(),
25    }
26}
27
28fn is_test_runtime() -> bool {
29    std::env::var("RUST_TEST_THREADS").is_ok()
30        || std::env::var("NEXTEST").is_ok_and(|value| value == "1")
31        || std::env::var("TEST")
32            .is_ok_and(|value| !value.is_empty() && value != "0" && value.to_lowercase() != "false")
33}
34
35/// Whether security-sensitive defaults should assume a production deployment.
36///
37/// Ambiguous deployments (neither explicitly production nor development) fail closed
38/// and are treated as production.
39pub fn is_production_posture(options: &RustAuthOptions) -> bool {
40    !allows_development_defaults(options)
41}
42
43/// Whether development-oriented security defaults are explicitly allowed.
44pub fn allows_development_defaults(options: &RustAuthOptions) -> bool {
45    match options.mode {
46        DeploymentMode::Production => false,
47        DeploymentMode::Development => !is_production(),
48        DeploymentMode::Auto => !is_production() && is_development_env(),
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use std::sync::{Mutex, MutexGuard, OnceLock};
55
56    use super::*;
57
58    struct EnvRestore(Vec<(&'static str, Option<String>)>);
59
60    impl EnvRestore {
61        fn unset(keys: &[&'static str]) -> Self {
62            let saved = keys
63                .iter()
64                .map(|key| (*key, std::env::var(key).ok()))
65                .collect::<Vec<_>>();
66            for key in keys {
67                std::env::remove_var(key);
68            }
69            Self(saved)
70        }
71    }
72
73    impl Drop for EnvRestore {
74        fn drop(&mut self) {
75            for (key, value) in &self.0 {
76                match value {
77                    Some(value) => std::env::set_var(key, value),
78                    None => std::env::remove_var(key),
79                }
80            }
81        }
82    }
83
84    fn env_lock() -> &'static Mutex<()> {
85        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
86        LOCK.get_or_init(|| Mutex::new(()))
87    }
88
89    fn lock_env() -> MutexGuard<'static, ()> {
90        env_lock()
91            .lock()
92            .unwrap_or_else(|poisoned| poisoned.into_inner())
93    }
94
95    #[test]
96    fn env_var_reads_rustauth_prefix() {
97        let _guard = lock_env();
98        let _restore = EnvRestore::unset(&["RUSTAUTH_SECRET"]);
99        std::env::set_var("RUSTAUTH_SECRET", "rustauth-secret");
100
101        assert_eq!(env_var("SECRET").as_deref(), Some("rustauth-secret"));
102    }
103
104    #[test]
105    fn env_var_ignores_empty_values() {
106        let _guard = lock_env();
107        let _restore = EnvRestore::unset(&["RUSTAUTH_SECRET"]);
108        std::env::set_var("RUSTAUTH_SECRET", "");
109
110        assert_eq!(env_var("SECRET"), None);
111    }
112
113    #[test]
114    fn env_var_returns_none_when_unset() {
115        let _guard = lock_env();
116        let _restore = EnvRestore::unset(&["RUSTAUTH_SECRET"]);
117
118        assert_eq!(env_var("SECRET"), None);
119    }
120}