mas_storage/user/
mod.rs

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