tuwunel_service/membership/
auto_accept.rs1use std::time::Duration;
7
8use futures::{FutureExt, StreamExt};
9use ruma::{
10 OwnedRoomId, OwnedUserId, RoomId, UserId, events::invite_permission_config::InvitePermission,
11};
12use tokio::time::sleep;
13use tuwunel_core::{
14 debug, debug_warn, implement,
15 utils::{
16 future::{ReadyBoolExt, and5},
17 result::LogErr,
18 stream::automatic_width,
19 },
20};
21
22use super::{Join, Service};
23
24pub(super) struct Pending {
30 room_id: OwnedRoomId,
31 user_id: OwnedUserId,
32 sender: OwnedUserId,
33 is_direct: bool,
34}
35
36const ATTEMPTS: u32 = 5;
43
44#[implement(Service)]
50pub fn auto_accept(&self, room_id: &RoomId, user_id: &UserId, sender: &UserId, is_direct: bool) {
51 let config = &self.services.config;
52
53 let accepts = config.auto_accept_invites
54 && (is_direct || !config.auto_accept_invites_direct_only)
55 && self.services.globals.user_is_local(user_id)
56 && (!config.auto_accept_invites_local_only
57 || self.services.globals.user_is_local(sender));
58
59 if !accepts {
60 return;
61 }
62
63 self.queue
64 .0
65 .send(Pending {
66 room_id: room_id.to_owned(),
67 user_id: user_id.to_owned(),
68 sender: sender.to_owned(),
69 is_direct,
70 })
71 .ok();
72}
73
74#[implement(Service)]
80pub(super) async fn accept_worker(&self) {
81 let accepting = self
82 .queue
83 .1
84 .stream()
85 .for_each_concurrent(automatic_width(), async |invite| self.accept(invite).await);
86
87 tokio::select! {
88 () = accepting => {},
89 () = self.services.server.until_shutdown() => {},
90 }
91}
92
93#[implement(Service)]
94#[tracing::instrument(
95 name = "auto_accept",
96 level = "debug",
97 skip_all,
98 fields(
99 %room_id,
100 %user_id,
101 ),
102)]
103async fn accept(&self, Pending { room_id, user_id, sender, is_direct }: Pending) {
104 if !self
105 .join_invited(&room_id, &user_id, &sender)
106 .await
107 {
108 return;
109 }
110
111 debug!("Accepted the invitation on the user's behalf.");
112
113 if is_direct {
114 self.services
115 .account_data
116 .mark_direct(&user_id, &sender, &room_id)
117 .await
118 .log_err()
119 .ok();
120 }
121}
122
123#[implement(Service)]
128async fn join_invited(&self, room_id: &RoomId, user_id: &UserId, sender: &UserId) -> bool {
129 for attempt in 0..ATTEMPTS {
130 let delay = attempt
131 .checked_sub(1)
132 .map_or(Duration::ZERO, |retry| Duration::from_secs(1 << retry));
133
134 sleep(delay).await;
135
136 if !self.acceptable(room_id, user_id, sender).await {
137 return false;
138 }
139
140 let joined = self
141 .join(Join {
142 sender_user: user_id,
143 room_id,
144 orig_room_id: None,
145 reason: None,
146 servers: &[],
147 is_appservice: false,
148 extra_content: None,
149 })
150 .await;
151
152 match joined {
153 | Ok(()) => return true,
154 | Err(e) => debug_warn!(?e, "Automatic invite acceptance attempt failed"),
155 }
156 }
157
158 false
159}
160
161#[implement(Service)]
175async fn acceptable(&self, room_id: &RoomId, user_id: &UserId, sender: &UserId) -> bool {
176 let state_cache = &self.services.state_cache;
177 let users = &self.services.users;
178
179 let invited = state_cache.is_invited(user_id, room_id);
180 let active = users.is_active_local(user_id);
181 let unsuspended = users.is_suspended(user_id).is_false();
182 let unlocked = users.is_locked(user_id).is_false();
183 let permitted = users
184 .invite_permission(sender, user_id)
185 .map(|permission| permission.eq(&InvitePermission::Allow));
186
187 and5(invited, active, unsuspended, unlocked, permitted).await
188}