Skip to main content

synapse_core/
auth.rs

1use std::collections::HashMap;
2use std::sync::RwLock;
3
4/// Namespace access control
5#[derive(Debug, Clone)]
6pub struct NamespacePermission {
7    pub read: bool,
8    pub write: bool,
9    pub delete: bool,
10    pub reason: bool,
11}
12
13impl Default for NamespacePermission {
14    fn default() -> Self {
15        Self {
16            read: true,
17            write: true,
18            delete: true,
19            reason: true,
20        }
21    }
22}
23
24/// Auth layer for namespace-based access control
25pub struct NamespaceAuth {
26    /// Token -> (namespace patterns, permissions)
27    tokens: RwLock<HashMap<String, (Vec<String>, NamespacePermission)>>,
28    /// Allow unauthenticated access to "default" namespace
29    pub allow_anonymous_default: bool,
30}
31
32impl Default for NamespaceAuth {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl NamespaceAuth {
39    pub fn new() -> Self {
40        Self {
41            tokens: RwLock::new(HashMap::new()),
42            allow_anonymous_default: true,
43        }
44    }
45
46    /// Register a token with access to specific namespaces
47    pub fn register_token(
48        &self,
49        token: &str,
50        namespaces: Vec<String>,
51        permissions: NamespacePermission,
52    ) {
53        let mut tokens = self.tokens.write().unwrap();
54        tokens.insert(token.to_string(), (namespaces, permissions));
55    }
56
57    /// Check if token has permission for namespace and operation
58    pub fn check(
59        &self,
60        token: Option<&str>,
61        namespace: &str,
62        operation: &str,
63    ) -> Result<(), String> {
64        // Anonymous access to default namespace
65        if token.is_none() && namespace == "default" && self.allow_anonymous_default {
66            return Ok(());
67        }
68
69        let token = token.ok_or("Authentication required")?;
70        let tokens = self.tokens.read().unwrap();
71
72        let (patterns, perms) = tokens.get(token).ok_or("Invalid token")?;
73
74        // Check namespace pattern match
75        let ns_match = patterns.iter().any(|p| {
76            if p == "*" {
77                true
78            } else if p.ends_with('*') {
79                namespace.starts_with(&p[..p.len() - 1])
80            } else {
81                p == namespace
82            }
83        });
84
85        if !ns_match {
86            return Err(format!("Token not authorized for namespace: {}", namespace));
87        }
88
89        // Check operation permission
90        match operation {
91            "read" if !perms.read => Err("Read permission denied".to_string()),
92            "write" if !perms.write => Err("Write permission denied".to_string()),
93            "delete" if !perms.delete => Err("Delete permission denied".to_string()),
94            "reason" if !perms.reason => Err("Reasoning permission denied".to_string()),
95            _ => Ok(()),
96        }
97    }
98
99    /// Load tokens from environment variable (JSON format)
100    pub fn load_from_env(&self) {
101        if let Ok(json) = std::env::var("SYNAPSE_AUTH_TOKENS") {
102            // Try parsing as complex object first: {"token": {"namespaces": [...], "permissions": {...}}}
103            if let Ok(map) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&json) {
104                for (token, value) in map {
105                    if let Ok(namespaces) = serde_json::from_value::<Vec<String>>(value.clone()) {
106                        // Legacy format: value is list of namespaces
107                        self.register_token(&token, namespaces, NamespacePermission::default());
108                    } else if let Some(obj) = value.as_object() {
109                        // Complex format
110                        let namespaces = obj
111                            .get("namespaces")
112                            .and_then(|v| serde_json::from_value::<Vec<String>>(v.clone()).ok())
113                            .unwrap_or_default();
114
115                        let permissions = if let Some(p) = obj.get("permissions") {
116                            NamespacePermission {
117                                read: p.get("read").and_then(|v| v.as_bool()).unwrap_or(true),
118                                write: p.get("write").and_then(|v| v.as_bool()).unwrap_or(true),
119                                delete: p.get("delete").and_then(|v| v.as_bool()).unwrap_or(true),
120                                reason: p.get("reason").and_then(|v| v.as_bool()).unwrap_or(true),
121                            }
122                        } else {
123                            NamespacePermission::default()
124                        };
125
126                        self.register_token(&token, namespaces, permissions);
127                    }
128                }
129            }
130        }
131    }
132}