mas_storage/user/
mod.rs

1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
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
7//! Repositories to interact with entities related to user accounts
8
9use async_trait::async_trait;
10use mas_data_model::User;
11use rand_core::RngCore;
12use ulid::Ulid;
13
14use crate::{Clock, Page, Pagination, repository_impl};
15
16mod email;
17mod password;
18mod recovery;
19mod registration;
20mod registration_token;
21mod session;
22mod terms;
23
24pub use self::{
25    email::{UserEmailFilter, UserEmailRepository},
26    password::UserPasswordRepository,
27    recovery::UserRecoveryRepository,
28    registration::UserRegistrationRepository,
29    registration_token::{UserRegistrationTokenFilter, UserRegistrationTokenRepository},
30    session::{BrowserSessionFilter, BrowserSessionRepository},
31    terms::UserTermsRepository,
32};
33
34/// The state of a user account
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum UserState {
37    /// The account is deactivated, it has the `deactivated_at` timestamp set
38    Deactivated,
39
40    /// The account is locked, it has the `locked_at` timestamp set
41    Locked,
42
43    /// The account is active
44    Active,
45}
46
47impl UserState {
48    /// Returns `true` if the user state is [`Locked`].
49    ///
50    /// [`Locked`]: UserState::Locked
51    #[must_use]
52    pub fn is_locked(&self) -> bool {
53        matches!(self, Self::Locked)
54    }
55
56    /// Returns `true` if the user state is [`Deactivated`].
57    ///
58    /// [`Deactivated`]: UserState::Deactivated
59    #[must_use]
60    pub fn is_deactivated(&self) -> bool {
61        matches!(self, Self::Deactivated)
62    }
63
64    /// Returns `true` if the user state is [`Active`].
65    ///
66    /// [`Active`]: UserState::Active
67    #[must_use]
68    pub fn is_active(&self) -> bool {
69        matches!(self, Self::Active)
70    }
71}
72
73/// Filter parameters for listing users
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
75pub struct UserFilter<'a> {
76    state: Option<UserState>,
77    can_request_admin: Option<bool>,
78    _phantom: std::marker::PhantomData<&'a ()>,
79}
80
81impl UserFilter<'_> {
82    /// Create a new [`UserFilter`] with default values
83    #[must_use]
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Filter for active users
89    #[must_use]
90    pub fn active_only(mut self) -> Self {
91        self.state = Some(UserState::Active);
92        self
93    }
94
95    /// Filter for locked users
96    #[must_use]
97    pub fn locked_only(mut self) -> Self {
98        self.state = Some(UserState::Locked);
99        self
100    }
101
102    /// Filter for deactivated users
103    #[must_use]
104    pub fn deactivated_only(mut self) -> Self {
105        self.state = Some(UserState::Deactivated);
106        self
107    }
108
109    /// Filter for users that can request admin privileges
110    #[must_use]
111    pub fn can_request_admin_only(mut self) -> Self {
112        self.can_request_admin = Some(true);
113        self
114    }
115
116    /// Filter for users that can't request admin privileges
117    #[must_use]
118    pub fn cannot_request_admin_only(mut self) -> Self {
119        self.can_request_admin = Some(false);
120        self
121    }
122
123    /// Get the state filter
124    ///
125    /// Returns [`None`] if no state filter was set
126    #[must_use]
127    pub fn state(&self) -> Option<UserState> {
128        self.state
129    }
130
131    /// Get the can request admin filter
132    ///
133    /// Returns [`None`] if no can request admin filter was set
134    #[must_use]
135    pub fn can_request_admin(&self) -> Option<bool> {
136        self.can_request_admin
137    }
138}
139
140/// A [`UserRepository`] helps interacting with [`User`] saved in the storage
141/// backend
142#[async_trait]
143pub trait UserRepository: Send + Sync {
144    /// The error type returned by the repository
145    type Error;
146
147    /// Lookup a [`User`] by its ID
148    ///
149    /// Returns `None` if no [`User`] was found
150    ///
151    /// # Parameters
152    ///
153    /// * `id`: The ID of the [`User`] to lookup
154    ///
155    /// # Errors
156    ///
157    /// Returns [`Self::Error`] if the underlying repository fails
158    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
159
160    /// Find a [`User`] by its username, in a case-insensitive manner
161    ///
162    /// Returns `None` if no [`User`] was found
163    ///
164    /// # Parameters
165    ///
166    /// * `username`: The username of the [`User`] to lookup
167    ///
168    /// # Errors
169    ///
170    /// Returns [`Self::Error`] if the underlying repository fails
171    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
172
173    /// Create a new [`User`]
174    ///
175    /// Returns the newly created [`User`]
176    ///
177    /// # Parameters
178    ///
179    /// * `rng`: A random number generator to generate the [`User`] ID
180    /// * `clock`: The clock used to generate timestamps
181    /// * `username`: The username of the [`User`]
182    ///
183    /// # Errors
184    ///
185    /// Returns [`Self::Error`] if the underlying repository fails
186    async fn add(
187        &mut self,
188        rng: &mut (dyn RngCore + Send),
189        clock: &dyn Clock,
190        username: String,
191    ) -> Result<User, Self::Error>;
192
193    /// Check if a [`User`] exists
194    ///
195    /// Returns `true` if the [`User`] exists, `false` otherwise
196    ///
197    /// # Parameters
198    ///
199    /// * `username`: The username of the [`User`] to lookup
200    ///
201    /// # Errors
202    ///
203    /// Returns [`Self::Error`] if the underlying repository fails
204    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
205
206    /// Lock a [`User`]
207    ///
208    /// Returns the locked [`User`]
209    ///
210    /// # Parameters
211    ///
212    /// * `clock`: The clock used to generate timestamps
213    /// * `user`: The [`User`] to lock
214    ///
215    /// # Errors
216    ///
217    /// Returns [`Self::Error`] if the underlying repository fails
218    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
219
220    /// Unlock a [`User`]
221    ///
222    /// Returns the unlocked [`User`]
223    ///
224    /// # Parameters
225    ///
226    /// * `user`: The [`User`] to unlock
227    ///
228    /// # Errors
229    ///
230    /// Returns [`Self::Error`] if the underlying repository fails
231    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
232
233    /// Deactivate a [`User`]
234    ///
235    /// Returns the deactivated [`User`]
236    ///
237    /// # Parameters
238    ///
239    /// * `clock`: The clock used to generate timestamps
240    /// * `user`: The [`User`] to deactivate
241    ///
242    /// # Errors
243    ///
244    /// Returns [`Self::Error`] if the underlying repository fails
245    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
246
247    /// Reactivate a [`User`]
248    ///
249    /// Returns the reactivated [`User`]
250    ///
251    /// # Parameters
252    ///
253    /// * `user`: The [`User`] to reactivate
254    ///
255    /// # Errors
256    ///
257    /// Returns [`Self::Error`] if the underlying repository fails
258    async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
259
260    /// Set whether a [`User`] can request admin
261    ///
262    /// Returns the [`User`] with the new `can_request_admin` value
263    ///
264    /// # Parameters
265    ///
266    /// * `user`: The [`User`] to update
267    ///
268    /// # Errors
269    ///
270    /// Returns [`Self::Error`] if the underlying repository fails
271    async fn set_can_request_admin(
272        &mut self,
273        user: User,
274        can_request_admin: bool,
275    ) -> Result<User, Self::Error>;
276
277    /// List [`User`] with the given filter and pagination
278    ///
279    /// # Parameters
280    ///
281    /// * `filter`: The filter parameters
282    /// * `pagination`: The pagination parameters
283    ///
284    /// # Errors
285    ///
286    /// Returns [`Self::Error`] if the underlying repository fails
287    async fn list(
288        &mut self,
289        filter: UserFilter<'_>,
290        pagination: Pagination,
291    ) -> Result<Page<User>, Self::Error>;
292
293    /// Count the [`User`] with the given filter
294    ///
295    /// # Parameters
296    ///
297    /// * `filter`: The filter parameters
298    ///
299    /// # Errors
300    ///
301    /// Returns [`Self::Error`] if the underlying repository fails
302    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
303
304    /// Acquire a lock on the user to make sure device operations are done in a
305    /// sequential way. The lock is released when the repository is saved or
306    /// rolled back.
307    ///
308    /// # Parameters
309    ///
310    /// * `user`: The user to lock
311    ///
312    /// # Errors
313    ///
314    /// Returns [`Self::Error`] if the underlying repository fails
315    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
316}
317
318repository_impl!(UserRepository:
319    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
320    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
321    async fn add(
322        &mut self,
323        rng: &mut (dyn RngCore + Send),
324        clock: &dyn Clock,
325        username: String,
326    ) -> Result<User, Self::Error>;
327    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
328    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
329    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
330    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
331    async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
332    async fn set_can_request_admin(
333        &mut self,
334        user: User,
335        can_request_admin: bool,
336    ) -> Result<User, Self::Error>;
337    async fn list(
338        &mut self,
339        filter: UserFilter<'_>,
340        pagination: Pagination,
341    ) -> Result<Page<User>, Self::Error>;
342    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
343    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
344);