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);