mas_storage/user/registration_token.rs
1// Copyright 2025 New Vector Ltd.
2//
3// SPDX-License-Identifier: AGPL-3.0-only
4// Please see LICENSE 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 /// List [`UserRegistrationToken`]s based on the provided filter
200 ///
201 /// Returns a list of matching [`UserRegistrationToken`]s
202 ///
203 /// # Parameters
204 ///
205 /// * `filter`: The filter to apply
206 /// * `pagination`: The pagination parameters
207 ///
208 /// # Errors
209 ///
210 /// Returns [`Self::Error`] if the underlying repository fails
211 async fn list(
212 &mut self,
213 filter: UserRegistrationTokenFilter,
214 pagination: crate::Pagination,
215 ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
216
217 /// Count [`UserRegistrationToken`]s based on the provided filter
218 ///
219 /// Returns the number of matching [`UserRegistrationToken`]s
220 ///
221 /// # Parameters
222 ///
223 /// * `filter`: The filter to apply
224 ///
225 /// # Errors
226 ///
227 /// Returns [`Self::Error`] if the underlying repository fails
228 async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
229}
230
231repository_impl!(UserRegistrationTokenRepository:
232 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserRegistrationToken>, Self::Error>;
233 async fn find_by_token(&mut self, token: &str) -> Result<Option<UserRegistrationToken>, Self::Error>;
234 async fn add(
235 &mut self,
236 rng: &mut (dyn RngCore + Send),
237 clock: &dyn Clock,
238 token: String,
239 usage_limit: Option<u32>,
240 expires_at: Option<DateTime<Utc>>,
241 ) -> Result<UserRegistrationToken, Self::Error>;
242 async fn use_token(
243 &mut self,
244 clock: &dyn Clock,
245 token: UserRegistrationToken,
246 ) -> Result<UserRegistrationToken, Self::Error>;
247 async fn revoke(
248 &mut self,
249 clock: &dyn Clock,
250 token: UserRegistrationToken,
251 ) -> Result<UserRegistrationToken, Self::Error>;
252 async fn list(
253 &mut self,
254 filter: UserRegistrationTokenFilter,
255 pagination: crate::Pagination,
256 ) -> Result<crate::Page<UserRegistrationToken>, Self::Error>;
257 async fn count(&mut self, filter: UserRegistrationTokenFilter) -> Result<usize, Self::Error>;
258);