mas_data_model/
users.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
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}
25
26impl User {
27    /// Returns `true` unless the user is locked or deactivated.
28    #[must_use]
29    pub fn is_valid(&self) -> bool {
30        self.locked_at.is_none() && self.deactivated_at.is_none()
31    }
32}
33
34impl User {
35    #[doc(hidden)]
36    #[must_use]
37    pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
38        vec![User {
39            id: Ulid::from_datetime_with_source(now.into(), rng),
40            username: "john".to_owned(),
41            sub: "123-456".to_owned(),
42            created_at: now,
43            locked_at: None,
44            deactivated_at: None,
45            can_request_admin: false,
46        }]
47    }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
51pub struct Password {
52    pub id: Ulid,
53    pub hashed_password: String,
54    pub version: u16,
55    pub upgraded_from_id: Option<Ulid>,
56    pub created_at: DateTime<Utc>,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
60pub struct Authentication {
61    pub id: Ulid,
62    pub created_at: DateTime<Utc>,
63    pub authentication_method: AuthenticationMethod,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67pub enum AuthenticationMethod {
68    Password { user_password_id: Ulid },
69    UpstreamOAuth2 { upstream_oauth2_session_id: Ulid },
70    Unknown,
71}
72
73/// A session to recover a user if they have lost their credentials
74///
75/// For each session intiated, there may be multiple [`UserRecoveryTicket`]s
76/// sent to the user, either because multiple [`User`] have the same email
77/// address, or because the user asked to send the recovery email again.
78#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
79pub struct UserRecoverySession {
80    pub id: Ulid,
81    pub email: String,
82    pub user_agent: String,
83    pub ip_address: Option<IpAddr>,
84    pub locale: String,
85    pub created_at: DateTime<Utc>,
86    pub consumed_at: Option<DateTime<Utc>>,
87}
88
89/// A single recovery ticket for a user recovery session
90///
91/// Whenever a new recovery session is initiated, a new ticket is created for
92/// each email address matching in the database. That ticket is sent by email,
93/// as a link that the user can click to recover their account.
94#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
95pub struct UserRecoveryTicket {
96    pub id: Ulid,
97    pub user_recovery_session_id: Ulid,
98    pub user_email_id: Ulid,
99    pub ticket: String,
100    pub created_at: DateTime<Utc>,
101    pub expires_at: DateTime<Utc>,
102}
103
104impl UserRecoveryTicket {
105    #[must_use]
106    pub fn active(&self, now: DateTime<Utc>) -> bool {
107        now < self.expires_at
108    }
109}
110
111/// A user email authentication session
112#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
113pub struct UserEmailAuthentication {
114    pub id: Ulid,
115    pub user_session_id: Option<Ulid>,
116    pub user_registration_id: Option<Ulid>,
117    pub email: String,
118    pub created_at: DateTime<Utc>,
119    pub completed_at: Option<DateTime<Utc>>,
120}
121
122/// A user email authentication code
123#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
124pub struct UserEmailAuthenticationCode {
125    pub id: Ulid,
126    pub user_email_authentication_id: Ulid,
127    pub code: String,
128    pub created_at: DateTime<Utc>,
129    pub expires_at: DateTime<Utc>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
133pub struct BrowserSession {
134    pub id: Ulid,
135    pub user: User,
136    pub created_at: DateTime<Utc>,
137    pub finished_at: Option<DateTime<Utc>>,
138    pub user_agent: Option<String>,
139    pub last_active_at: Option<DateTime<Utc>>,
140    pub last_active_ip: Option<IpAddr>,
141}
142
143impl BrowserSession {
144    #[must_use]
145    pub fn active(&self) -> bool {
146        self.finished_at.is_none() && self.user.is_valid()
147    }
148}
149
150impl BrowserSession {
151    #[must_use]
152    pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
153        User::samples(now, rng)
154            .into_iter()
155            .map(|user| BrowserSession {
156                id: Ulid::from_datetime_with_source(now.into(), rng),
157                user,
158                created_at: now,
159                finished_at: None,
160                user_agent: Some(
161                    "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()
162                ),
163                last_active_at: Some(now),
164                last_active_ip: None,
165            })
166            .collect()
167    }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
171pub struct UserEmail {
172    pub id: Ulid,
173    pub user_id: Ulid,
174    pub email: String,
175    pub created_at: DateTime<Utc>,
176}
177
178impl UserEmail {
179    #[must_use]
180    pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
181        vec![
182            Self {
183                id: Ulid::from_datetime_with_source(now.into(), rng),
184                user_id: Ulid::from_datetime_with_source(now.into(), rng),
185                email: "alice@example.com".to_owned(),
186                created_at: now,
187            },
188            Self {
189                id: Ulid::from_datetime_with_source(now.into(), rng),
190                user_id: Ulid::from_datetime_with_source(now.into(), rng),
191                email: "bob@example.com".to_owned(),
192                created_at: now,
193            },
194        ]
195    }
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
199pub struct UserRegistrationPassword {
200    pub hashed_password: String,
201    pub version: u16,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
205pub struct UserRegistration {
206    pub id: Ulid,
207    pub username: String,
208    pub display_name: Option<String>,
209    pub terms_url: Option<Url>,
210    pub email_authentication_id: Option<Ulid>,
211    pub password: Option<UserRegistrationPassword>,
212    pub post_auth_action: Option<serde_json::Value>,
213    pub ip_address: Option<IpAddr>,
214    pub user_agent: Option<String>,
215    pub created_at: DateTime<Utc>,
216    pub completed_at: Option<DateTime<Utc>>,
217}