1mod create;
2mod dehydrated_device;
3pub mod device;
4mod invite_filter;
5mod keys;
6mod ldap;
7mod register;
8mod server_user;
9
10use std::sync::Arc;
11
12use futures::{Stream, StreamExt, TryFutureExt};
13use ruma::{
14 MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
15 api::client::filter::FilterDefinition,
16 events::{
17 GlobalAccountDataEventType,
18 ignored_user_list::{IgnoredUserListEvent, IgnoredUserListEventContent},
19 },
20};
21use serde::{Deserialize, Serialize};
22use tuwunel_core::{
23 Err, Result, debug_warn, err, is_equal_to,
24 matrix::pdu::PduCount,
25 trace,
26 utils::{
27 self, BoolExt, MutexMap, ReadyExt, hash::password as hash_password, result::NotFound,
28 stream::TryIgnore,
29 },
30 warn,
31};
32use tuwunel_database::{Deserialized, Json, Map};
33
34pub use self::{
35 dehydrated_device::DehydratedDevice,
36 invite_filter::InviteFilter,
37 keys::{DeviceListChange, DeviceListRecord, parse_master_key},
38 register::Register,
39 server_user::SERVER_USER_KEY,
40};
41
42pub const PASSWORD_SENTINEL: &str = "*";
43pub const PASSWORD_DISABLED: &str = "";
44
45#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct Moderation {
50 pub when: MilliSecondsSinceUnixEpoch,
51 pub by: OwnedUserId,
52}
53
54pub struct Service {
55 services: Arc<crate::services::OnceServices>,
56 db: Data,
57 device_list_mutex: MutexMap<OwnedUserId, ()>,
58}
59
60struct Data {
61 keychangeid_devicechange: Arc<Map>,
62 keychangeid_userid: Arc<Map>,
63 keyid_key: Arc<Map>,
64 onetimekeyid4225_otk: Option<Arc<Map>>,
65 openidtoken_expiresatuserid: Arc<Map>,
66 logintoken_expiresatuserid: Arc<Map>,
67 todeviceid_events: Arc<Map>,
68 spentrefresh_userdeviceid: Arc<Map>,
69 token_userdeviceid: Arc<Map>,
70 userdeviceid_metadata: Arc<Map>,
71 userdeviceid_token: Arc<Map>,
72 userdeviceidtoken_index: Arc<Map>,
73 userdeviceid_refresh: Arc<Map>,
74 userdeviceid_spentrefresh: Arc<Map>,
75 userdeviceidalgorithm_fallback: Arc<Map>,
76 oidcdevice_userdeviceid: Arc<Map>,
77 oidccskeybypass_userid: Arc<Map>,
78 userfilterid_filter: Arc<Map>,
79 userid_dehydrateddevice: Arc<Map>,
80 userid_devicelistversion: Arc<Map>,
81 userid_erased: Arc<Map>,
82 userid_lastonetimekeyupdate: Arc<Map>,
83 userid_locked: Arc<Map>,
84 userid_masterkeyid: Arc<Map>,
85 userid_password: Arc<Map>,
86 userid_origin: Arc<Map>,
87 userid_selfsigningkeyid: Arc<Map>,
88 userid_suspended: Arc<Map>,
89 userid_usersigningkeyid: Arc<Map>,
90}
91
92impl crate::Service for Service {
93 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
94 Ok(Arc::new(Self {
95 services: args.services.clone(),
96 device_list_mutex: MutexMap::new(),
97 db: Data {
98 keychangeid_devicechange: args.db["keychangeid_devicechange"].clone(),
99 keychangeid_userid: args.db["keychangeid_userid"].clone(),
100 keyid_key: args.db["keyid_key"].clone(),
101 onetimekeyid4225_otk: args.db.get("onetimekeyid4225_otk").ok().cloned(),
102 openidtoken_expiresatuserid: args.db["openidtoken_expiresatuserid"].clone(),
103 logintoken_expiresatuserid: args.db["logintoken_expiresatuserid"].clone(),
104 oidcdevice_userdeviceid: args.db["oidcdevice_userdeviceid"].clone(),
105 oidccskeybypass_userid: args.db["oidccskeybypass_userid"].clone(),
106 todeviceid_events: args.db["todeviceid_events"].clone(),
107 spentrefresh_userdeviceid: args.db["spentrefresh_userdeviceid"].clone(),
108 token_userdeviceid: args.db["token_userdeviceid"].clone(),
109 userdeviceid_metadata: args.db["userdeviceid_metadata"].clone(),
110 userdeviceid_token: args.db["userdeviceid_token"].clone(),
111 userdeviceidtoken_index: args.db["userdeviceidtoken_index"].clone(),
112 userdeviceid_refresh: args.db["userdeviceid_refresh"].clone(),
113 userdeviceid_spentrefresh: args.db["userdeviceid_spentrefresh"].clone(),
114 userdeviceidalgorithm_fallback: args.db["userdeviceidalgorithm_fallback"].clone(),
115 userfilterid_filter: args.db["userfilterid_filter"].clone(),
116 userid_dehydrateddevice: args.db["userid_dehydrateddevice"].clone(),
117 userid_devicelistversion: args.db["userid_devicelistversion"].clone(),
118 userid_erased: args.db["userid_erased"].clone(),
119 userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
120 userid_locked: args.db["userid_locked"].clone(),
121 userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
122 userid_password: args.db["userid_password"].clone(),
123 userid_origin: args.db["userid_origin"].clone(),
124 userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
125 userid_suspended: args.db["userid_suspended"].clone(),
126 userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
127 },
128 }))
129 }
130
131 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
132}
133
134impl Service {
135 pub async fn user_is_ignored(&self, sender_user: &UserId, recipient_user: &UserId) -> bool {
138 self.ignored_users(recipient_user)
139 .await
140 .is_some_and(|ignored| {
141 ignored
142 .ignored_users
143 .keys()
144 .any(|blocked_user| blocked_user == sender_user)
145 })
146 }
147
148 pub async fn ignored_users(&self, user_id: &UserId) -> Option<IgnoredUserListEventContent> {
153 self.services
154 .account_data
155 .get_global(user_id, GlobalAccountDataEventType::IgnoredUserList)
156 .await
157 .map(|ignored: IgnoredUserListEvent| ignored.content)
158 .ok()
159 }
160
161 #[inline]
167 pub async fn create(
168 &self,
169 user_id: &UserId,
170 password: Option<&str>,
171 origin: Option<&str>,
172 ) -> Result {
173 self.check_creation(user_id).await?;
174
175 let origin = origin.unwrap_or("password");
176
177 if password.is_none() {
178 self.create_disabled(user_id, origin);
179 return Ok(());
180 }
181
182 self.db.userid_origin.insert(user_id, origin);
183 self.set_password(user_id, password).await
184 }
185
186 pub async fn check_creation(&self, user_id: &UserId) -> Result {
191 if self.services.globals.user_is_local(user_id)
192 && user_id != self.services.globals.server_user
193 && let Some(admin_room) = self
194 .services
195 .alias
196 .resolve_local_alias(&self.services.admin.admin_alias)
197 .await
198 .optional()?
199 && self
200 .services
201 .state_cache
202 .once_joined(user_id, &admin_room)
203 .await
204 && !self.exists(user_id).await
205 {
206 warn!(%user_id, "Refusing a name previously joined to the admin room");
207 return Err!(Request(UserInUse("User ID is not available.")));
208 }
209
210 Ok(())
211 }
212
213 pub async fn deactivate_account(&self, user_id: &UserId) -> Result {
215 let admin_lock = self.services.admin.lock_admin_room().await;
217
218 if self
222 .services
223 .admin
224 .user_is_last_admin(user_id)
225 .await
226 {
227 return Err!(Request(Forbidden(
228 "Cannot deactivate the last admin of this server. Make another user an admin \
229 first."
230 )));
231 }
232
233 self.all_device_ids(user_id)
235 .for_each(|device_id| self.remove_device(user_id, device_id))
236 .await;
237
238 self.set_password(user_id, None).await?;
243 drop(admin_lock);
244
245 self.services
248 .oauth
249 .revoke_user_tokens(user_id)
250 .await;
251
252 Ok(())
254 }
255
256 #[inline]
258 pub async fn exists(&self, user_id: &UserId) -> bool {
259 self.db.userid_password.get(user_id).await.is_ok()
260 }
261
262 pub async fn is_deactivated(&self, user_id: &UserId) -> Result<bool> {
264 self.db
265 .userid_password
266 .get(user_id)
267 .map_ok(|val| val.is_empty())
268 .map_err(|_| err!(Request(NotFound("User does not exist."))))
269 .await
270 }
271
272 pub async fn is_active(&self, user_id: &UserId) -> bool {
274 !self.is_deactivated(user_id).await.unwrap_or(true)
275 }
276
277 pub async fn is_active_local(&self, user_id: &UserId) -> bool {
279 self.services.globals.user_is_local(user_id) && self.is_active(user_id).await
280 }
281
282 pub async fn check_ldap_login(&self, user_id: &UserId) -> Result {
289 self.deactivated_check(user_id).await
290 }
291
292 pub async fn deactivated_check(&self, user_id: &UserId) -> Result {
298 self.db
299 .userid_password
300 .get(user_id)
301 .map_ok(|password| password.is_empty())
302 .await
303 .optional()?
304 .unwrap_or_default()
305 .is_false()
306 .into_option()
307 .ok_or_else(|| err!(Request(UserDeactivated("This user has been deactivated."))))
308 }
309
310 pub async fn is_suspended(&self, user_id: &UserId) -> bool {
312 self.db
313 .userid_suspended
314 .get(user_id)
315 .await
316 .is_ok()
317 }
318
319 pub async fn locked_check(&self, user_id: &UserId) -> Result {
325 self.is_locked(user_id)
326 .await
327 .is_false()
328 .ok_or_else(|| err!(Request(UserLocked("This account has been locked."))))
329 }
330
331 pub async fn is_locked(&self, user_id: &UserId) -> bool {
337 self.db.userid_locked.get(user_id).await.is_ok()
338 }
339
340 pub async fn is_erased(&self, user_id: &UserId) -> bool {
343 self.db.userid_erased.get(user_id).await.is_ok()
344 }
345
346 pub async fn erasure_count(&self, user_id: &UserId) -> Option<PduCount> {
349 self.db
350 .userid_erased
351 .get(user_id)
352 .await
353 .deserialized()
354 .map(PduCount::from_unsigned)
355 .ok()
356 }
357
358 pub async fn get_suspension(&self, user_id: &UserId) -> Option<Moderation> {
360 self.db
361 .userid_suspended
362 .get(user_id)
363 .await
364 .deserialized::<Json<_>>()
365 .map(|Json(m)| m)
366 .ok()
367 }
368
369 pub async fn get_lock(&self, user_id: &UserId) -> Option<Moderation> {
371 self.db
372 .userid_locked
373 .get(user_id)
374 .await
375 .deserialized::<Json<_>>()
376 .map(|Json(m)| m)
377 .ok()
378 }
379
380 pub fn set_suspended(&self, user_id: &UserId, by: &UserId) {
381 let entry = Moderation {
382 when: MilliSecondsSinceUnixEpoch::now(),
383 by: by.to_owned(),
384 };
385
386 self.db
387 .userid_suspended
388 .raw_put(user_id, Json(entry));
389 }
390
391 pub fn clear_suspended(&self, user_id: &UserId) { self.db.userid_suspended.remove(user_id); }
392
393 pub fn set_erased(&self, user_id: &UserId) {
395 let count = self.services.globals.current_count();
396
397 self.db.userid_erased.raw_put(user_id, count);
398 }
399
400 pub fn clear_erased(&self, user_id: &UserId) { self.db.userid_erased.remove(user_id); }
403
404 pub fn set_locked(&self, user_id: &UserId, by: &UserId) {
405 let entry = Moderation {
406 when: MilliSecondsSinceUnixEpoch::now(),
407 by: by.to_owned(),
408 };
409
410 self.db
411 .userid_locked
412 .raw_put(user_id, Json(entry));
413 }
414
415 pub fn clear_locked(&self, user_id: &UserId) { self.db.userid_locked.remove(user_id); }
416
417 #[inline]
419 pub async fn count(&self) -> usize { self.db.userid_password.count().await }
420
421 #[tracing::instrument(level = "trace", skip_all)]
425 pub async fn is_empty(&self) -> Result<bool> {
426 self.db
427 .userid_password
428 .keys()
429 .take(1)
430 .ready_fold(Ok(true), |_, user: Result<&UserId>| user.map(|_| false))
431 .await
432 }
433
434 pub fn stream(&self) -> impl Stream<Item = &UserId> + Send {
436 self.db.userid_password.keys().ignore_err()
437 }
438
439 pub fn list_local_users(&self) -> impl Stream<Item = &UserId> + Send + '_ {
444 self.db
445 .userid_password
446 .stream()
447 .ignore_err()
448 .ready_filter_map(|(u, p): (&UserId, &[u8])| (!p.is_empty()).then_some(u))
449 }
450
451 pub async fn origin(&self, user_id: &UserId) -> Result<String> {
453 self.db
454 .userid_origin
455 .get(user_id)
456 .await
457 .deserialized()
458 }
459
460 pub async fn has_password(&self, user_id: &UserId) -> Result<bool> {
463 self.password_hash(user_id)
464 .map_ok(|value| is_password_hash(&value))
465 .await
466 }
467
468 pub async fn password_hash(&self, user_id: &UserId) -> Result<String> {
470 self.db
471 .userid_password
472 .get(user_id)
473 .await
474 .deserialized()
475 }
476
477 pub async fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result {
484 let allowed_origins = ["password", "sso"];
492 if password.is_some() && password != Some(PASSWORD_SENTINEL) {
493 let origin = self.origin(user_id).await;
494 let origin = origin.as_deref().unwrap_or("password");
495
496 if !allowed_origins.iter().any(is_equal_to!(&origin)) {
497 return Err!(Request(InvalidParam(
498 "Cannot change password of an {origin:?} user."
499 )));
500 }
501 }
502
503 match password {
504 | None => {
505 self.db
506 .userid_password
507 .insert(user_id, PASSWORD_DISABLED);
508 },
509 | Some(PASSWORD_SENTINEL) => {
510 self.db
511 .userid_password
512 .insert(user_id, PASSWORD_SENTINEL);
513 },
514 | Some(password) => {
515 let cost = self.services.config.password_hash_cost();
516 let hash = hash_password(password, cost).map_err(|e| {
517 err!(Request(InvalidParam("Password does not meet the requirements: {e}")))
518 })?;
519
520 self.db.userid_password.insert(user_id, hash);
521 self.db.userid_origin.insert(user_id, "password");
522 },
523 }
524
525 Ok(())
526 }
527
528 #[must_use]
530 pub fn create_filter(&self, user_id: &UserId, filter: &FilterDefinition) -> String {
531 let filter_id = utils::random_string(4);
532
533 let key = (user_id, &filter_id);
534 self.db.userfilterid_filter.put(key, Json(filter));
535
536 filter_id
537 }
538
539 pub async fn get_filter(
540 &self,
541 user_id: &UserId,
542 filter_id: &str,
543 ) -> Result<FilterDefinition> {
544 let key = (user_id, filter_id);
545 self.db
546 .userfilterid_filter
547 .qry(&key)
548 .await
549 .deserialized()
550 }
551
552 pub fn create_openid_token(&self, user_id: &UserId, token: &str) -> Result<u64> {
555 use std::num::Saturating as Sat;
556
557 let expires_in = self.services.server.config.openid_token_ttl;
558 let expires_at = Sat(utils::millis_since_unix_epoch()) + Sat(expires_in) * Sat(1000);
559
560 let mut value = expires_at.0.to_be_bytes().to_vec();
561 value.extend_from_slice(user_id.as_bytes());
562
563 self.db
564 .openidtoken_expiresatuserid
565 .insert(token.as_bytes(), value.as_slice());
566
567 Ok(expires_in)
568 }
569
570 pub async fn find_from_openid_token(&self, token: &str) -> Result<OwnedUserId> {
572 let Ok(value) = self
573 .db
574 .openidtoken_expiresatuserid
575 .get(token)
576 .await
577 else {
578 return Err!(Request(Unauthorized("OpenID token is unrecognised")));
579 };
580
581 let (expires_at_bytes, user_bytes) = value.split_at(0_u64.to_be_bytes().len());
582 let expires_at =
583 u64::from_be_bytes(expires_at_bytes.try_into().map_err(|e| {
584 err!(Database("expires_at in openid_userid is invalid u64. {e}"))
585 })?);
586
587 if expires_at < utils::millis_since_unix_epoch() {
588 debug_warn!("OpenID token is expired, removing");
589 self.db
590 .openidtoken_expiresatuserid
591 .remove(token.as_bytes());
592
593 return Err!(Request(Unauthorized("OpenID token is expired")));
594 }
595
596 let user_string = utils::string_from_bytes(user_bytes)
597 .map_err(|e| err!(Database("User ID in openid_userid is invalid unicode. {e}")))?;
598
599 OwnedUserId::try_from(user_string)
600 .map_err(|e| err!(Database("User ID in openid_userid is invalid. {e}")))
601 }
602
603 #[must_use]
606 pub fn create_login_token(&self, user_id: &UserId, token: &str) -> u64 {
607 use std::num::Saturating as Sat;
608
609 let expires_in = self.services.server.config.login_token_ttl;
610 let expires_at = Sat(utils::millis_since_unix_epoch()) + Sat(expires_in);
611
612 let value = (expires_at.0, user_id);
613 self.db
614 .logintoken_expiresatuserid
615 .raw_put(token, value);
616
617 expires_in
618 }
619
620 pub async fn peek_login_token(&self, token: &str) -> Result<OwnedUserId> {
624 let Ok(value) = self
625 .db
626 .logintoken_expiresatuserid
627 .get(token)
628 .await
629 else {
630 return Err!(Request(Forbidden("Login token is unrecognised")));
631 };
632 let (expires_at, user_id): (u64, OwnedUserId) = value.deserialized()?;
633
634 if expires_at < utils::millis_since_unix_epoch() {
635 trace!(?user_id, ?token, "Removing expired login token");
636 self.db.logintoken_expiresatuserid.remove(token);
637 return Err!(Request(Forbidden("Login token is expired")));
638 }
639
640 Ok(user_id)
641 }
642
643 pub async fn find_from_login_token(&self, token: &str) -> Result<OwnedUserId> {
646 let Ok(value) = self
647 .db
648 .logintoken_expiresatuserid
649 .get(token)
650 .await
651 else {
652 return Err!(Request(Forbidden("Login token is unrecognised")));
653 };
654 let (expires_at, user_id): (u64, OwnedUserId) = value.deserialized()?;
655
656 if expires_at < utils::millis_since_unix_epoch() {
657 trace!(?user_id, ?token, "Removing expired login token");
658
659 self.db.logintoken_expiresatuserid.remove(token);
660
661 return Err!(Request(Forbidden("Login token is expired")));
662 }
663
664 self.db.logintoken_expiresatuserid.remove(token);
665
666 Ok(user_id)
667 }
668
669 #[cfg(not(feature = "ldap"))]
670 #[expect(clippy::unused_async)]
671 pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, bool)>> {
672 Err!(FeatureDisabled("ldap"))
673 }
674
675 #[cfg(not(feature = "ldap"))]
676 #[expect(clippy::unused_async)]
677 pub async fn auth_ldap(&self, _user_dn: &str, _password: &str) -> Result {
678 Err!(FeatureDisabled("ldap"))
679 }
680
681 #[cfg(not(feature = "ldap"))]
682 #[must_use]
683 pub fn ldap_bind_dn(&self, _localpart: &str) -> Option<String> { None }
684}
685
686#[must_use]
690pub fn is_password_hash(value: &str) -> bool {
691 value != PASSWORD_DISABLED && value != PASSWORD_SENTINEL
692}