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    ///
154    /// # Errors
155    ///
156    /// Returns [`Self::Error`] if the underlying repository fails
157    async fn add(
158        &mut self,
159        rng: &mut (dyn Rng + Send),
160        clock: &dyn Clock,
161        token: String,
162        usage_limit: Option<u32>,
163        expires_at: Option<DateTime<Utc>>,
164    ) -> Result<UserRegistrationToken, Self::Error>;
165
166    /// Increment the usage count of a [`UserRegistrationToken`]
167    ///
168    /// Returns the updated [`UserRegistrationToken`]
169    ///
170    /// # Parameters
171    ///
172    /// * `clock`: The clock used to generate timestamps
173    /// * `token`: The [`UserRegistrationToken`] to update
174    ///
175    /// # Errors
176    ///
177    /// Returns [`Self::Error`] if the underlying repository fails
178    async fn use_token(
179        &mut self,
180        clock: &dyn Clock,
181        token: UserRegistrationToken,
182    ) -> Result<UserRegistrationToken, Self::Error>;
183
184    /// Revoke a [`UserRegistrationToken`]
185    ///
186    /// # Parameters
187    ///
188    /// * `clock`: The clock used to generate timestamps
189    /// * `token`: The [`UserRegistrationToken`] to delete
190    ///
191    /// # Errors
192    ///
193    /// Returns [`Self::Error`] if the underlying repository fails
194    async fn revoke(
195        &mut self,
196        clock: &dyn Clock,
197        token: UserRegistrationToken,
198    ) -> Result<UserRegistrationToken, Self::Error>;
199
200    /// Unrevoke a previously revoked [`UserRegistrationToken`]
201    ///
202    /// # Parameters
203    ///
204    /// * `token`: The [`UserRegistrationToken`] to unrevoke
205    ///
206    /// # Errors
207    ///
208    /// Returns [`Self::Error`] if the underlying repository fails
209    async fn unrevoke(
210        &mut self,
211        token: UserRegistrationToken,
212    ) -> Result<UserRegistrationToken, Self::Error>;
213
214    /// Set the expiration time of a [`UserRegistrationToken`]
215    ///
216    /// # Parameters
217    ///
218    /// * `token`: The [`UserRegistrationToken`] to update
219    /// * `expires_at`: The new expiration time, or `None` to remove the
220    ///   expiration
221    ///
222    /// # Errors
223    ///
224    /// Returns [`Self::Error`] if the underlying repository fails
225    async fn set_expiry(
226        &mut self,
227        token: UserRegistrationToken,
228        expires_at: Option<DateTime<Utc>>,
229    ) -> Result<UserRegistrationToken, Self::Error>;
230
231    /// Set the usage limit of a [`UserRegistrationToken`]
232    ///
233    /// # Parameters
234    ///
235    /// * `token`: The [`UserRegistrationToken`] to update
236    /// * `usage_limit`: The new usage limit, or `None` to remove the limit
237    ///
238    /// # Errors
239    ///
240    /// Returns [`Self::Error`] if the underlying repository fails
241    async fn set_usage_limit(
242        &mut self,
243        token: UserRegistrationToken,
244        usage_limit: Option<u32>,
245    ) -> Result<UserRegistrationToken, Self::Error>;
246
247    /// List [`UserRegistrationToken`]s based on the provided filter
248    ///
249    /// Returns a list of matching [`UserRegistrationToken`]s
250    ///
251    /// # Parameters
252    ///
253    /// * `filter`: The filter to apply
254    /// * `pagination`: The pagination parameters
255    ///
256    /// # Errors
257    ///
258    /// Returns [`Self::Error`] if the underlying repository fails
259    async fn list(
260        &mut self,
261        filter: UserRegistrationTokenFilter,
262        pagination: crate::Pagination,
263    ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
264
265    /// Count [`UserRegistrationToken`]s based on the provided filter
266    ///
267    /// Returns the number of matching [`UserRegistrationToken`]s
268    ///
269    /// # Parameters
270    ///
271    /// * `filter`: The filter to apply
272    ///
273    /// # Errors
274    ///
275    /// Returns [`Self::Error`] if the underlying repository fails
276    async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
277}
278
279repository_impl!(UserRegistrationTokenRepository:
280    async fn lookup(&mut self, id: Ulid) -> Result<Option<UserRegistrationToken>, Self::Error>;
281    async fn find_by_token(&mut self, token: &str) -> Result<Option<UserRegistrationToken>, Self::Error>;
282    async fn add(
283        &mut self,
284        rng: &mut (dyn Rng + Send),
285        clock: &dyn Clock,
286        token: String,
287        usage_limit: Option<u32>,
288        expires_at: Option<DateTime<Utc>>,
289    ) -> Result<UserRegistrationToken, Self::Error>;
290    async fn use_token(
291        &mut self,
292        clock: &dyn Clock,
293        token: UserRegistrationToken,
294    ) -> Result<UserRegistrationToken, Self::Error>;
295    async fn revoke(
296        &mut self,
297        clock: &dyn Clock,
298        token: UserRegistrationToken,
299    ) -> Result<UserRegistrationToken, Self::Error>;
300    async fn unrevoke(
301        &mut self,
302        token: UserRegistrationToken,
303    ) -> Result<UserRegistrationToken, Self::Error>;
304    async fn set_expiry(
305        &mut self,
306        token: UserRegistrationToken,
307        expires_at: Option<DateTime<Utc>>,
308    ) -> Result<UserRegistrationToken, Self::Error>;
309    async fn set_usage_limit(
310        &mut self,
311        token: UserRegistrationToken,
312        usage_limit: Option<u32>,
313    ) -> Result<UserRegistrationToken, Self::Error>;
314    async fn list(
315        &mut self,
316        filter: UserRegistrationTokenFilter,
317        pagination: crate::Pagination,
318    ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
319    async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
320);