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 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    /// Set whether a [`User`] can request admin
248    ///
249    /// Returns the [`User`] with the new `can_request_admin` value
250    ///
251    /// # Parameters
252    ///
253    /// * `user`: The [`User`] to update
254    ///
255    /// # Errors
256    ///
257    /// Returns [`Self::Error`] if the underlying repository fails
258    async fn set_can_request_admin(
259        &mut self,
260        user: User,
261        can_request_admin: bool,
262    ) -> Result<User, Self::Error>;
263
264    /// List [`User`] with the given filter and pagination
265    ///
266    /// # Parameters
267    ///
268    /// * `filter`: The filter parameters
269    /// * `pagination`: The pagination parameters
270    ///
271    /// # Errors
272    ///
273    /// Returns [`Self::Error`] if the underlying repository fails
274    async fn list(
275        &mut self,
276        filter: UserFilter<'_>,
277        pagination: Pagination,
278    ) -> Result<Page<User>, Self::Error>;
279
280    /// Count the [`User`] with the given filter
281    ///
282    /// # Parameters
283    ///
284    /// * `filter`: The filter parameters
285    ///
286    /// # Errors
287    ///
288    /// Returns [`Self::Error`] if the underlying repository fails
289    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
290
291    /// Acquire a lock on the user to make sure device operations are done in a
292    /// sequential way. The lock is released when the repository is saved or
293    /// rolled back.
294    ///
295    /// # Parameters
296    ///
297    /// * `user`: The user to lock
298    ///
299    /// # Errors
300    ///
301    /// Returns [`Self::Error`] if the underlying repository fails
302    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
303}
304
305repository_impl!(UserRepository:
306    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
307    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
308    async fn add(
309        &mut self,
310        rng: &mut (dyn RngCore + Send),
311        clock: &dyn Clock,
312        username: String,
313    ) -> Result<User, Self::Error>;
314    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
315    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
316    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
317    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
318    async fn set_can_request_admin(
319        &mut self,
320        user: User,
321        can_request_admin: bool,
322    ) -> Result<User, Self::Error>;
323    async fn list(
324        &mut self,
325        filter: UserFilter<'_>,
326        pagination: Pagination,
327    ) -> Result<Page<User>, Self::Error>;
328    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
329    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
330);