tuwunel_service/threepid/
binding.rs1use futures::{Stream, StreamExt};
8use ruma::{
9 MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
10 thirdparty::{Medium, ThirdPartyIdentifier, ThirdPartyIdentifierInit},
11};
12use tuwunel_core::{Result, implement, result::NotFound, utils::stream::TryIgnore};
13use tuwunel_database::{Cbor, Deserialized, Ignore, Interfix};
14
15use super::Binding;
16
17#[implement(super::Service)]
23#[tracing::instrument(
24 level = "debug",
25 skip(self),
26 fields(
27 %user_id,
28 ),
29)]
30pub async fn put_binding(
31 &self,
32 user_id: &UserId,
33 email_canon: &str,
34 medium: Medium,
35 validated_at: MilliSecondsSinceUnixEpoch,
36 added_at: MilliSecondsSinceUnixEpoch,
37) {
38 let binding = Binding { medium, validated_at, added_at };
39
40 self.db
41 .userid_email
42 .put((user_id, email_canon), Cbor(binding));
43
44 self.db.email_userid.insert(email_canon, user_id);
45}
46
47#[implement(super::Service)]
52#[tracing::instrument(
53 level = "debug",
54 skip(self),
55 fields(
56 %user_id,
57 ),
58)]
59pub fn get_bindings<'a>(
60 &'a self,
61 user_id: &'a UserId,
62) -> impl Stream<Item = ThirdPartyIdentifier> + Send + 'a {
63 type KeyVal = ((Ignore, String), Cbor<Binding>);
64
65 self.db
66 .userid_email
67 .stream_prefix(&(user_id, Interfix))
68 .ignore_err()
69 .map(|((_, address), Cbor(binding)): KeyVal| {
70 ThirdPartyIdentifierInit {
71 address,
72 medium: binding.medium,
73 validated_at: binding.validated_at,
74 added_at: binding.added_at,
75 }
76 .into()
77 })
78}
79
80#[implement(super::Service)]
86#[tracing::instrument(
87 level = "debug",
88 skip(self),
89 fields(
90 %user_id,
91 ),
92)]
93pub async fn del_binding(&self, user_id: &UserId, email_canon: &str) {
94 self.db.userid_email.del((user_id, email_canon));
95
96 if self
97 .user_id_for_email(email_canon)
98 .await
99 .ok()
100 .flatten()
101 .is_some_and(|bound| bound == user_id)
102 {
103 self.db.email_userid.remove(email_canon);
104 }
105}
106
107#[implement(super::Service)]
113#[tracing::instrument(
114 level = "debug",
115 skip(self),
116 fields(
117 %user_id,
118 ),
119)]
120pub async fn bound_elsewhere(&self, user_id: &UserId, email_canon: &str) -> Result<bool> {
121 self.user_id_for_email(email_canon)
122 .await
123 .map(|bound| bound.is_some_and(|bound| bound != user_id))
124}
125
126#[implement(super::Service)]
131#[tracing::instrument(level = "debug", skip(self))]
132pub async fn user_id_for_email(&self, email_canon: &str) -> Result<Option<OwnedUserId>> {
133 self.db
134 .email_userid
135 .get(email_canon)
136 .await
137 .optional()?
138 .map(|handle| handle.deserialized())
139 .transpose()
140}
141
142#[implement(super::Service)]
147#[tracing::instrument(level = "debug", skip(self))]
148pub async fn address_in_use(&self, email_canon: &str) -> bool {
149 self.db
150 .email_userid
151 .get(email_canon)
152 .await
153 .is_ok()
154}