mas_storage/user/registration_token.rs
1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2025 New Vector Ltd.
3//
4// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5// Please see LICENSE files in the repository root for full details.
6
7use async_trait::async_trait;
8use chrono::{DateTime, Utc};
9use mas_data_model::{Clock, UserRegistrationToken};
10use rand_core::Rng;
11use ulid::Ulid;
12
13use crate::repository_impl;
14
15/// A filter to apply when listing [`UserRegistrationToken`]s
16#[derive(Debug, Clone, Copy)]
17pub struct UserRegistrationTokenFilter {
18 now: DateTime<Utc>,
19 has_been_used: Option<bool>,
20 is_revoked: Option<bool>,
21 is_expired: Option<bool>,
22 is_valid: Option<bool>,
23}
24
25impl UserRegistrationTokenFilter {
26 /// Create a new empty filter
27 #[must_use]
28 pub fn new(now: DateTime<Utc>) -> Self {
29 Self {
30 now,
31 has_been_used: None,
32 is_revoked: None,
33 is_expired: None,
34 is_valid: None,
35 }
36 }
37
38 /// Filter by whether the token has been used at least once
39 #[must_use]
40 pub fn with_been_used(mut self, has_been_used: bool) -> Self {
41 self.has_been_used = Some(has_been_used);
42 self
43 }
44
45 /// Filter by revoked status
46 #[must_use]
47 pub fn with_revoked(mut self, is_revoked: bool) -> Self {
48 self.is_revoked = Some(is_revoked);
49 self
50 }
51
52 /// Filter by expired status
53 #[must_use]
54 pub fn with_expired(mut self, is_expired: bool) -> Self {
55 self.is_expired = Some(is_expired);
56 self
57 }
58
59 /// Filter by valid status (meaning: not expired, not revoked, and still
60 /// with uses left)
61 #[must_use]
62 pub fn with_valid(mut self, is_valid: bool) -> Self {
63 self.is_valid = Some(is_valid);
64 self
65 }
66
67 /// Get the used status filter
68 ///
69 /// Returns [`None`] if no used status filter was set
70 #[must_use]
71 pub fn has_been_used(&self) -> Option<bool> {
72 self.has_been_used
73 }
74
75 /// Get the revoked status filter
76 ///
77 /// Returns [`None`] if no revoked status filter was set
78 #[must_use]
79 pub fn is_revoked(&self) -> Option<bool> {
80 self.is_revoked
81 }
82
83 /// Get the expired status filter
84 ///
85 /// Returns [`None`] if no expired status filter was set
86 #[must_use]
87 pub fn is_expired(&self) -> Option<bool> {
88 self.is_expired
89 }
90
91 /// Get the valid status filter
92 ///
93 /// Returns [`None`] if no valid status filter was set
94 #[must_use]
95 pub fn is_valid(&self) -> Option<bool> {
96 self.is_valid
97 }
98
99 /// Get the current time for this filter evaluation
100 #[must_use]
101 pub fn now(&self) -> DateTime<Utc> {
102 self.now
103 }
104}
105
106/// A [`UserRegistrationTokenRepository`] helps interacting with
107/// [`UserRegistrationToken`] saved in the storage backend
108#[async_trait]
109pub trait UserRegistrationTokenRepository: Send + Sync {
110 /// The error type returned by the repository
111 type Error;
112
113 /// Lookup a [`UserRegistrationToken`] by its ID
114 ///
115 /// Returns `None` if no [`UserRegistrationToken`] was found
116 ///
117 /// # Parameters
118 ///
119 /// * `id`: The ID of the [`UserRegistrationToken`] to lookup
120 ///
121 /// # Errors
122 ///
123 /// Returns [`Self::Error`] if the underlying repository fails
124 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserRegistrationToken>, Self::Error>;
125
126 /// Lookup a [`UserRegistrationToken`] by its token string
127 ///
128 /// Returns `None` if no [`UserRegistrationToken`] was found
129 ///
130 /// # Parameters
131 ///
132 /// * `token`: The token string to lookup
133 ///
134 /// # Errors
135 ///
136 /// Returns [`Self::Error`] if the underlying repository fails
137 async fn find_by_token(
138 &mut self,
139 token: &str,
140 ) -> Result<Option<UserRegistrationToken>, Self::Error>;
141
142 /// Create a new [`UserRegistrationToken`]
143 ///
144 /// Returns the newly created [`UserRegistrationToken`]
145 ///
146 /// # Parameters
147 ///
148 /// * `rng`: The random number generator to use
149 /// * `clock`: The clock used to generate timestamps
150 /// * `token`: The token string
151 /// * `usage_limit`: Optional limit on how many times the token can be used
152 /// * `expires_at`: Optional expiration time for the token
153 /// * `username`: Optional username to impose on the registering user
154 /// * `email`: Optional email to impose on the registering user
155 /// * `passwordless`: Whether registrations using this token skip the
156 /// password step
157 /// * `room_id`: Optional room the token invites the registering user to
158 ///
159 /// # Errors
160 ///
161 /// Returns [`Self::Error`] if the underlying repository fails
162 #[expect(clippy::too_many_arguments)]
163 async fn add(
164 &mut self,
165 rng: &mut (dyn Rng + Send),
166 clock: &dyn Clock,
167 token: String,
168 usage_limit: Option<u32>,
169 expires_at: Option<DateTime<Utc>>,
170 username: Option<String>,
171 email: Option<String>,
172 passwordless: bool,
173 room_id: Option<String>,
174 ) -> Result<UserRegistrationToken, Self::Error>;
175
176 /// Increment the usage count of a [`UserRegistrationToken`]
177 ///
178 /// Returns the updated [`UserRegistrationToken`]
179 ///
180 /// # Parameters
181 ///
182 /// * `clock`: The clock used to generate timestamps
183 /// * `token`: The [`UserRegistrationToken`] to update
184 ///
185 /// # Errors
186 ///
187 /// Returns [`Self::Error`] if the underlying repository fails
188 async fn use_token(
189 &mut self,
190 clock: &dyn Clock,
191 token: UserRegistrationToken,
192 ) -> Result<UserRegistrationToken, Self::Error>;
193
194 /// Revoke a [`UserRegistrationToken`]
195 ///
196 /// # Parameters
197 ///
198 /// * `clock`: The clock used to generate timestamps
199 /// * `token`: The [`UserRegistrationToken`] to delete
200 ///
201 /// # Errors
202 ///
203 /// Returns [`Self::Error`] if the underlying repository fails
204 async fn revoke(
205 &mut self,
206 clock: &dyn Clock,
207 token: UserRegistrationToken,
208 ) -> Result<UserRegistrationToken, Self::Error>;
209
210 /// Unrevoke a previously revoked [`UserRegistrationToken`]
211 ///
212 /// # Parameters
213 ///
214 /// * `token`: The [`UserRegistrationToken`] to unrevoke
215 ///
216 /// # Errors
217 ///
218 /// Returns [`Self::Error`] if the underlying repository fails
219 async fn unrevoke(
220 &mut self,
221 token: UserRegistrationToken,
222 ) -> Result<UserRegistrationToken, Self::Error>;
223
224 /// Set the expiration time of a [`UserRegistrationToken`]
225 ///
226 /// # Parameters
227 ///
228 /// * `token`: The [`UserRegistrationToken`] to update
229 /// * `expires_at`: The new expiration time, or `None` to remove the
230 /// expiration
231 ///
232 /// # Errors
233 ///
234 /// Returns [`Self::Error`] if the underlying repository fails
235 async fn set_expiry(
236 &mut self,
237 token: UserRegistrationToken,
238 expires_at: Option<DateTime<Utc>>,
239 ) -> Result<UserRegistrationToken, Self::Error>;
240
241 /// Set the usage limit of a [`UserRegistrationToken`]
242 ///
243 /// # Parameters
244 ///
245 /// * `token`: The [`UserRegistrationToken`] to update
246 /// * `usage_limit`: The new usage limit, or `None` to remove the limit
247 ///
248 /// # Errors
249 ///
250 /// Returns [`Self::Error`] if the underlying repository fails
251 async fn set_usage_limit(
252 &mut self,
253 token: UserRegistrationToken,
254 usage_limit: Option<u32>,
255 ) -> Result<UserRegistrationToken, Self::Error>;
256
257 /// Set or clear the username imposed by this [`UserRegistrationToken`]
258 ///
259 /// # Parameters
260 ///
261 /// * `token`: The [`UserRegistrationToken`] to update
262 /// * `username`: The new username, or `None` to clear it
263 ///
264 /// # Errors
265 ///
266 /// Returns [`Self::Error`] if the underlying repository fails
267 async fn set_username(
268 &mut self,
269 token: UserRegistrationToken,
270 username: Option<String>,
271 ) -> Result<UserRegistrationToken, Self::Error>;
272
273 /// Set or clear the email imposed by this [`UserRegistrationToken`]
274 ///
275 /// # Parameters
276 ///
277 /// * `token`: The [`UserRegistrationToken`] to update
278 /// * `email`: The new email, or `None` to clear it
279 ///
280 /// # Errors
281 ///
282 /// Returns [`Self::Error`] if the underlying repository fails
283 async fn set_email(
284 &mut self,
285 token: UserRegistrationToken,
286 email: Option<String>,
287 ) -> Result<UserRegistrationToken, Self::Error>;
288
289 /// List [`UserRegistrationToken`]s based on the provided filter
290 ///
291 /// Returns a list of matching [`UserRegistrationToken`]s
292 ///
293 /// # Parameters
294 ///
295 /// * `filter`: The filter to apply
296 /// * `pagination`: The pagination parameters
297 ///
298 /// # Errors
299 ///
300 /// Returns [`Self::Error`] if the underlying repository fails
301 async fn list(
302 &mut self,
303 filter: UserRegistrationTokenFilter,
304 pagination: crate::Pagination,
305 ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
306
307 /// Count [`UserRegistrationToken`]s based on the provided filter
308 ///
309 /// Returns the number of matching [`UserRegistrationToken`]s
310 ///
311 /// # Parameters
312 ///
313 /// * `filter`: The filter to apply
314 ///
315 /// # Errors
316 ///
317 /// Returns [`Self::Error`] if the underlying repository fails
318 async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
319}
320
321repository_impl!(UserRegistrationTokenRepository:
322 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserRegistrationToken>, Self::Error>;
323 async fn find_by_token(&mut self, token: &str) -> Result<Option<UserRegistrationToken>, Self::Error>;
324 async fn add(
325 &mut self,
326 rng: &mut (dyn Rng + Send),
327 clock: &dyn Clock,
328 token: String,
329 usage_limit: Option<u32>,
330 expires_at: Option<DateTime<Utc>>,
331 username: Option<String>,
332 email: Option<String>,
333 passwordless: bool,
334 room_id: Option<String>,
335 ) -> Result<UserRegistrationToken, Self::Error>;
336 async fn use_token(
337 &mut self,
338 clock: &dyn Clock,
339 token: UserRegistrationToken,
340 ) -> Result<UserRegistrationToken, Self::Error>;
341 async fn revoke(
342 &mut self,
343 clock: &dyn Clock,
344 token: UserRegistrationToken,
345 ) -> Result<UserRegistrationToken, Self::Error>;
346 async fn unrevoke(
347 &mut self,
348 token: UserRegistrationToken,
349 ) -> Result<UserRegistrationToken, Self::Error>;
350 async fn set_expiry(
351 &mut self,
352 token: UserRegistrationToken,
353 expires_at: Option<DateTime<Utc>>,
354 ) -> Result<UserRegistrationToken, Self::Error>;
355 async fn set_usage_limit(
356 &mut self,
357 token: UserRegistrationToken,
358 usage_limit: Option<u32>,
359 ) -> Result<UserRegistrationToken, Self::Error>;
360 async fn set_username(
361 &mut self,
362 token: UserRegistrationToken,
363 username: Option<String>,
364 ) -> Result<UserRegistrationToken, Self::Error>;
365 async fn set_email(
366 &mut self,
367 token: UserRegistrationToken,
368 email: Option<String>,
369 ) -> Result<UserRegistrationToken, Self::Error>;
370 async fn list(
371 &mut self,
372 filter: UserRegistrationTokenFilter,
373 pagination: crate::Pagination,
374 ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
375 async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
376);