1mod dehydrated_device;
2pub mod device;
3mod keys;
4mod ldap;
5mod register;
6
7use std::sync::Arc;
8
9use futures::{Stream, StreamExt, TryFutureExt};
10use ruma::{
11 MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
12 api::client::filter::FilterDefinition,
13 events::{
14 GlobalAccountDataEventType,
15 ignored_user_list::IgnoredUserListEvent,
16 invite_permission_config::{InvitePermissionAction, InvitePermissionConfigEvent},
17 },
18};
19use serde::{Deserialize, Serialize};
20use tuwunel_core::{
21 Err, Result, debug_warn, err, is_equal_to,
22 matrix::pdu::PduCount,
23 trace,
24 utils::{self, ReadyExt, stream::TryIgnore},
25};
26use tuwunel_database::{Deserialized, Json, Map};
27
28pub use self::{dehydrated_device::DehydratedDevice, keys::parse_master_key, register::Register};
29
30pub const PASSWORD_SENTINEL: &str = "*";
31pub const PASSWORD_DISABLED: &str = "";
32
33#[derive(Clone, Debug, Serialize, Deserialize)]
37pub struct Moderation {
38 pub when: MilliSecondsSinceUnixEpoch,
39 pub by: OwnedUserId,
40}
41
42pub struct Service {
43 services: Arc<crate::services::OnceServices>,
44 db: Data,
45}
46
47struct Data {
48 keychangeid_userid: Arc<Map>,
49 keyid_key: Arc<Map>,
50 onetimekeyid4225_otk: Option<Arc<Map>>,
51 openidtoken_expiresatuserid: Arc<Map>,
52 logintoken_expiresatuserid: Arc<Map>,
53 todeviceid_events: Arc<Map>,
54 spentrefresh_userdeviceid: Arc<Map>,
55 token_userdeviceid: Arc<Map>,
56 userdeviceid_metadata: Arc<Map>,
57 userdeviceid_token: Arc<Map>,
58 userdeviceidtoken_index: Arc<Map>,
59 userdeviceid_refresh: Arc<Map>,
60 userdeviceid_spentrefresh: Arc<Map>,
61 userdeviceidalgorithm_fallback: Arc<Map>,
62 oidcdevice_userdeviceid: Arc<Map>,
63 oidccskeybypass_userid: Arc<Map>,
64 userfilterid_filter: Arc<Map>,
65 userid_dehydrateddevice: Arc<Map>,
66 userid_devicelistversion: Arc<Map>,
67 userid_erased: Arc<Map>,
68 userid_lastonetimekeyupdate: Arc<Map>,
69 userid_locked: Arc<Map>,
70 userid_masterkeyid: Arc<Map>,
71 userid_password: Arc<Map>,
72 userid_origin: Arc<Map>,
73 userid_selfsigningkeyid: Arc<Map>,
74 userid_suspended: Arc<Map>,
75 userid_usersigningkeyid: Arc<Map>,
76}
77
78impl crate::Service for Service {
79 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
80 Ok(Arc::new(Self {
81 services: args.services.clone(),
82 db: Data {
83 keychangeid_userid: args.db["keychangeid_userid"].clone(),
84 keyid_key: args.db["keyid_key"].clone(),
85 onetimekeyid4225_otk: args.db.get("onetimekeyid4225_otk").ok().cloned(),
86 openidtoken_expiresatuserid: args.db["openidtoken_expiresatuserid"].clone(),
87 logintoken_expiresatuserid: args.db["logintoken_expiresatuserid"].clone(),
88 oidcdevice_userdeviceid: args.db["oidcdevice_userdeviceid"].clone(),
89 oidccskeybypass_userid: args.db["oidccskeybypass_userid"].clone(),
90 todeviceid_events: args.db["todeviceid_events"].clone(),
91 spentrefresh_userdeviceid: args.db["spentrefresh_userdeviceid"].clone(),
92 token_userdeviceid: args.db["token_userdeviceid"].clone(),
93 userdeviceid_metadata: args.db["userdeviceid_metadata"].clone(),
94 userdeviceid_token: args.db["userdeviceid_token"].clone(),
95 userdeviceidtoken_index: args.db["userdeviceidtoken_index"].clone(),
96 userdeviceid_refresh: args.db["userdeviceid_refresh"].clone(),
97 userdeviceid_spentrefresh: args.db["userdeviceid_spentrefresh"].clone(),
98 userdeviceidalgorithm_fallback: args.db["userdeviceidalgorithm_fallback"].clone(),
99 userfilterid_filter: args.db["userfilterid_filter"].clone(),
100 userid_dehydrateddevice: args.db["userid_dehydrateddevice"].clone(),
101 userid_devicelistversion: args.db["userid_devicelistversion"].clone(),
102 userid_erased: args.db["userid_erased"].clone(),
103 userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
104 userid_locked: args.db["userid_locked"].clone(),
105 userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
106 userid_password: args.db["userid_password"].clone(),
107 userid_origin: args.db["userid_origin"].clone(),
108 userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
109 userid_suspended: args.db["userid_suspended"].clone(),
110 userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
111 },
112 }))
113 }
114
115 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
116}
117
118impl Service {
119 pub async fn user_is_ignored(&self, sender_user: &UserId, recipient_user: &UserId) -> bool {
122 self.services
123 .account_data
124 .get_global(recipient_user, GlobalAccountDataEventType::IgnoredUserList)
125 .await
126 .is_ok_and(|ignored: IgnoredUserListEvent| {
127 ignored
128 .content
129 .ignored_users
130 .keys()
131 .any(|blocked_user| blocked_user == sender_user)
132 })
133 }
134
135 pub async fn invites_blocked(&self, user_id: &UserId) -> bool {
137 self.services
138 .account_data
139 .get_global(user_id, GlobalAccountDataEventType::InvitePermissionConfig)
140 .await
141 .is_ok_and(|event: InvitePermissionConfigEvent| {
142 matches!(event.content.default_action, Some(InvitePermissionAction::Block))
143 })
144 }
145
146 #[inline]
152 pub async fn create(
153 &self,
154 user_id: &UserId,
155 password: Option<&str>,
156 origin: Option<&str>,
157 ) -> Result {
158 let origin = origin.unwrap_or("password");
159 self.db.userid_origin.insert(user_id, origin);
160 self.set_password(user_id, password).await
161 }
162
163 pub async fn deactivate_account(&self, user_id: &UserId) -> Result {
165 self.services
167 .oauth
168 .revoke_user_tokens(user_id)
169 .await;
170
171 self.all_device_ids(user_id)
173 .for_each(|device_id| self.remove_device(user_id, device_id))
174 .await;
175
176 self.set_password(user_id, None).await?;
181
182 Ok(())
184 }
185
186 #[inline]
188 pub async fn exists(&self, user_id: &UserId) -> bool {
189 self.db.userid_password.get(user_id).await.is_ok()
190 }
191
192 pub async fn is_deactivated(&self, user_id: &UserId) -> Result<bool> {
194 self.db
195 .userid_password
196 .get(user_id)
197 .map_ok(|val| val.is_empty())
198 .map_err(|_| err!(Request(NotFound("User does not exist."))))
199 .await
200 }
201
202 pub async fn is_active(&self, user_id: &UserId) -> bool {
204 !self.is_deactivated(user_id).await.unwrap_or(true)
205 }
206
207 pub async fn is_active_local(&self, user_id: &UserId) -> bool {
209 self.services.globals.user_is_local(user_id) && self.is_active(user_id).await
210 }
211
212 pub async fn is_suspended(&self, user_id: &UserId) -> bool {
214 self.db
215 .userid_suspended
216 .get(user_id)
217 .await
218 .is_ok()
219 }
220
221 pub async fn is_locked(&self, user_id: &UserId) -> bool {
223 self.db.userid_locked.get(user_id).await.is_ok()
224 }
225
226 pub async fn is_erased(&self, user_id: &UserId) -> bool {
229 self.db.userid_erased.get(user_id).await.is_ok()
230 }
231
232 pub async fn erasure_count(&self, user_id: &UserId) -> Option<PduCount> {
235 self.db
236 .userid_erased
237 .get(user_id)
238 .await
239 .deserialized()
240 .map(PduCount::from_unsigned)
241 .ok()
242 }
243
244 pub async fn get_suspension(&self, user_id: &UserId) -> Option<Moderation> {
246 self.db
247 .userid_suspended
248 .get(user_id)
249 .await
250 .deserialized::<Json<_>>()
251 .map(|Json(m)| m)
252 .ok()
253 }
254
255 pub async fn get_lock(&self, user_id: &UserId) -> Option<Moderation> {
257 self.db
258 .userid_locked
259 .get(user_id)
260 .await
261 .deserialized::<Json<_>>()
262 .map(|Json(m)| m)
263 .ok()
264 }
265
266 pub fn set_suspended(&self, user_id: &UserId, by: &UserId) {
267 let entry = Moderation {
268 when: MilliSecondsSinceUnixEpoch::now(),
269 by: by.to_owned(),
270 };
271
272 self.db
273 .userid_suspended
274 .raw_put(user_id, Json(entry));
275 }
276
277 pub fn clear_suspended(&self, user_id: &UserId) { self.db.userid_suspended.remove(user_id); }
278
279 pub fn set_erased(&self, user_id: &UserId) {
281 let count = self.services.globals.current_count();
282
283 self.db.userid_erased.raw_put(user_id, count);
284 }
285
286 pub fn clear_erased(&self, user_id: &UserId) { self.db.userid_erased.remove(user_id); }
289
290 pub fn set_locked(&self, user_id: &UserId, by: &UserId) {
291 let entry = Moderation {
292 when: MilliSecondsSinceUnixEpoch::now(),
293 by: by.to_owned(),
294 };
295
296 self.db
297 .userid_locked
298 .raw_put(user_id, Json(entry));
299 }
300
301 pub fn clear_locked(&self, user_id: &UserId) { self.db.userid_locked.remove(user_id); }
302
303 #[inline]
305 pub async fn count(&self) -> usize { self.db.userid_password.count().await }
306
307 pub fn stream(&self) -> impl Stream<Item = &UserId> + Send {
309 self.db.userid_password.keys().ignore_err()
310 }
311
312 pub fn list_local_users(&self) -> impl Stream<Item = &UserId> + Send + '_ {
317 self.db
318 .userid_password
319 .stream()
320 .ignore_err()
321 .ready_filter_map(|(u, p): (&UserId, &[u8])| (!p.is_empty()).then_some(u))
322 }
323
324 pub async fn origin(&self, user_id: &UserId) -> Result<String> {
326 self.db
327 .userid_origin
328 .get(user_id)
329 .await
330 .deserialized()
331 }
332
333 pub async fn has_password(&self, user_id: &UserId) -> Result<bool> {
336 self.password_hash(user_id)
337 .map_ok(|value| value != PASSWORD_DISABLED && value != PASSWORD_SENTINEL)
338 .await
339 }
340
341 pub async fn password_hash(&self, user_id: &UserId) -> Result<String> {
343 self.db
344 .userid_password
345 .get(user_id)
346 .await
347 .deserialized()
348 }
349
350 pub async fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result {
352 let allowed_origins = ["password", "sso"];
360 if password.is_some() && password != Some(PASSWORD_SENTINEL) {
361 let origin = self.origin(user_id).await;
362 let origin = origin.as_deref().unwrap_or("password");
363
364 if !allowed_origins.iter().any(is_equal_to!(&origin)) {
365 return Err!(Request(InvalidParam(
366 "Cannot change password of an {origin:?} user."
367 )));
368 }
369 }
370
371 match password.map(utils::hash::password) {
372 | None => {
373 self.db
374 .userid_password
375 .insert(user_id, PASSWORD_DISABLED);
376 },
377 | Some(Ok(_)) if password == Some(PASSWORD_SENTINEL) => {
378 self.db
379 .userid_password
380 .insert(user_id, PASSWORD_SENTINEL);
381 },
382 | Some(Ok(hash)) => {
383 self.db.userid_password.insert(user_id, hash);
384 self.db.userid_origin.insert(user_id, "password");
385 },
386 | Some(Err(e)) => {
387 return Err!(Request(InvalidParam(
388 "Password does not meet the requirements: {e}"
389 )));
390 },
391 }
392
393 Ok(())
394 }
395
396 #[must_use]
398 pub fn create_filter(&self, user_id: &UserId, filter: &FilterDefinition) -> String {
399 let filter_id = utils::random_string(4);
400
401 let key = (user_id, &filter_id);
402 self.db.userfilterid_filter.put(key, Json(filter));
403
404 filter_id
405 }
406
407 pub async fn get_filter(
408 &self,
409 user_id: &UserId,
410 filter_id: &str,
411 ) -> Result<FilterDefinition> {
412 let key = (user_id, filter_id);
413 self.db
414 .userfilterid_filter
415 .qry(&key)
416 .await
417 .deserialized()
418 }
419
420 pub fn create_openid_token(&self, user_id: &UserId, token: &str) -> Result<u64> {
423 use std::num::Saturating as Sat;
424
425 let expires_in = self.services.server.config.openid_token_ttl;
426 let expires_at = Sat(utils::millis_since_unix_epoch()) + Sat(expires_in) * Sat(1000);
427
428 let mut value = expires_at.0.to_be_bytes().to_vec();
429 value.extend_from_slice(user_id.as_bytes());
430
431 self.db
432 .openidtoken_expiresatuserid
433 .insert(token.as_bytes(), value.as_slice());
434
435 Ok(expires_in)
436 }
437
438 pub async fn find_from_openid_token(&self, token: &str) -> Result<OwnedUserId> {
440 let Ok(value) = self
441 .db
442 .openidtoken_expiresatuserid
443 .get(token)
444 .await
445 else {
446 return Err!(Request(Unauthorized("OpenID token is unrecognised")));
447 };
448
449 let (expires_at_bytes, user_bytes) = value.split_at(0_u64.to_be_bytes().len());
450 let expires_at =
451 u64::from_be_bytes(expires_at_bytes.try_into().map_err(|e| {
452 err!(Database("expires_at in openid_userid is invalid u64. {e}"))
453 })?);
454
455 if expires_at < utils::millis_since_unix_epoch() {
456 debug_warn!("OpenID token is expired, removing");
457 self.db
458 .openidtoken_expiresatuserid
459 .remove(token.as_bytes());
460
461 return Err!(Request(Unauthorized("OpenID token is expired")));
462 }
463
464 let user_string = utils::string_from_bytes(user_bytes)
465 .map_err(|e| err!(Database("User ID in openid_userid is invalid unicode. {e}")))?;
466
467 OwnedUserId::try_from(user_string)
468 .map_err(|e| err!(Database("User ID in openid_userid is invalid. {e}")))
469 }
470
471 #[must_use]
474 pub fn create_login_token(&self, user_id: &UserId, token: &str) -> u64 {
475 use std::num::Saturating as Sat;
476
477 let expires_in = self.services.server.config.login_token_ttl;
478 let expires_at = Sat(utils::millis_since_unix_epoch()) + Sat(expires_in);
479
480 let value = (expires_at.0, user_id);
481 self.db
482 .logintoken_expiresatuserid
483 .raw_put(token, value);
484
485 expires_in
486 }
487
488 pub async fn peek_login_token(&self, token: &str) -> Result<OwnedUserId> {
492 let Ok(value) = self
493 .db
494 .logintoken_expiresatuserid
495 .get(token)
496 .await
497 else {
498 return Err!(Request(Forbidden("Login token is unrecognised")));
499 };
500 let (expires_at, user_id): (u64, OwnedUserId) = value.deserialized()?;
501
502 if expires_at < utils::millis_since_unix_epoch() {
503 trace!(?user_id, ?token, "Removing expired login token");
504 self.db.logintoken_expiresatuserid.remove(token);
505 return Err!(Request(Forbidden("Login token is expired")));
506 }
507
508 Ok(user_id)
509 }
510
511 pub async fn find_from_login_token(&self, token: &str) -> Result<OwnedUserId> {
514 let Ok(value) = self
515 .db
516 .logintoken_expiresatuserid
517 .get(token)
518 .await
519 else {
520 return Err!(Request(Forbidden("Login token is unrecognised")));
521 };
522 let (expires_at, user_id): (u64, OwnedUserId) = value.deserialized()?;
523
524 if expires_at < utils::millis_since_unix_epoch() {
525 trace!(?user_id, ?token, "Removing expired login token");
526
527 self.db.logintoken_expiresatuserid.remove(token);
528
529 return Err!(Request(Forbidden("Login token is expired")));
530 }
531
532 self.db.logintoken_expiresatuserid.remove(token);
533
534 Ok(user_id)
535 }
536
537 #[cfg(not(feature = "ldap"))]
538 #[expect(clippy::unused_async)]
539 pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, bool)>> {
540 Err!(FeatureDisabled("ldap"))
541 }
542
543 #[cfg(not(feature = "ldap"))]
544 #[expect(clippy::unused_async)]
545 pub async fn auth_ldap(&self, _user_dn: &str, _password: &str) -> Result {
546 Err!(FeatureDisabled("ldap"))
547 }
548
549 #[cfg(not(feature = "ldap"))]
550 #[must_use]
551 pub fn ldap_bind_dn(&self, _localpart: &str) -> Option<String> { None }
552}