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