mas_data_model/
users.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
7use std::net::IpAddr;
8
9use chrono::{DateTime, Utc};
10use rand::Rng;
11use serde::Serialize;
12use ulid::Ulid;
13use url::Url;
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
16pub struct MatrixUser {
17    pub mxid: String,
18    pub display_name: Option<String>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
22pub struct User {
23    pub id: Ulid,
24    pub username: String,
25    pub sub: String,
26    pub created_at: DateTime<Utc>,
27    pub locked_at: Option<DateTime<Utc>>,
28    pub deactivated_at: Option<DateTime<Utc>>,
29    pub can_request_admin: bool,
30    pub is_guest: bool,
31}
32
33impl User {
34    /// Returns `true` unless the user is locked or deactivated.
35    #[must_use]
36    pub fn is_valid(&self) -> bool {
37        self.locked_at.is_none() && self.deactivated_at.is_none()
38    }
39
40    /// Returns `true` if the user is a valid actor, for example
41    /// of a personal session.
42    ///
43    /// Currently: this is `true` unless the user is deactivated.
44    ///
45    /// This is a weaker form of validity: `is_valid` always implies
46    /// `is_valid_actor`, but some users (currently: locked users)
47    /// can be valid actors for personal sessions but aren't valid
48    /// except through administrative access.
49    #[must_use]
50    pub fn is_valid_actor(&self) -> bool {
51        self.deactivated_at.is_none()
52    }
53}
54
55impl User {
56    #[doc(hidden)]
57    #[must_use]
58    pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
59        vec![User {
60            id: Ulid::from_datetime_with_source(now.into(), rng),
61            username: "john".to_owned(),
62            sub: "123-456".to_owned(),
63            created_at: now,
64            locked_at: None,
65            deactivated_at: None,
66            can_request_admin: false,
67            is_guest: false,
68        }]
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
73pub struct Password {
74    pub id: Ulid,
75    pub hashed_password: String,
76    pub version: u16,
77    pub upgraded_from_id: Option<Ulid>,
78    pub created_at: DateTime<Utc>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
82pub struct Authentication {
83    pub id: Ulid,
84    pub created_at: DateTime<Utc>,
85    pub authentication_method: AuthenticationMethod,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
89pub enum AuthenticationMethod {
90    Password { user_password_id: Ulid },
91    UpstreamOAuth2 { upstream_oauth2_session_id: Ulid },
92    Unknown,
93}
94
95/// A session to recover a user if they have lost their credentials
96///
97/// For each session intiated, there may be multiple [`UserRecoveryTicket`]s
98/// sent to the user, either because multiple [`User`] have the same email
99/// address, or because the user asked to send the recovery email again.
100#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
101pub struct UserRecoverySession {
102    pub id: Ulid,
103    pub email: String,
104    pub user_agent: String,
105    pub ip_address: Option<IpAddr>,
106    pub locale: String,
107    pub created_at: DateTime<Utc>,
108    pub consumed_at: Option<DateTime<Utc>>,
109}
110
111/// A single recovery ticket for a user recovery session
112///
113/// Whenever a new recovery session is initiated, a new ticket is created for
114/// each email address matching in the database. That ticket is sent by email,
115/// as a link that the user can click to recover their account.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
117pub struct UserRecoveryTicket {
118    pub id: Ulid,
119    pub user_recovery_session_id: Ulid,
120    pub user_email_id: Ulid,
121    pub ticket: String,
122    pub created_at: DateTime<Utc>,
123    pub expires_at: DateTime<Utc>,
124}
125
126impl UserRecoveryTicket {
127    #[must_use]
128    pub fn active(&self, now: DateTime<Utc>) -> bool {
129        now < self.expires_at
130    }
131}
132
133/// A user email authentication session
134#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
135pub struct UserEmailAuthentication {
136    pub id: Ulid,
137    pub user_session_id: Option<Ulid>,
138    pub user_registration_id: Option<Ulid>,
139    pub email: String,
140    pub created_at: DateTime<Utc>,
141    pub completed_at: Option<DateTime<Utc>>,
142}
143
144/// A user email authentication code
145#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
146pub struct UserEmailAuthenticationCode {
147    pub id: Ulid,
148    pub user_email_authentication_id: Ulid,
149    pub code: String,
150    pub created_at: DateTime<Utc>,
151    pub expires_at: DateTime<Utc>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
155pub struct BrowserSession {
156    pub id: Ulid,
157    pub user: User,
158    pub created_at: DateTime<Utc>,
159    pub finished_at: Option<DateTime<Utc>>,
160    pub user_agent: Option<String>,
161    pub last_active_at: Option<DateTime<Utc>>,
162    pub last_active_ip: Option<IpAddr>,
163}
164
165impl BrowserSession {
166    #[must_use]
167    pub fn active(&self) -> bool {
168        self.finished_at.is_none() && self.user.is_valid()
169    }
170}
171
172impl BrowserSession {
173    #[must_use]
174    pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
175        User::samples(now, rng)
176            .into_iter()
177            .map(|user| BrowserSession {
178                id: Ulid::from_datetime_with_source(now.into(), rng),
179                user,
180                created_at: now,
181                finished_at: None,
182                user_agent: Some(
183                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36".to_owned()
184                ),
185                last_active_at: Some(now),
186                last_active_ip: None,
187            })
188            .collect()
189    }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
193pub struct UserEmail {
194    pub id: Ulid,
195    pub user_id: Ulid,
196    pub email: String,
197    pub created_at: DateTime<Utc>,
198}
199
200impl UserEmail {
201    #[must_use]
202    pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
203        vec![
204            Self {
205                id: Ulid::from_datetime_with_source(now.into(), rng),
206                user_id: Ulid::from_datetime_with_source(now.into(), rng),
207                email: "alice@example.com".to_owned(),
208                created_at: now,
209            },
210            Self {
211                id: Ulid::from_datetime_with_source(now.into(), rng),
212                user_id: Ulid::from_datetime_with_source(now.into(), rng),
213                email: "bob@example.com".to_owned(),
214                created_at: now,
215            },
216        ]
217    }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
221pub struct UserRegistrationPassword {
222    pub hashed_password: String,
223    pub version: u16,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
227pub struct UserRegistrationToken {
228    pub id: Ulid,
229    pub token: String,
230    pub usage_limit: Option<u32>,
231    pub times_used: u32,
232    pub created_at: DateTime<Utc>,
233    pub last_used_at: Option<DateTime<Utc>>,
234    pub expires_at: Option<DateTime<Utc>>,
235    pub revoked_at: Option<DateTime<Utc>>,
236}
237
238impl UserRegistrationToken {
239    /// Returns `true` if the token is still valid and can be used
240    #[must_use]
241    pub fn is_valid(&self, now: DateTime<Utc>) -> bool {
242        // Check if revoked
243        if self.revoked_at.is_some() {
244            return false;
245        }
246
247        // Check if expired
248        if let Some(expires_at) = self.expires_at
249            && now >= expires_at
250        {
251            return false;
252        }
253
254        // Check if usage limit exceeded
255        if let Some(usage_limit) = self.usage_limit
256            && self.times_used >= usage_limit
257        {
258            return false;
259        }
260
261        true
262    }
263
264    /// Returns `true` if the token can still be used (not expired and under
265    /// usage limit)
266    #[must_use]
267    pub fn can_be_used(&self, now: DateTime<Utc>) -> bool {
268        self.is_valid(now)
269    }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
273pub struct UserRegistration {
274    pub id: Ulid,
275    pub username: String,
276    pub display_name: Option<String>,
277    pub terms_url: Option<Url>,
278    pub email_authentication_id: Option<Ulid>,
279    pub user_registration_token_id: Option<Ulid>,
280    pub password: Option<UserRegistrationPassword>,
281    pub upstream_oauth_authorization_session_id: Option<Ulid>,
282    pub post_auth_action: Option<serde_json::Value>,
283    pub ip_address: Option<IpAddr>,
284    pub user_agent: Option<String>,
285    pub created_at: DateTime<Utc>,
286    pub completed_at: Option<DateTime<Utc>>,
287}