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