realtime/server/
policy.rs1use super::{ChannelName, ConnectionMeta, RealtimeError};
2
3pub trait ChannelPolicy: Send + Sync {
4 fn can_join(&self, meta: &ConnectionMeta, channel: &ChannelName) -> Result<(), RealtimeError>;
5 fn can_publish(
6 &self,
7 meta: &ConnectionMeta,
8 channel: &ChannelName,
9 event: &str,
10 ) -> Result<(), RealtimeError>;
11}
12
13#[derive(Debug, Default)]
14pub struct DefaultChannelPolicy;
15
16impl ChannelPolicy for DefaultChannelPolicy {
17 fn can_join(&self, meta: &ConnectionMeta, channel: &ChannelName) -> Result<(), RealtimeError> {
18 let name = channel.as_str();
19 if let Some(user) = name.strip_prefix("user:") {
20 if user == meta.user_id || is_admin(meta) {
21 return Ok(());
22 }
23 return Err(RealtimeError::forbidden(
24 "Cannot join another user's private channel",
25 ));
26 }
27 if name.starts_with("admin:") && !is_admin(meta) {
28 return Err(RealtimeError::forbidden(
29 "Admin channel requires admin role",
30 ));
31 }
32 Ok(())
33 }
34
35 fn can_publish(
36 &self,
37 meta: &ConnectionMeta,
38 channel: &ChannelName,
39 event: &str,
40 ) -> Result<(), RealtimeError> {
41 if event.trim().is_empty() {
42 return Err(RealtimeError::bad_request("Event name is required"));
43 }
44 let name = channel.as_str();
45 if let Some(user) = name.strip_prefix("user:") {
46 if user == meta.user_id || is_admin(meta) {
47 return Ok(());
48 }
49 return Err(RealtimeError::forbidden(
50 "Cannot publish to another user's private channel",
51 ));
52 }
53 if name.starts_with("admin:") && !is_admin(meta) {
54 return Err(RealtimeError::forbidden(
55 "Admin channel requires admin role",
56 ));
57 }
58 Ok(())
59 }
60}
61
62fn is_admin(meta: &ConnectionMeta) -> bool {
63 meta.roles.iter().any(|role| role == "admin")
64}
65
66#[cfg(test)]
67mod tests {
68 use uuid::Uuid;
69
70 use super::*;
71 use crate::server::ConnectionId;
72
73 fn connection_meta(user_id: &str, roles: Vec<String>) -> ConnectionMeta {
74 ConnectionMeta {
75 id: ConnectionId(Uuid::new_v4()),
76 user_id: user_id.to_string(),
77 roles,
78 joined_at_unix: 0,
79 }
80 }
81
82 #[test]
83 fn user_cannot_join_another_private_channel() {
84 let policy = DefaultChannelPolicy;
85 let user_meta = connection_meta("u1", vec!["user".to_string()]);
86 let channel = ChannelName::parse("user:u2").expect("channel should parse");
87
88 let err = policy
89 .can_join(&user_meta, &channel)
90 .expect_err("join should be denied");
91 assert_eq!(err.message(), "Cannot join another user's private channel");
92 }
93
94 #[test]
95 fn admin_can_join_another_private_channel() {
96 let policy = DefaultChannelPolicy;
97 let admin_meta = connection_meta("admin", vec!["admin".to_string(), "user".to_string()]);
98 let channel = ChannelName::parse("user:u2").expect("channel should parse");
99
100 policy
101 .can_join(&admin_meta, &channel)
102 .expect("admin should be allowed");
103 }
104
105 #[test]
106 fn user_publish_to_admin_channel_is_denied() {
107 let policy = DefaultChannelPolicy;
108 let user_meta = connection_meta("u1", vec!["user".to_string()]);
109 let channel = ChannelName::parse("admin:ops").expect("channel should parse");
110
111 let err = policy
112 .can_publish(&user_meta, &channel, "status.updated")
113 .expect_err("publish should be denied");
114 assert_eq!(err.message(), "Admin channel requires admin role");
115 }
116}