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(&self, token: &str, namespaces: Vec<String>, permissions: NamespacePermission) {
48        let mut tokens = self.tokens.write().unwrap();
49        tokens.insert(token.to_string(), (namespaces, permissions));
50    }
51
52    /// Check if token has permission for namespace and operation
53    pub fn check(&self, token: Option<&str>, namespace: &str, operation: &str) -> Result<(), String> {
54        // Anonymous access to default namespace
55        if token.is_none() && namespace == "default" && self.allow_anonymous_default {
56            return Ok(());
57        }
58
59        let token = token.ok_or("Authentication required")?;
60        let tokens = self.tokens.read().unwrap();
61        
62        let (patterns, perms) = tokens.get(token)
63            .ok_or("Invalid token")?;
64
65        // Check namespace pattern match
66        let ns_match = patterns.iter().any(|p| {
67            if p == "*" { true }
68            else if p.ends_with('*') { namespace.starts_with(&p[..p.len()-1]) }
69            else { p == namespace }
70        });
71
72        if !ns_match {
73            return Err(format!("Token not authorized for namespace: {}", namespace));
74        }
75
76        // Check operation permission
77        match operation {
78            "read" if !perms.read => Err("Read permission denied".to_string()),
79            "write" if !perms.write => Err("Write permission denied".to_string()),
80            "delete" if !perms.delete => Err("Delete permission denied".to_string()),
81            "reason" if !perms.reason => Err("Reasoning permission denied".to_string()),
82            _ => Ok(()),
83        }
84    }
85
86    /// Load tokens from environment variable (JSON format)
87    pub fn load_from_env(&self) {
88        if let Ok(json) = std::env::var("SYNAPSE_AUTH_TOKENS") {
89            if let Ok(map) = serde_json::from_str::<HashMap<String, Vec<String>>>(&json) {
90                for (token, namespaces) in map {
91                    self.register_token(&token, namespaces, NamespacePermission::default());
92                }
93            }
94        }
95    }
96}