tuwunel_service/admin/
grant.rs1use futures::FutureExt;
2use ruma::{
3 UserId,
4 events::{
5 StateEventType,
6 room::{
7 member::{MembershipState, RoomMemberEventContent},
8 message::RoomMessageEventContent,
9 power_levels::RoomPowerLevelsEventContent,
10 },
11 },
12};
13use tuwunel_core::{
14 Err, Result, debug_info, debug_warn, error, implement, matrix::pdu::PduBuilder,
15};
16
17#[implement(super::Service)]
21pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
22 let Ok(room_id) = self.get_admin_room().await else {
23 debug_warn!(
24 "make_user_admin was called without an admin room being available or created"
25 );
26 return Ok(());
27 };
28
29 let state_lock = self.services.state.mutex.lock(&room_id).await;
30
31 if self
32 .services
33 .state_cache
34 .is_joined(user_id, &room_id)
35 .await
36 {
37 return Err!(debug_warn!("User is already joined in the admin room"));
38 }
39
40 if self
41 .services
42 .state_cache
43 .is_invited(user_id, &room_id)
44 .await
45 {
46 return Err!(debug_warn!("User is already pending an invitation to the admin room"));
47 }
48
49 let server_user = self.services.globals.server_user.as_ref();
51
52 if self.services.globals.user_is_local(user_id) {
55 debug_info!("Inviting local user {user_id} to admin room {room_id}");
56 self.services
57 .timeline
58 .build_and_append_pdu(
59 PduBuilder::state(
60 String::from(user_id),
61 &RoomMemberEventContent::new(MembershipState::Invite),
62 ),
63 server_user,
64 &room_id,
65 &state_lock,
66 )
67 .await?;
68
69 debug_info!("Force joining local user {user_id} to admin room {room_id}");
70 self.services
71 .timeline
72 .build_and_append_pdu(
73 PduBuilder::state(
74 String::from(user_id),
75 &RoomMemberEventContent::new(MembershipState::Join),
76 ),
77 user_id,
78 &room_id,
79 &state_lock,
80 )
81 .await?;
82 } else {
83 debug_info!("Inviting remote user {user_id} to admin room {room_id}");
84 self.services
85 .timeline
86 .build_and_append_pdu(
87 PduBuilder::state(
88 user_id.to_string(),
89 &RoomMemberEventContent::new(MembershipState::Invite),
90 ),
91 server_user,
92 &room_id,
93 &state_lock,
94 )
95 .await?;
96 }
97
98 let mut room_power_levels = self
100 .services
101 .state_accessor
102 .room_state_get_content::<RoomPowerLevelsEventContent>(
103 &room_id,
104 &StateEventType::RoomPowerLevels,
105 "",
106 )
107 .await
108 .unwrap_or_default();
109
110 room_power_levels
111 .users
112 .insert(server_user.into(), 69420.into());
113 room_power_levels
114 .users
115 .insert(user_id.into(), 100.into());
116
117 self.services
118 .timeline
119 .build_and_append_pdu(
120 PduBuilder::state(String::new(), &room_power_levels),
121 server_user,
122 &room_id,
123 &state_lock,
124 )
125 .await?;
126
127 let room_tag = self
129 .services
130 .server
131 .config
132 .admin_room_tag
133 .as_str();
134
135 if !room_tag.is_empty()
136 && let Err(e) = self
137 .services
138 .account_data
139 .set_room_tag(user_id, &room_id, room_tag.into(), None)
140 .await
141 {
142 error!(?room_id, ?user_id, ?room_tag, "Failed to set tag for admin grant: {e}");
143 }
144
145 if self.services.server.config.admin_room_notices {
146 let welcome_message = String::from(
147 "## Thank you for trying out tuwunel!\n\nTuwunel is a continuation of conduwuit which was technically a hard fork of Conduit.\n\nHelpful links:\n> GitHub Repo: https://github.com/matrix-construct/tuwunel\n> Documentation: https://matrix-construct.github.io/tuwunel\n> Report issues: https://github.com/matrix-construct/tuwunel/issues\n\nFor a list of available commands, send the following message in this room: `!admin --help`",
148 );
149
150 self.services
152 .timeline
153 .build_and_append_pdu(
154 PduBuilder::timeline(&RoomMessageEventContent::text_markdown(welcome_message)),
155 server_user,
156 &room_id,
157 &state_lock,
158 )
159 .await?;
160 }
161
162 Ok(())
163}
164
165#[implement(super::Service)]
167pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
168 use MembershipState::{Invite, Join, Knock, Leave};
169
170 let Ok(room_id) = self.get_admin_room().await else {
171 return Err!(error!("No admin room available or created."));
172 };
173
174 let state_lock = self.services.state.mutex.lock(&room_id).await;
175
176 let event = match self
177 .services
178 .state_accessor
179 .get_member(&room_id, user_id)
180 .await
181 {
182 | Err(e) if e.is_not_found() => return Err!("{user_id} was never an admin."),
183
184 | Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
185
186 | Ok(event) if !matches!(event.membership, Invite | Knock | Join) => {
187 return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership);
188 },
189
190 | Ok(event) => {
191 assert!(
192 matches!(event.membership, Invite | Knock | Join),
193 "Incorrect membership state to remove user."
194 );
195
196 event
197 },
198 };
199
200 self.services
201 .timeline
202 .build_and_append_pdu(
203 PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
204 membership: Leave,
205 reason: Some("Admin Revoked".into()),
206 is_direct: None,
207 join_authorized_via_users_server: None,
208 third_party_invite: None,
209 ..event
210 }),
211 self.services.globals.server_user.as_ref(),
212 &room_id,
213 &state_lock,
214 )
215 .boxed()
216 .await
217 .map(|_| ())
218}