rainier_framework/
broadcasting.rs1use std::sync::Arc;
18
19use rainier_auth::AuthenticatedUser;
20use rainier_broadcast::{Broadcasting, Channel, ChannelRegistry};
21use rainier_container::Application;
22use rainier_http::{Request, Response, StatusCode};
23use rainier_notify::{Channel as NotificationChannel, Delivery};
24use rainier_routing::Req;
25use rainier_support::{BoxFuture, Error, Result};
26
27pub const SOCKET_ID_HEADER: &str = "X-Socket-ID";
32
33pub fn socket_id(request: &Request) -> Option<String> {
39 request.header(SOCKET_ID_HEADER).map(str::to_string)
40}
41
42pub async fn authorize<U>(request: Req) -> Result<Response>
61where
62 U: Send + Sync + 'static,
63{
64 let socket = required(&request, "socket_id")?;
65 let wire_name = required(&request, "channel_name")?;
66 let channel = Channel::from_wire_name(&wire_name);
67
68 let app = rainier_container::facade_application();
69 let registry = app.resolve::<ChannelRegistry<U>>()?;
70 let broadcasting = app.resolve::<Broadcasting>()?;
71
72 let Some(user) = request.extension::<AuthenticatedUser<U>>() else {
73 return Err(Error::unauthenticated("Unauthenticated."));
74 };
75
76 let access = registry.authorize(user.get(), &channel).await?;
77 if !access.is_allowed() {
78 return Ok(Response::new(StatusCode::FORBIDDEN));
82 }
83
84 let body = broadcasting.driver().auth_response(&socket, &channel, access.member())?;
88
89 Ok(Response::json(&body))
90}
91
92fn required(request: &Request, name: &str) -> Result<String> {
94 request
95 .input(name)
96 .map(|value| value.to_string())
97 .filter(|value| !value.is_empty())
98 .ok_or_else(|| Error::bad_request(format!("`{name}` is required.")))
99}
100
101pub struct BroadcastChannel {
120 broadcasting: Arc<Broadcasting>,
121}
122
123impl BroadcastChannel {
124 pub fn new(broadcasting: Arc<Broadcasting>) -> Self {
126 Self { broadcasting }
127 }
128
129 pub fn channel_for(notifiable_type: &str, notifiable_id: &str) -> Channel {
133 Channel::private(format!("notifications.{notifiable_type}.{notifiable_id}"))
134 }
135}
136
137impl NotificationChannel for BroadcastChannel {
138 fn name(&self) -> &'static str {
139 "broadcast"
140 }
141
142 fn send<'a>(&'a self, delivery: &'a Delivery) -> BoxFuture<'a, Result<()>> {
143 Box::pin(async move {
144 let Some(payload) = delivery.data_or_text() else {
145 return Err(delivery.nothing_to_send("broadcast", "data or text"));
146 };
147
148 let channel = Self::channel_for(delivery.recipient_type, &delivery.recipient_id);
149
150 self.broadcasting.to(vec![channel], delivery.notification, payload).await
151 })
152 }
153}
154
155pub fn authorize_notifications<U, F>(
164 registry: &mut ChannelRegistry<U>,
165 notifiable_type: &'static str,
166 identify: F,
167) where
168 U: Send + Sync + 'static,
169 F: Fn(&U) -> String + Send + Sync + 'static,
170{
171 registry.channel("notifications.{type}.{id}", move |user: &U, params| {
172 let allowed = params.get("type") == Some(notifiable_type)
173 && params.get("id") == Some(identify(user).as_str());
174
175 Box::pin(async move { Ok(rainier_broadcast::ChannelAccess::allowed_if(allowed)) })
176 });
177}
178
179pub fn bind_defaults<U: Send + Sync + 'static>(app: &Application) {
185 app.instance(Broadcasting::log());
186 app.instance(ChannelRegistry::<U>::new());
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use rainier_broadcast::{ChannelAccess, MemoryBroadcaster};
193 use rainier_notify::{Channels, Notifiable, Notification, Notifier};
194
195 struct User(u64);
196
197 impl Notifiable for User {
198 fn notifiable_id(&self) -> String {
199 self.0.to_string()
200 }
201 fn notifiable_type(&self) -> &'static str {
202 "User"
203 }
204 fn route_for(&self, _: &str) -> Option<String> {
205 None
206 }
207 }
208
209 struct Mentioned;
210
211 impl Notification<User> for Mentioned {
212 fn notification_name(&self) -> &'static str {
213 "post.mentioned"
214 }
215 fn via(&self, _: &User) -> Channels {
216 Channels::new().with::<BroadcastChannel>()
217 }
218 fn to_text(&self, _: &User) -> Option<String> {
219 Some("someone mentioned you".into())
220 }
221 }
222
223 #[tokio::test]
224 async fn a_notification_broadcasts_to_the_recipients_own_channel() {
225 let driver = Arc::new(MemoryBroadcaster::new());
226 let broadcasting = Arc::new(Broadcasting::new(driver.clone()));
227 let notifier = Notifier::new().with(BroadcastChannel::new(broadcasting));
228
229 notifier.send(&User(7), &Mentioned).await.unwrap();
230
231 driver.assert_broadcast("post.mentioned", "private-notifications.User.7");
232 assert_eq!(driver.sent()[0].payload["message"], "someone mentioned you");
233 }
234
235 #[tokio::test]
236 async fn a_recipient_may_listen_to_their_own_and_nobody_elses() {
237 let mut registry = ChannelRegistry::<User>::new();
238 authorize_notifications(&mut registry, "User", |user| user.notifiable_id());
239
240 let mine = BroadcastChannel::channel_for("User", "7");
241 assert_eq!(registry.authorize(&User(7), &mine).await.unwrap(), ChannelAccess::Allowed);
242 assert_eq!(registry.authorize(&User(8), &mine).await.unwrap(), ChannelAccess::Denied);
243 }
244
245 #[tokio::test]
246 async fn another_notifiable_type_with_the_same_id_is_denied() {
247 let mut registry = ChannelRegistry::<User>::new();
250 authorize_notifications(&mut registry, "User", |user| user.notifiable_id());
251
252 let theirs = BroadcastChannel::channel_for("Team", "7");
253 assert_eq!(registry.authorize(&User(7), &theirs).await.unwrap(), ChannelAccess::Denied);
254 }
255}