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 ///
158 /// # Errors
159 ///
160 /// Returns [`Self::Error`] if the underlying repository fails
161 #[expect(clippy::too_many_arguments)]
162 async fn add(
163 &mut self,
164 rng: &mut (dyn Rng + Send),
165 clock: &dyn Clock,
166 token: String,
167 usage_limit: Option<u32>,
168 expires_at: Option<DateTime<Utc>>,
169 username: Option<String>,
170 email: Option<String>,
171 passwordless: bool,
172 ) -> Result<UserRegistrationToken, Self::Error>;
173
174 /// Increment the usage count of a [`UserRegistrationToken`]
175 ///
176 /// Returns the updated [`UserRegistrationToken`]
177 ///
178 /// # Parameters
179 ///
180 /// * `clock`: The clock used to generate timestamps
181 /// * `token`: The [`UserRegistrationToken`] to update
182 ///
183 /// # Errors
184 ///
185 /// Returns [`Self::Error`] if the underlying repository fails
186 async fn use_token(
187 &mut self,
188 clock: &dyn Clock,
189 token: UserRegistrationToken,
190 ) -> Result<UserRegistrationToken, Self::Error>;
191
192 /// Revoke a [`UserRegistrationToken`]
193 ///
194 /// # Parameters
195 ///
196 /// * `clock`: The clock used to generate timestamps
197 /// * `token`: The [`UserRegistrationToken`] to delete
198 ///
199 /// # Errors
200 ///
201 /// Returns [`Self::Error`] if the underlying repository fails
202 async fn revoke(
203 &mut self,
204 clock: &dyn Clock,
205 token: UserRegistrationToken,
206 ) -> Result<UserRegistrationToken, Self::Error>;
207
208 /// Unrevoke a previously revoked [`UserRegistrationToken`]
209 ///
210 /// # Parameters
211 ///
212 /// * `token`: The [`UserRegistrationToken`] to unrevoke
213 ///
214 /// # Errors
215 ///
216 /// Returns [`Self::Error`] if the underlying repository fails
217 async fn unrevoke(
218 &mut self,
219 token: UserRegistrationToken,
220 ) -> Result<UserRegistrationToken, Self::Error>;
221
222 /// Set the expiration time of a [`UserRegistrationToken`]
223 ///
224 /// # Parameters
225 ///
226 /// * `token`: The [`UserRegistrationToken`] to update
227 /// * `expires_at`: The new expiration time, or `None` to remove the
228 /// expiration
229 ///
230 /// # Errors
231 ///
232 /// Returns [`Self::Error`] if the underlying repository fails
233 async fn set_expiry(
234 &mut self,
235 token: UserRegistrationToken,
236 expires_at: Option<DateTime<Utc>>,
237 ) -> Result<UserRegistrationToken, Self::Error>;
238
239 /// Set the usage limit of a [`UserRegistrationToken`]
240 ///
241 /// # Parameters
242 ///
243 /// * `token`: The [`UserRegistrationToken`] to update
244 /// * `usage_limit`: The new usage limit, or `None` to remove the limit
245 ///
246 /// # Errors
247 ///
248 /// Returns [`Self::Error`] if the underlying repository fails
249 async fn set_usage_limit(
250 &mut self,
251 token: UserRegistrationToken,
252 usage_limit: Option<u32>,
253 ) -> Result<UserRegistrationToken, Self::Error>;
254
255 /// Set or clear the username imposed by this [`UserRegistrationToken`]
256 ///
257 /// # Parameters
258 ///
259 /// * `token`: The [`UserRegistrationToken`] to update
260 /// * `username`: The new username, or `None` to clear it
261 ///
262 /// # Errors
263 ///
264 /// Returns [`Self::Error`] if the underlying repository fails
265 async fn set_username(
266 &mut self,
267 token: UserRegistrationToken,
268 username: Option<String>,
269 ) -> Result<UserRegistrationToken, Self::Error>;
270
271 /// Set or clear the email imposed by this [`UserRegistrationToken`]
272 ///
273 /// # Parameters
274 ///
275 /// * `token`: The [`UserRegistrationToken`] to update
276 /// * `email`: The new email, or `None` to clear it
277 ///
278 /// # Errors
279 ///
280 /// Returns [`Self::Error`] if the underlying repository fails
281 async fn set_email(
282 &mut self,
283 token: UserRegistrationToken,
284 email: Option<String>,
285 ) -> Result<UserRegistrationToken, Self::Error>;
286
287 /// List [`UserRegistrationToken`]s based on the provided filter
288 ///
289 /// Returns a list of matching [`UserRegistrationToken`]s
290 ///
291 /// # Parameters
292 ///
293 /// * `filter`: The filter to apply
294 /// * `pagination`: The pagination parameters
295 ///
296 /// # Errors
297 ///
298 /// Returns [`Self::Error`] if the underlying repository fails
299 async fn list(
300 &mut self,
301 filter: UserRegistrationTokenFilter,
302 pagination: crate::Pagination,
303 ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
304
305 /// Count [`UserRegistrationToken`]s based on the provided filter
306 ///
307 /// Returns the number of matching [`UserRegistrationToken`]s
308 ///
309 /// # Parameters
310 ///
311 /// * `filter`: The filter to apply
312 ///
313 /// # Errors
314 ///
315 /// Returns [`Self::Error`] if the underlying repository fails
316 async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
317}
318
319repository_impl!(UserRegistrationTokenRepository:
320 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserRegistrationToken>, Self::Error>;
321 async fn find_by_token(&mut self, token: &str) -> Result<Option<UserRegistrationToken>, Self::Error>;
322 async fn add(
323 &mut self,
324 rng: &mut (dyn Rng + Send),
325 clock: &dyn Clock,
326 token: String,
327 usage_limit: Option<u32>,
328 expires_at: Option<DateTime<Utc>>,
329 username: Option<String>,
330 email: Option<String>,
331 passwordless: bool,
332 ) -> Result<UserRegistrationToken, Self::Error>;
333 async fn use_token(
334 &mut self,
335 clock: &dyn Clock,
336 token: UserRegistrationToken,
337 ) -> Result<UserRegistrationToken, Self::Error>;
338 async fn revoke(
339 &mut self,
340 clock: &dyn Clock,
341 token: UserRegistrationToken,
342 ) -> Result<UserRegistrationToken, Self::Error>;
343 async fn unrevoke(
344 &mut self,
345 token: UserRegistrationToken,
346 ) -> Result<UserRegistrationToken, Self::Error>;
347 async fn set_expiry(
348 &mut self,
349 token: UserRegistrationToken,
350 expires_at: Option<DateTime<Utc>>,
351 ) -> Result<UserRegistrationToken, Self::Error>;
352 async fn set_usage_limit(
353 &mut self,
354 token: UserRegistrationToken,
355 usage_limit: Option<u32>,
356 ) -> Result<UserRegistrationToken, Self::Error>;
357 async fn set_username(
358 &mut self,
359 token: UserRegistrationToken,
360 username: Option<String>,
361 ) -> Result<UserRegistrationToken, Self::Error>;
362 async fn set_email(
363 &mut self,
364 token: UserRegistrationToken,
365 email: Option<String>,
366 ) -> Result<UserRegistrationToken, Self::Error>;
367 async fn list(
368 &mut self,
369 filter: UserRegistrationTokenFilter,
370 pagination: crate::Pagination,
371 ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
372 async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
373);