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