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