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    /// * `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);