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    _phantom: std::marker::PhantomData<&'a ()>,
80}
81
82impl UserFilter<'_> {
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    /// Get the state filter
139    ///
140    /// Returns [`None`] if no state filter was set
141    #[must_use]
142    pub fn state(&self) -> Option<UserState> {
143        self.state
144    }
145
146    /// Get the can request admin filter
147    ///
148    /// Returns [`None`] if no can request admin filter was set
149    #[must_use]
150    pub fn can_request_admin(&self) -> Option<bool> {
151        self.can_request_admin
152    }
153
154    /// Get the is guest filter
155    ///
156    /// Returns [`None`] if no is guest filter was set
157    #[must_use]
158    pub fn is_guest(&self) -> Option<bool> {
159        self.is_guest
160    }
161}
162
163/// A [`UserRepository`] helps interacting with [`User`] saved in the storage
164/// backend
165#[async_trait]
166pub trait UserRepository: Send + Sync {
167    /// The error type returned by the repository
168    type Error;
169
170    /// Lookup a [`User`] by its ID
171    ///
172    /// Returns `None` if no [`User`] was found
173    ///
174    /// # Parameters
175    ///
176    /// * `id`: The ID of the [`User`] to lookup
177    ///
178    /// # Errors
179    ///
180    /// Returns [`Self::Error`] if the underlying repository fails
181    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
182
183    /// Find a [`User`] by its username, in a case-insensitive manner
184    ///
185    /// Returns `None` if no [`User`] was found
186    ///
187    /// # Parameters
188    ///
189    /// * `username`: The username of the [`User`] to lookup
190    ///
191    /// # Errors
192    ///
193    /// Returns [`Self::Error`] if the underlying repository fails
194    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
195
196    /// Create a new [`User`]
197    ///
198    /// Returns the newly created [`User`]
199    ///
200    /// # Parameters
201    ///
202    /// * `rng`: A random number generator to generate the [`User`] ID
203    /// * `clock`: The clock used to generate timestamps
204    /// * `username`: The username of the [`User`]
205    ///
206    /// # Errors
207    ///
208    /// Returns [`Self::Error`] if the underlying repository fails
209    async fn add(
210        &mut self,
211        rng: &mut (dyn RngCore + Send),
212        clock: &dyn Clock,
213        username: String,
214    ) -> Result<User, Self::Error>;
215
216    /// Check if a [`User`] exists
217    ///
218    /// Returns `true` if the [`User`] exists, `false` otherwise
219    ///
220    /// # Parameters
221    ///
222    /// * `username`: The username of the [`User`] to lookup
223    ///
224    /// # Errors
225    ///
226    /// Returns [`Self::Error`] if the underlying repository fails
227    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
228
229    /// Lock a [`User`]
230    ///
231    /// Returns the locked [`User`]
232    ///
233    /// # Parameters
234    ///
235    /// * `clock`: The clock used to generate timestamps
236    /// * `user`: The [`User`] to lock
237    ///
238    /// # Errors
239    ///
240    /// Returns [`Self::Error`] if the underlying repository fails
241    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
242
243    /// Unlock a [`User`]
244    ///
245    /// Returns the unlocked [`User`]
246    ///
247    /// # Parameters
248    ///
249    /// * `user`: The [`User`] to unlock
250    ///
251    /// # Errors
252    ///
253    /// Returns [`Self::Error`] if the underlying repository fails
254    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
255
256    /// Deactivate a [`User`]
257    ///
258    /// Returns the deactivated [`User`]
259    ///
260    /// # Parameters
261    ///
262    /// * `clock`: The clock used to generate timestamps
263    /// * `user`: The [`User`] to deactivate
264    ///
265    /// # Errors
266    ///
267    /// Returns [`Self::Error`] if the underlying repository fails
268    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
269
270    /// Reactivate a [`User`]
271    ///
272    /// Returns the reactivated [`User`]
273    ///
274    /// # Parameters
275    ///
276    /// * `user`: The [`User`] to reactivate
277    ///
278    /// # Errors
279    ///
280    /// Returns [`Self::Error`] if the underlying repository fails
281    async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
282
283    /// Set whether a [`User`] can request admin
284    ///
285    /// Returns the [`User`] with the new `can_request_admin` value
286    ///
287    /// # Parameters
288    ///
289    /// * `user`: The [`User`] to update
290    ///
291    /// # Errors
292    ///
293    /// Returns [`Self::Error`] if the underlying repository fails
294    async fn set_can_request_admin(
295        &mut self,
296        user: User,
297        can_request_admin: bool,
298    ) -> Result<User, Self::Error>;
299
300    /// List [`User`] with the given filter and pagination
301    ///
302    /// # Parameters
303    ///
304    /// * `filter`: The filter parameters
305    /// * `pagination`: The pagination parameters
306    ///
307    /// # Errors
308    ///
309    /// Returns [`Self::Error`] if the underlying repository fails
310    async fn list(
311        &mut self,
312        filter: UserFilter<'_>,
313        pagination: Pagination,
314    ) -> Result<Page<User>, Self::Error>;
315
316    /// Count the [`User`] with the given filter
317    ///
318    /// # Parameters
319    ///
320    /// * `filter`: The filter parameters
321    ///
322    /// # Errors
323    ///
324    /// Returns [`Self::Error`] if the underlying repository fails
325    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
326
327    /// Acquire a lock on the user to make sure device operations are done in a
328    /// sequential way. The lock is released when the repository is saved or
329    /// rolled back.
330    ///
331    /// # Parameters
332    ///
333    /// * `user`: The user to lock
334    ///
335    /// # Errors
336    ///
337    /// Returns [`Self::Error`] if the underlying repository fails
338    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
339}
340
341repository_impl!(UserRepository:
342    async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
343    async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
344    async fn add(
345        &mut self,
346        rng: &mut (dyn RngCore + Send),
347        clock: &dyn Clock,
348        username: String,
349    ) -> Result<User, Self::Error>;
350    async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
351    async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
352    async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
353    async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
354    async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
355    async fn set_can_request_admin(
356        &mut self,
357        user: User,
358        can_request_admin: bool,
359    ) -> Result<User, Self::Error>;
360    async fn list(
361        &mut self,
362        filter: UserFilter<'_>,
363        pagination: Pagination,
364    ) -> Result<Page<User>, Self::Error>;
365    async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
366    async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
367);