Skip to main content

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);