Skip to main content

auth/
config.rs

1use platform_core::{
2    AppContext, RuntimeConfigDescriptor, RuntimeConfigScope, RuntimeConfigSnapshot,
3    RuntimeConfigType,
4};
5use std::collections::BTreeMap;
6use std::sync::LazyLock;
7use std::time::Duration;
8
9const SESSION_CACHE_KEY: &str = "auth.session_cache";
10const CONSOLE_ADMIN_USER_SCOPES_KEY: &str = "auth.console_admin_user_scopes";
11
12pub const SESSION_CACHE_MAX_TTL: Duration = Duration::from_secs(12 * 60 * 60);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SessionCacheMode {
16    Database,
17    Redis,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct AuthRuntimeConfig {
22    pub session_cache: SessionCacheMode,
23    pub console_admin_user_scopes: BTreeMap<String, Vec<String>>,
24}
25
26impl AuthRuntimeConfig {
27    #[must_use]
28    pub fn from_context(ctx: &AppContext) -> Self {
29        Self::from_snapshot(&ctx.runtime_config.snapshot())
30    }
31
32    #[must_use]
33    pub fn from_snapshot(snapshot: &RuntimeConfigSnapshot) -> Self {
34        let session_cache = snapshot
35            .raw(SESSION_CACHE_KEY)
36            .and_then(serde_json::Value::as_str)
37            .and_then(SessionCacheMode::from_value)
38            .unwrap_or(SessionCacheMode::Database);
39        let console_admin_user_scopes = snapshot
40            .raw(CONSOLE_ADMIN_USER_SCOPES_KEY)
41            .and_then(|value| serde_json::from_value(value.clone()).ok())
42            .unwrap_or_default();
43
44        Self {
45            session_cache,
46            console_admin_user_scopes,
47        }
48    }
49}
50
51impl Default for AuthRuntimeConfig {
52    fn default() -> Self {
53        Self {
54            session_cache: SessionCacheMode::Database,
55            console_admin_user_scopes: BTreeMap::new(),
56        }
57    }
58}
59
60impl SessionCacheMode {
61    fn from_value(value: &str) -> Option<Self> {
62        match value {
63            "database" => Some(Self::Database),
64            "redis" => Some(Self::Redis),
65            _ => None,
66        }
67    }
68}
69
70pub static RUNTIME_CONFIG: LazyLock<Vec<RuntimeConfigDescriptor>> = LazyLock::new(|| {
71    vec![
72        RuntimeConfigDescriptor {
73            key: SESSION_CACHE_KEY.to_owned(),
74            scope: RuntimeConfigScope::Shared,
75            group: None,
76            section: None,
77            order: 10,
78            visible_when: None,
79            generated: None,
80            value_type: RuntimeConfigType::Enum(&["database", "redis"]),
81            default: serde_json::json!("database"),
82            editable: true,
83            restart_only: true,
84            description: "Session cache backend used by auth session resolution.",
85        },
86        RuntimeConfigDescriptor {
87            key: CONSOLE_ADMIN_USER_SCOPES_KEY.to_owned(),
88            scope: RuntimeConfigScope::Shared,
89            group: None,
90            section: None,
91            order: 20,
92            visible_when: None,
93            generated: None,
94            value_type: RuntimeConfigType::Json,
95            default: serde_json::json!({}),
96            editable: true,
97            restart_only: true,
98            description: "Map of auth user ids to Console admin scopes. Users must include `console.admin` to enter admin HTTP endpoints.",
99        },
100    ]
101});
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use platform_core::{RuntimeConfigRegistry, RuntimeConfigSnapshot};
107    use serde_json::json;
108
109    #[test]
110    fn defaults_to_database_cache_and_empty_console_admin_scopes() {
111        let registry = RuntimeConfigRegistry::try_new(RUNTIME_CONFIG.clone()).unwrap();
112        let snapshot = RuntimeConfigSnapshot::resolve(&registry, "api", &BTreeMap::new());
113
114        let config = AuthRuntimeConfig::from_snapshot(&snapshot);
115
116        assert_eq!(config.session_cache, SessionCacheMode::Database);
117        assert!(config.console_admin_user_scopes.is_empty());
118    }
119
120    #[test]
121    fn reads_console_admin_user_scopes_from_runtime_config() {
122        let registry = RuntimeConfigRegistry::try_new(RUNTIME_CONFIG.clone()).unwrap();
123        let mut stored = BTreeMap::new();
124        stored.insert(
125            ("*".to_owned(), CONSOLE_ADMIN_USER_SCOPES_KEY.to_owned()),
126            json!({
127                "usr_admin": [
128                    "console.admin",
129                    "auth.users.read",
130                    "auth.users.manage",
131                    "auth.sessions.revoke"
132                ]
133            }),
134        );
135        let snapshot = RuntimeConfigSnapshot::resolve(&registry, "api", &stored);
136
137        let config = AuthRuntimeConfig::from_snapshot(&snapshot);
138
139        assert_eq!(
140            config.console_admin_user_scopes.get("usr_admin"),
141            Some(&vec![
142                "console.admin".to_owned(),
143                "auth.users.read".to_owned(),
144                "auth.users.manage".to_owned(),
145                "auth.sessions.revoke".to_owned()
146            ])
147        );
148    }
149}