1use std::{
2 net::IpAddr,
3 sync::Arc,
4 time::{Duration, SystemTime},
5};
6
7use futures::{FutureExt, Stream, StreamExt, future::join};
8use ruma::{
9 DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, UserId,
10 api::client::device::Device, events::AnyToDeviceEvent, serde::Raw,
11};
12use serde_json::json;
13use tuwunel_core::{
14 Err, Result, at, implement, trace,
15 utils::{
16 self, BoolExt, ReadyExt, random_string,
17 stream::{IterStream, TryIgnore},
18 string::to_small_string,
19 time::{
20 duration_since_epoch, timepoint_from_epoch, timepoint_from_now, timepoint_has_passed,
21 },
22 },
23};
24use tuwunel_database::{Cbor, Deserialized, Ignore, Interfix, Json, Map, Txn};
25
26const DEVICE_ID_LENGTH: usize = 10;
28
29pub const TOKEN_LENGTH: usize = 32;
31
32#[implement(super::Service)]
34#[tracing::instrument(level = "info", skip(self, access_token))]
35pub async fn create_device(
36 &self,
37 user_id: &UserId,
38 device_id: Option<&DeviceId>,
39 (access_token, expires_in): (Option<&str>, Option<Duration>),
40 refresh_token: Option<&str>,
41 initial_device_display_name: Option<&str>,
42 client_ip: Option<IpAddr>,
43) -> Result<OwnedDeviceId> {
44 let device_id = resolve_device_id(device_id);
45
46 if !self.exists(user_id).await {
47 return Err!(Request(InvalidParam(error!(
48 "Called create_device for non-existent user {user_id}"
49 ))));
50 }
51
52 let notify = true;
53 self.put_device_metadata(user_id, notify, &Device {
54 device_id: device_id.clone(),
55 display_name: initial_device_display_name.map(Into::into),
56 last_seen_ts: Some(MilliSecondsSinceUnixEpoch::now()),
57 last_seen_ip: client_ip.map(to_small_string),
58 });
59
60 if let Some(access_token) = access_token {
61 self.set_access_token(user_id, &device_id, access_token, expires_in, refresh_token)
62 .await?;
63 }
64
65 Ok(device_id)
66}
67
68fn resolve_device_id(device_id: Option<&DeviceId>) -> OwnedDeviceId {
69 device_id
71 .filter(|device_id| !device_id.as_str().is_empty())
72 .map(ToOwned::to_owned)
73 .unwrap_or_else(|| OwnedDeviceId::from(random_string(DEVICE_ID_LENGTH)))
74}
75
76#[implement(super::Service)]
78#[tracing::instrument(level = "info", skip(self))]
79pub async fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) {
80 self.remove_tokens(user_id, device_id).await;
82
83 let prefix = (user_id, device_id, Interfix);
85 self.db
86 .todeviceid_events
87 .keys_prefix_raw(&prefix)
88 .ignore_err()
89 .ready_for_each(|key| self.db.todeviceid_events.remove(key))
90 .await;
91
92 self.services
94 .pusher
95 .get_device_pushkeys(user_id, device_id)
96 .map(Vec::into_iter)
97 .map(IterStream::stream)
98 .flatten_stream()
99 .for_each(async |pushkey| {
100 self.services
101 .pusher
102 .delete_pusher(user_id, &pushkey)
103 .await;
104 })
105 .await;
106
107 self.remove_dehydrated_device(user_id, Some(device_id))
109 .await
110 .ok();
111
112 let prefix = (user_id, device_id, Interfix);
116 self.db
117 .userdeviceidalgorithm_fallback
118 .keys_prefix_raw(&prefix)
119 .ignore_err()
120 .ready_for_each(|key| self.db.userdeviceidalgorithm_fallback.remove(key))
121 .await;
122
123 let event_type = format!("org.matrix.msc3890.local_notification_settings.{device_id}").into();
125 self.services
126 .account_data
127 .delete(None, user_id, event_type)
128 .await
129 .ok();
130
131 let userdeviceid = (user_id, device_id);
132 self.db.userdeviceid_metadata.del(userdeviceid);
133 self.db.oidcdevice_userdeviceid.del(userdeviceid);
134
135 self.mark_device_key_update(user_id).await;
136 increment(&self.db.userid_devicelistversion, user_id.as_bytes());
137}
138
139#[implement(super::Service)]
141pub fn all_device_ids<'a>(
142 &'a self,
143 user_id: &'a UserId,
144) -> impl Stream<Item = &DeviceId> + Send + 'a {
145 let prefix = (user_id, Interfix);
146 self.db
147 .userdeviceid_metadata
148 .keys_prefix(&prefix)
149 .ignore_err()
150 .map(|(_, device_id): (Ignore, &DeviceId)| device_id)
151}
152
153#[implement(super::Service)]
155#[tracing::instrument(level = "trace", skip(self, token))]
156pub async fn find_from_token(
157 &self,
158 token: &str,
159) -> Result<(OwnedUserId, OwnedDeviceId, Option<SystemTime>)> {
160 self.db
161 .token_userdeviceid
162 .get(token)
163 .await
164 .deserialized()
165 .and_then(|(user_id, device_id, expires_at): (_, _, Option<u64>)| {
166 let expires_at = expires_at
167 .map(Duration::from_secs)
168 .map(timepoint_from_epoch)
169 .transpose()?;
170
171 Ok((user_id, device_id, expires_at))
172 })
173}
174
175#[implement(super::Service)]
176#[tracing::instrument(level = "debug", skip(self))]
177pub async fn remove_tokens(&self, user_id: &UserId, device_id: &DeviceId) {
178 let remove_access = self
179 .remove_access_token(user_id, device_id)
180 .map(Result::ok);
181
182 let remove_refresh = self
183 .remove_refresh_token(user_id, device_id)
184 .map(Result::ok);
185
186 join(remove_access, remove_refresh).await;
187}
188
189#[implement(super::Service)]
191#[tracing::instrument(level = "debug", skip(self))]
192pub async fn set_access_token(
193 &self,
194 user_id: &UserId,
195 device_id: &DeviceId,
196 access_token: &str,
197 expires_in: Option<Duration>,
198 refresh_token: Option<&str>,
199) -> Result {
200 assert!(
201 access_token.len() >= TOKEN_LENGTH,
202 "Caller must supply an access_token >= {TOKEN_LENGTH} chars."
203 );
204
205 if let Some(refresh_token) = refresh_token {
206 self.set_refresh_token(user_id, device_id, refresh_token)
207 .await?;
208 }
209
210 let expires_at = expires_in
211 .map(timepoint_from_now)
212 .transpose()?
213 .map(duration_since_epoch)
214 .as_ref()
215 .map(Duration::as_secs);
216
217 let userdeviceid = (user_id, device_id);
218
219 let previous = self
221 .db
222 .userdeviceid_token
223 .qry(&userdeviceid)
224 .await
225 .deserialized::<String>()
226 .ok();
227
228 let mut txn = self.services.db.txn();
229
230 if let Some(previous) = previous.as_deref() {
231 let key = (user_id, device_id, previous);
232
233 txn.put_raw(&self.db.userdeviceidtoken_index, key, []);
234 }
235
236 let key = (user_id, device_id, access_token);
237 let value = (user_id, device_id, expires_at);
238
239 txn.raw_put(&self.db.token_userdeviceid, access_token, value);
240 txn.put_raw(&self.db.userdeviceidtoken_index, key, []);
241 txn.put_raw(&self.db.userdeviceid_token, userdeviceid, access_token);
242
243 txn.execute();
244
245 Ok(())
246}
247
248#[implement(super::Service)]
251pub async fn remove_access_token(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
252 let prefix = (user_id, device_id, Interfix);
253 self.db
254 .userdeviceidtoken_index
255 .keys_prefix(&prefix)
256 .ignore_err()
257 .ready_for_each(|(_, _, token): (Ignore, Ignore, &str)| {
258 self.db.token_userdeviceid.remove(token);
259 self.db
260 .userdeviceidtoken_index
261 .del((user_id, device_id, token));
262 })
263 .await;
264
265 let token = self
267 .db
268 .userdeviceid_token
269 .qry(&(user_id, device_id))
270 .await
271 .deserialized::<String>()
272 .ok();
273
274 let mut txn = self.services.db.txn();
275
276 if let Some(token) = token.as_deref() {
277 txn.del_raw(&self.db.token_userdeviceid, token);
278 }
279
280 txn.del(&self.db.userdeviceid_token, (user_id, device_id));
281
282 txn.execute();
283
284 Ok(())
285}
286
287#[implement(super::Service)]
290pub async fn remove_access_token_value(&self, access_token: &str) {
291 let owner = self
292 .db
293 .token_userdeviceid
294 .get(access_token)
295 .await
296 .deserialized::<(OwnedUserId, OwnedDeviceId, Option<u64>)>()
297 .ok();
298
299 let mut txn = self.services.db.txn();
300
301 if let Some((user_id, device_id, _)) = owner {
302 let user_device_token = (&*user_id, &*device_id, access_token);
303
304 txn.del(&self.db.userdeviceidtoken_index, user_device_token);
305 }
306
307 txn.del_raw(&self.db.token_userdeviceid, access_token);
308
309 txn.execute();
310}
311
312#[implement(super::Service)]
313pub fn generate_access_token(&self, expires: bool) -> (String, Option<Duration>) {
314 let access_token = random_string(TOKEN_LENGTH);
315 let expires_in = expires
316 .then_some(self.services.server.config.access_token_ttl)
317 .map(Duration::from_secs);
318
319 (access_token, expires_in)
320}
321
322#[implement(super::Service)]
324#[tracing::instrument(level = "debug", skip(self))]
325pub async fn set_refresh_token(
326 &self,
327 user_id: &UserId,
328 device_id: &DeviceId,
329 refresh_token: &str,
330) -> Result {
331 debug_assert!(refresh_token.starts_with("refresh_"), "refresh_token missing prefix");
332
333 let config = &self.services.server.config;
334 let ttl = config.refresh_token_ttl;
335 let idle_only = config.refresh_token_idle_only;
336
337 let prior_expires_at: Option<SystemTime> = (ttl != 0 && !idle_only)
339 .then_async(|| self.find_refresh_token_expires_at(user_id, device_id))
340 .await
341 .flatten();
342
343 let spent: Option<String> = self
346 .db
347 .userdeviceid_refresh
348 .qry(&(user_id, device_id))
349 .await
350 .deserialized()
351 .ok();
352
353 self.remove_refresh_token(user_id, device_id)
355 .await
356 .ok();
357
358 let expires_at = match (ttl, prior_expires_at) {
359 | (0, _) => None,
360 | (_, Some(prior)) => Some(prior),
361 | (ttl, None) => Some(timepoint_from_now(Duration::from_secs(ttl))?),
362 };
363
364 let expires_at_secs = expires_at
365 .map(duration_since_epoch)
366 .as_ref()
367 .map(Duration::as_secs);
368
369 let userdeviceid = (user_id, device_id);
370 let value = (user_id, device_id, expires_at_secs);
371 let mut txn = self.services.db.txn();
372
373 txn.raw_put(&self.db.token_userdeviceid, refresh_token, value);
374 txn.put_raw(&self.db.userdeviceid_refresh, userdeviceid, refresh_token);
375
376 if let Some(spent) = spent {
379 let spent_at = duration_since_epoch(SystemTime::now()).as_secs();
380 let value = (user_id, device_id, refresh_token, spent_at);
381
382 txn.raw_put(&self.db.spentrefresh_userdeviceid, &*spent, value);
383 txn.put_raw(&self.db.userdeviceid_spentrefresh, userdeviceid, &*spent);
384 }
385
386 txn.execute();
387
388 Ok(())
389}
390
391#[implement(super::Service)]
395async fn find_refresh_token_expires_at(
396 &self,
397 user_id: &UserId,
398 device_id: &DeviceId,
399) -> Option<SystemTime> {
400 let userdeviceid = (user_id, device_id);
401 let old_token: String = self
402 .db
403 .userdeviceid_refresh
404 .qry(&userdeviceid)
405 .await
406 .deserialized()
407 .ok()?;
408
409 let (_, _, expires_at_secs): (Ignore, Ignore, Option<u64>) = self
410 .db
411 .token_userdeviceid
412 .get(&old_token)
413 .await
414 .deserialized()
415 .ok()?;
416
417 expires_at_secs
418 .map(Duration::from_secs)
419 .map(timepoint_from_epoch)?
420 .ok()
421}
422
423#[implement(super::Service)]
426pub async fn remove_refresh_token(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
427 let userdeviceid = (user_id, device_id);
428 let refresh_token = self
429 .db
430 .userdeviceid_refresh
431 .qry(&userdeviceid)
432 .await;
433
434 let mut txn = self.services.db.txn();
435
436 if let Ok(refresh_token) = refresh_token {
437 txn.del_raw(&self.db.token_userdeviceid, &refresh_token);
438 }
439
440 txn.del(&self.db.userdeviceid_refresh, userdeviceid);
441
442 self.forget_spent_refresh_token(user_id, device_id, &mut txn)
443 .await;
444
445 txn.execute();
446
447 Ok(())
448}
449
450#[implement(super::Service)]
453async fn forget_spent_refresh_token(
454 &self,
455 user_id: &UserId,
456 device_id: &DeviceId,
457 txn: &mut Txn,
458) {
459 let userdeviceid = (user_id, device_id);
460
461 if let Ok(spent) = self
462 .db
463 .userdeviceid_spentrefresh
464 .qry(&userdeviceid)
465 .await
466 {
467 txn.del_raw(&self.db.spentrefresh_userdeviceid, &spent);
468 }
469
470 txn.del(&self.db.userdeviceid_spentrefresh, userdeviceid);
471}
472
473pub enum RefreshToken {
476 Current {
478 user_id: OwnedUserId,
479 device_id: OwnedDeviceId,
480 expires_at: Option<SystemTime>,
481 },
482
483 Replayed {
488 user_id: OwnedUserId,
489 device_id: OwnedDeviceId,
490 current: String,
491 grace: bool,
492 },
493
494 Unknown,
496}
497
498#[implement(super::Service)]
500pub async fn classify_refresh_token(&self, presented: &str) -> RefreshToken {
501 if let Ok((user_id, device_id, expires_at)) = self.find_from_token(presented).await {
504 let current: Option<String> = self
505 .db
506 .userdeviceid_refresh
507 .qry(&(&user_id, &device_id))
508 .await
509 .deserialized()
510 .ok();
511
512 if current.as_deref() == Some(presented) {
513 return RefreshToken::Current { user_id, device_id, expires_at };
514 }
515 }
516
517 let Ok((user_id, device_id, successor, spent_at)) = self
520 .db
521 .spentrefresh_userdeviceid
522 .get(presented)
523 .await
524 .deserialized::<(OwnedUserId, OwnedDeviceId, String, u64)>()
525 else {
526 return RefreshToken::Unknown;
527 };
528
529 let current: Option<String> = self
530 .db
531 .userdeviceid_refresh
532 .qry(&(&user_id, &device_id))
533 .await
534 .deserialized()
535 .ok();
536
537 let grace_window = self
538 .services
539 .server
540 .config
541 .refresh_token_reuse_grace;
542 let elapsed = duration_since_epoch(SystemTime::now())
543 .as_secs()
544 .saturating_sub(spent_at);
545
546 let grace = grace_window != 0
547 && elapsed <= grace_window
548 && current.as_deref() == Some(successor.as_str());
549
550 RefreshToken::Replayed {
551 user_id,
552 device_id,
553 current: successor,
554 grace,
555 }
556}
557
558#[must_use]
559pub fn generate_refresh_token() -> String { format!("refresh_{}", random_string(TOKEN_LENGTH)) }
560
561#[implement(super::Service)]
562pub fn add_to_device_event(
563 &self,
564 sender: &UserId,
565 target_user_id: &UserId,
566 target_device_id: &DeviceId,
567 event_type: &str,
568 content: &serde_json::Value,
569) -> u64 {
570 let count = self.services.globals.next_count();
571
572 let key = (target_user_id, target_device_id, *count);
573 self.db.todeviceid_events.put(
574 key,
575 Json(json!({
576 "type": event_type,
577 "sender": sender,
578 "content": content,
579 })),
580 );
581
582 trace!(
583 %target_user_id,
584 %target_device_id,
585 count = *count,
586 %event_type,
587 %sender,
588 "to_device write",
589 );
590
591 *count
592}
593
594#[implement(super::Service)]
595pub fn get_to_device_events<'a>(
596 &'a self,
597 user_id: &'a UserId,
598 device_id: &'a DeviceId,
599 since: Option<u64>,
600 to: Option<u64>,
601) -> impl Stream<Item = (u64, Raw<AnyToDeviceEvent>)> + Send + 'a {
602 type Key<'a> = (&'a UserId, &'a DeviceId, u64);
603
604 let from = (user_id, device_id, since.map_or(0, |since| since.saturating_add(1)));
605
606 self.db
607 .todeviceid_events
608 .stream_from(&from)
609 .ignore_err()
610 .ready_take_while(move |((user_id_, device_id_, count), _): &(Key<'_>, _)| {
611 user_id == *user_id_ && device_id == *device_id_ && to.is_none_or(|to| *count <= to)
612 })
613 .map(|((_, _, count), event)| (count, event))
614}
615
616#[implement(super::Service)]
617pub async fn remove_to_device_events<Until>(
618 &self,
619 user_id: &UserId,
620 device_id: &DeviceId,
621 until: Until,
622) where
623 Until: Into<Option<u64>> + Send,
624{
625 type Key<'a> = (&'a UserId, &'a DeviceId, u64);
626
627 let until = until.into().unwrap_or(u64::MAX);
628 let from = (user_id, device_id, until);
629 self.db
630 .todeviceid_events
631 .rev_keys_from(&from)
632 .ignore_err()
633 .ready_take_while(move |(user_id_, device_id_, _): &Key<'_>| {
634 user_id == *user_id_ && device_id == *device_id_
635 })
636 .ready_for_each(|key: Key<'_>| {
637 self.db.todeviceid_events.del(key);
638 })
639 .await;
640}
641
642#[implement(super::Service)]
643pub async fn update_device_last_seen(
644 &self,
645 user_id: &UserId,
646 device_id: &DeviceId,
647 last_seen_ip: Option<IpAddr>,
648 last_seen_ts: Option<MilliSecondsSinceUnixEpoch>,
649) -> Result {
650 let mut device = self
651 .get_device_metadata(user_id, device_id)
652 .await?;
653
654 if let Some(last_seen_ip) = last_seen_ip.map(to_small_string) {
655 device.last_seen_ip.replace(last_seen_ip);
656 }
657
658 device
659 .last_seen_ts
660 .replace(last_seen_ts.unwrap_or_else(MilliSecondsSinceUnixEpoch::now));
661
662 self.put_device_metadata(user_id, false, &device);
663
664 Ok(())
665}
666
667#[implement(super::Service)]
668pub fn put_device_metadata(&self, user_id: &UserId, notify: bool, device: &Device) {
669 let key = (user_id, &device.device_id);
670 self.db
671 .userdeviceid_metadata
672 .put(key, Json(device));
673
674 if notify {
675 increment(&self.db.userid_devicelistversion, user_id.as_bytes());
676 }
677}
678
679#[implement(super::Service)]
681pub async fn get_device_metadata(
682 &self,
683 user_id: &UserId,
684 device_id: &DeviceId,
685) -> Result<Device> {
686 self.db
687 .userdeviceid_metadata
688 .qry(&(user_id, device_id))
689 .await
690 .deserialized()
691 .inspect(|device: &Device| {
692 debug_assert_eq!(&device.device_id, device_id, "device_id mismatch");
693 })
694}
695
696#[implement(super::Service)]
697pub async fn device_exists(&self, user_id: &UserId, device_id: &DeviceId) -> bool {
698 self.db
699 .userdeviceid_metadata
700 .contains(&(user_id, device_id))
701 .await
702}
703
704#[implement(super::Service)]
705pub async fn is_oidc_device(&self, user_id: &UserId, device_id: &DeviceId) -> bool {
706 self.db
707 .oidcdevice_userdeviceid
708 .contains(&(user_id, device_id))
709 .await
710}
711
712#[implement(super::Service)]
715pub async fn get_oidc_device_idp(
716 &self,
717 user_id: &UserId,
718 device_id: &DeviceId,
719) -> Option<String> {
720 self.db
721 .oidcdevice_userdeviceid
722 .qry(&(user_id, device_id))
723 .await
724 .deserialized::<Json<String>>()
725 .ok()
726 .map(|Json(idp)| idp)
727 .filter(|idp| !idp.is_empty())
728}
729
730#[implement(super::Service)]
731pub fn mark_oidc_device(&self, user_id: &UserId, device_id: &DeviceId, idp_id: &str) {
732 self.db
733 .oidcdevice_userdeviceid
734 .put((user_id, device_id), Json(idp_id));
735}
736
737#[expect(clippy::must_use_candidate)]
740#[implement(super::Service)]
741pub fn allow_cross_signing_replacement(&self, user_id: &UserId) -> SystemTime {
742 let duration = Duration::from_mins(10);
743 let expires = timepoint_from_now(duration).expect("failed to create timepoint from now");
744
745 self.db
746 .oidccskeybypass_userid
747 .raw_put(user_id, Cbor(expires));
748
749 expires
750}
751
752#[implement(super::Service)]
754pub async fn can_replace_cross_signing_keys(&self, user_id: &UserId) -> bool {
755 let Ok(expires): Result<SystemTime, _> = self
756 .db
757 .oidccskeybypass_userid
758 .get(user_id)
759 .await
760 .deserialized::<Cbor<_>>()
761 .map(at!(0))
762 else {
763 return false;
764 };
765
766 if !timepoint_has_passed(expires) {
767 return true;
768 }
769
770 self.db.oidccskeybypass_userid.remove(user_id);
771 false
772}
773
774#[implement(super::Service)]
775pub async fn get_devicelist_version(&self, user_id: &UserId) -> Result<u64> {
776 self.db
777 .userid_devicelistversion
778 .get(user_id)
779 .await
780 .deserialized()
781}
782
783#[implement(super::Service)]
784pub fn all_devices_metadata<'a>(
785 &'a self,
786 user_id: &'a UserId,
787) -> impl Stream<Item = Device> + Send + 'a {
788 let key = (user_id, Interfix);
789 self.db
790 .userdeviceid_metadata
791 .stream_prefix(&key)
792 .ignore_err()
793 .map(|(_, val): (Ignore, Device)| val)
794}
795
796fn increment(db: &Arc<Map>, key: &[u8]) {
798 let old = db.get_blocking(key);
799 let new = utils::increment(old.ok().as_deref());
800 db.insert(key, new);
801}
802
803#[cfg(test)]
804mod tests {
805 use super::*;
806
807 #[test]
808 fn absent_device_id_is_generated() {
809 let device_id = resolve_device_id(None);
810
811 assert_eq!(device_id.as_str().len(), DEVICE_ID_LENGTH);
812 }
813
814 #[test]
815 fn empty_device_id_is_generated() {
816 let device_id = resolve_device_id(Some("".into()));
817
818 assert_eq!(device_id.as_str().len(), DEVICE_ID_LENGTH);
819 }
820
821 #[test]
822 fn provided_device_id_is_preserved() {
823 let device_id = resolve_device_id(Some("HELLOWORLD".into()));
824
825 assert_eq!(device_id.as_str(), "HELLOWORLD");
826 }
827}