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