mas_storage/user/
registration_token.rs

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