tuwunel_api/client/admin/users/
create_or_modify.rs1use std::collections::BTreeSet;
2
3use axum::extract::State;
4use futures::StreamExt;
5use ruma::{MilliSecondsSinceUnixEpoch, MxcUri, UserId, thirdparty::Medium};
6use synapse_admin_api::users::create_or_modify::v2 as create_or_modify;
7use tuwunel_core::{
8 Err, Result,
9 utils::{IterStream, ReadyExt, stream::automatic_width},
10};
11use tuwunel_service::{threepid::canonicalize_email, users::PASSWORD_SENTINEL};
12
13use super::user_details;
14use crate::{Ruma, client::admin::require_admin};
15
16pub(crate) async fn admin_create_or_modify_route(
21 State(services): State<crate::State>,
22 body: Ruma<create_or_modify::Request>,
23) -> Result<create_or_modify::Response> {
24 let sender_user = body.sender_user();
25
26 require_admin(&services, sender_user).await?;
27
28 let user_id = &body.user_id;
29
30 if !services.globals.user_is_local(user_id) {
31 return Err!(Request(InvalidParam("Can only create or modify local users")));
32 }
33
34 if body.deactivated == Some(true) && body.locked == Some(true) {
35 return Err!(Request(InvalidParam("An account cannot be deactivated and locked")));
36 }
37
38 if body.admin == Some(false) && sender_user == body.user_id {
39 return Err!(Request(InvalidParam("You may not demote yourself.")));
40 }
41
42 let created = !services.users.exists(user_id).await;
43
44 if created {
45 services
46 .users
47 .create(user_id, body.password.as_deref(), None)
48 .await?;
49 } else if let Some(password) = body.password.as_deref() {
50 services
51 .users
52 .set_password(user_id, Some(password))
53 .await?;
54
55 if body.logout_devices {
56 services
57 .users
58 .all_device_ids(user_id)
59 .map(ToOwned::to_owned)
60 .for_each_concurrent(automatic_width(), async |device_id| {
61 services
62 .users
63 .remove_device(user_id, &device_id)
64 .await;
65 })
66 .await;
67 }
68 }
69
70 if let Some(displayname) = body.displayname.as_deref() {
71 let displayname = (!displayname.is_empty()).then_some(displayname);
72
73 services
74 .profile
75 .set_displayname(user_id, displayname, None)
76 .await?;
77 }
78
79 if let Some(avatar_url) = body.avatar_url.as_deref() {
80 let avatar_url = (!avatar_url.is_empty()).then(|| <&MxcUri>::from(avatar_url));
81
82 services
83 .profile
84 .set_avatar_url(user_id, avatar_url, None)
85 .await?;
86 }
87
88 match body.admin {
89 | Some(true) => services.admin.make_user_admin(user_id).await?,
90 | Some(false) => services.admin.revoke_admin(user_id).await?,
91 | None => {},
92 }
93
94 match body.deactivated {
95 | Some(true) => services.users.deactivate_account(user_id).await?,
96 | Some(false)
97 if services
98 .users
99 .is_deactivated(user_id)
100 .await
101 .unwrap_or(false) =>
102 {
103 if body.password.is_none() {
106 services
107 .users
108 .set_password(user_id, Some(PASSWORD_SENTINEL))
109 .await?;
110 }
111 },
112 | _ => {},
113 }
114
115 match body.locked {
116 | Some(true) => services.users.set_locked(user_id, sender_user),
117 | Some(false) => services.users.clear_locked(user_id),
118 | None => {},
119 }
120
121 if let Some(threepids) = body.threepids.as_deref() {
122 replace_emails(services, user_id, threepids).await?;
123 }
124
125 let details = user_details(services, user_id).await;
126
127 Ok(create_or_modify::Response::new(details))
128}
129
130async fn replace_emails(
133 services: crate::State,
134 user_id: &UserId,
135 threepids: &[create_or_modify::ThirdPartyIdentifier],
136) -> Result {
137 let desired: BTreeSet<String> = threepids
138 .iter()
139 .filter(|tpid| tpid.medium == Medium::Email)
140 .map(|tpid| canonicalize_email(&tpid.address))
141 .collect::<Result<_>>()?;
142
143 let current: BTreeSet<String> = services
144 .threepid
145 .get_bindings(user_id)
146 .ready_filter_map(|tpid| (tpid.medium == Medium::Email).then_some(tpid.address))
147 .collect()
148 .await;
149
150 current
151 .difference(&desired)
152 .stream()
153 .for_each_concurrent(automatic_width(), |address| {
154 services.threepid.del_binding(user_id, address)
155 })
156 .await;
157
158 let now = MilliSecondsSinceUnixEpoch::now();
159
160 desired
161 .difference(¤t)
162 .stream()
163 .for_each_concurrent(automatic_width(), |address| {
164 services
165 .threepid
166 .put_binding(user_id, address, Medium::Email, now, now)
167 })
168 .await;
169
170 Ok(())
171}