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::{Clock, User};
11use rand_core::RngCore;
12use ulid::Ulid;
13
14use crate::{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    is_guest: Option<bool>,
79    search: Option<&'a str>,
80}
81
82impl<'a> UserFilter<'a> {
83    /// Create a new [`UserFilter`] with default values
84    #[must_use]
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// Filter for active users
90    #[must_use]
91    pub fn active_only(mut self) -> Self {
92        self.state = Some(UserState::Active);
93        self
94    }
95
96    /// Filter for locked users
97    #[must_use]
98    pub fn locked_only(mut self) -> Self {
99        self.state = Some(UserState::Locked);
100        self
101    }
102
103    /// Filter for deactivated users
104    #[must_use]
105    pub fn deactivated_only(mut self) -> Self {
106        self.state = Some(UserState::Deactivated);
107        self
108    }
109
110    /// Filter for users that can request admin privileges
111    #[must_use]
112    pub fn can_request_admin_only(mut self) -> Self {
113        self.can_request_admin = Some(true);
114        self
115    }
116
117    /// Filter for users that can't request admin privileges
118    #[must_use]
119    pub fn cannot_request_admin_only(mut self) -> Self {
120        self.can_request_admin = Some(false);
121        self
122    }
123
124    /// Filter for guest users
125    #[must_use]
126    pub fn guest_only(mut self) -> Self {
127        self.is_guest = Some(true);
128        self
129    }
130
131    /// Filter for non-guest users
132    #[must_use]
133    pub fn non_guest_only(mut self) -> Self {
134        self.is_guest = Some(false);
135        self
136    }
137
138    /// Filter for users that match the given search string
139    #[must_use]
140    pub fn matching_search(mut self, search: &'a str) -> Self {
141        self.search = Some(search);
142        self
143    }
144
145    /// Get the state filter
146    ///
147    /// Returns [`None`] if no state filter was set
148    #[must_use]
149    pub fn state(&self) -> Option<UserState> {
150        self.state
151    }
152
153    /// Get the can request admin filter
154    ///
155    /// Returns [`None`] if no can request admin filter was set
156    #[must_use]
157    pub fn can_request_admin(&self) -> Option<bool> {
158        self.can_request_admin
159    }
160
161    /// Get the is guest filter
162    ///
163    /// Returns [`None`] if no is guest filter was set
164    #[must_use]
165    pub fn is_guest(&self) -> Option<bool> {
166        self.is_guest
167    }
168
169    /// Get the search filter
170    ///
171    /// Returns [`None`] if no search filter was set
172    #[must_use]
173    pub fn search(&self) -> Option<&'a str> {
174        self.search
175    }
176}
177
178/// A [`UserRepository`] helps interacting with [`User`] saved in the storage
179/// backend
180#[async_trait]
181pub trait UserRepository: Send + Sync {
182    /// The error type returned by the repository
183    type Error;
184
185    /// Lookup a [`User`] by its ID
186    ///
187    /// Returns `None` if no [`User`] was found
188    ///
189    /// # Parameters
190    ///
191    /// * `id`: The ID of the [`User`] to lookup
192    ///
193    /// # Errors
194    ///
195    /// Returns [`Self::Error`] if the underlying repository fails
196    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
197
198    /// Find a [`User`] by its username, in a case-insensitive manner
199    ///
200    /// Returns `None` if no [`User`] was found
201    ///
202    /// # Parameters
203    ///
204    /// * `username`: The username of the [`User`] to lookup
205    ///
206    /// # Errors
207    ///
208    /// Returns [`Self::Error`] if the underlying repository fails
209    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
210
211    /// Create a new [`User`]
212    ///
213    /// Returns the newly created [`User`]
214    ///
215    /// # Parameters
216    ///
217    /// * `rng`: A random number generator to generate the [`User`] ID
218    /// * `clock`: The clock used to generate timestamps
219    /// * `username`: The username of the [`User`]
220    ///
221    /// # Errors
222    ///
223    /// Returns [`Self::Error`] if the underlying repository fails
224    async fn add(
225        &mut self,
226        rng: &mut (dyn RngCore + Send),
227        clock: &dyn Clock,
228        username: String,
229    ) -> Result<User, Self::Error>;
230
231    /// Check if a [`User`] exists
232    ///
233    /// Returns `true` if the [`User`] exists, `false` otherwise
234    ///
235    /// # Parameters
236    ///
237    /// * `username`: The username of the [`User`] to lookup
238    ///
239    /// # Errors
240    ///
241    /// Returns [`Self::Error`] if the underlying repository fails
242    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
243
244    /// Lock a [`User`]
245    ///
246    /// Returns the locked [`User`]
247    ///
248    /// # Parameters
249    ///
250    /// * `clock`: The clock used to generate timestamps
251    /// * `user`: The [`User`] to lock
252    ///
253    /// # Errors
254    ///
255    /// Returns [`Self::Error`] if the underlying repository fails
256    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
257
258    /// Unlock a [`User`]
259    ///
260    /// Returns the unlocked [`User`]
261    ///
262    /// # Parameters
263    ///
264    /// * `user`: The [`User`] to unlock
265    ///
266    /// # Errors
267    ///
268    /// Returns [`Self::Error`] if the underlying repository fails
269    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
270
271    /// Deactivate a [`User`]
272    ///
273    /// Returns the deactivated [`User`]
274    ///
275    /// # Parameters
276    ///
277    /// * `clock`: The clock used to generate timestamps
278    /// * `user`: The [`User`] to deactivate
279    ///
280    /// # Errors
281    ///
282    /// Returns [`Self::Error`] if the underlying repository fails
283    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
284
285    /// Reactivate a [`User`]
286    ///
287    /// Returns the reactivated [`User`]
288    ///
289    /// # Parameters
290    ///
291    /// * `user`: The [`User`] to reactivate
292    ///
293    /// # Errors
294    ///
295    /// Returns [`Self::Error`] if the underlying repository fails
296    async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
297
298    /// Set whether a [`User`] can request admin
299    ///
300    /// Returns the [`User`] with the new `can_request_admin` value
301    ///
302    /// # Parameters
303    ///
304    /// * `user`: The [`User`] to update
305    ///
306    /// # Errors
307    ///
308    /// Returns [`Self::Error`] if the underlying repository fails
309    async fn set_can_request_admin(
310        &mut self,
311        user: User,
312        can_request_admin: bool,
313    ) -> Result<User, Self::Error>;
314
315    /// List [`User`] with the given filter and pagination
316    ///
317    /// # Parameters
318    ///
319    /// * `filter`: The filter parameters
320    /// * `pagination`: The pagination parameters
321    ///
322    /// # Errors
323    ///
324    /// Returns [`Self::Error`] if the underlying repository fails
325    async fn list(
326        &mut self,
327        filter: UserFilter<'_>,
328        pagination: Pagination,
329    ) -> Result<Page<User>, Self::Error>;
330
331    /// Count the [`User`] with the given filter
332    ///
333    /// # Parameters
334    ///
335    /// * `filter`: The filter parameters
336    ///
337    /// # Errors
338    ///
339    /// Returns [`Self::Error`] if the underlying repository fails
340    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
341
342    /// Acquire a lock on the user to make sure device operations are done in a
343    /// sequential way. The lock is released when the repository is saved or
344    /// rolled back.
345    ///
346    /// # Parameters
347    ///
348    /// * `user`: The user to lock
349    ///
350    /// # Errors
351    ///
352    /// Returns [`Self::Error`] if the underlying repository fails
353    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
354}
355
356repository_impl!(UserRepository:
357    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
358    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
359    async fn add(
360        &mut self,
361        rng: &mut (dyn RngCore + Send),
362        clock: &dyn Clock,
363        username: String,
364    ) -> Result<User, Self::Error>;
365    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
366    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
367    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
368    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
369    async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
370    async fn set_can_request_admin(
371        &mut self,
372        user: User,
373        can_request_admin: bool,
374    ) -> Result<User, Self::Error>;
375    async fn list(
376        &mut self,
377        filter: UserFilter<'_>,
378        pagination: Pagination,
379    ) -> Result<Page<User>, Self::Error>;
380    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
381    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
382);