palpo_data/user/
mod.rs

1mod device;
2pub use device::*;
3mod password;
4pub use password::*;
5mod profile;
6pub use profile::*;
7mod filter;
8pub use filter::*;
9mod refresh_token;
10pub use refresh_token::*;
11mod data;
12pub use data::*;
13pub mod key;
14pub mod pusher;
15// pub mod push_rule;
16pub use key::*;
17pub mod key_backup;
18pub mod session;
19pub use key_backup::*;
20pub use session::*;
21pub mod presence;
22pub use presence::*;
23
24use std::collections::BTreeMap;
25use std::mem;
26use std::sync::{Arc, LazyLock, Mutex};
27
28use diesel::dsl::count_distinct;
29use diesel::prelude::*;
30
31use crate::core::client::sync_events;
32use crate::core::events::ignored_user_list::IgnoredUserListEvent;
33use crate::core::events::{AnyStrippedStateEvent, GlobalAccountDataEventType};
34use crate::core::identifiers::*;
35use crate::core::serde::{JsonValue, RawJson};
36use crate::core::Seqnum;
37use crate::core::{OwnedMxcUri, UnixMillis};
38use crate::schema::*;
39use crate::{connect, diesel_exists, DataError, DataResult};
40
41#[derive(Insertable, Identifiable, Queryable, Debug, Clone)]
42#[diesel(table_name = users)]
43pub struct DbUser {
44    pub id: OwnedUserId,
45    pub ty: Option<String>,
46    pub is_admin: bool,
47    pub is_guest: bool,
48    pub appservice_id: Option<String>,
49    pub shadow_banned: bool,
50    pub consent_at: Option<UnixMillis>,
51    pub consent_version: Option<String>,
52    pub consent_server_notice_sent: Option<String>,
53    pub approved_at: Option<UnixMillis>,
54    pub approved_by: Option<OwnedUserId>,
55    pub deactivated_at: Option<UnixMillis>,
56    pub deactivated_by: Option<OwnedUserId>,
57    pub locked_at: Option<UnixMillis>,
58    pub locked_by: Option<OwnedUserId>,
59    pub created_at: UnixMillis,
60}
61
62#[derive(Insertable, AsChangeset, Debug, Clone)]
63#[diesel(table_name = users)]
64pub struct NewDbUser {
65    pub id: OwnedUserId,
66    pub ty: Option<String>,
67    pub is_admin: bool,
68    pub is_guest: bool,
69    pub appservice_id: Option<String>,
70    pub created_at: UnixMillis,
71}
72
73impl DbUser {
74    pub fn is_deactivated(&self) -> bool {
75        self.deactivated_at.is_some()
76    }
77}
78
79#[derive(Identifiable, Queryable, Debug, Clone)]
80#[diesel(table_name = user_access_tokens)]
81pub struct DbAccessToken {
82    pub id: i64,
83    pub user_id: OwnedUserId,
84    pub device_id: OwnedDeviceId,
85    pub token: String,
86    pub puppets_user_id: Option<OwnedUserId>,
87    pub last_validated: Option<UnixMillis>,
88    pub refresh_token_id: Option<i64>,
89    pub is_used: bool,
90    pub expired_at: Option<UnixMillis>,
91    pub created_at: UnixMillis,
92}
93#[derive(Insertable, Debug, Clone)]
94#[diesel(table_name = user_access_tokens)]
95pub struct NewDbAccessToken {
96    pub user_id: OwnedUserId,
97    pub device_id: OwnedDeviceId,
98    pub token: String,
99    pub puppets_user_id: Option<OwnedUserId>,
100    pub last_validated: Option<UnixMillis>,
101    pub refresh_token_id: Option<i64>,
102    pub is_used: bool,
103    pub expired_at: Option<UnixMillis>,
104    pub created_at: UnixMillis,
105}
106
107impl NewDbAccessToken {
108    pub fn new(user_id: OwnedUserId, device_id: OwnedDeviceId, token: String) -> Self {
109        Self {
110            user_id,
111            device_id,
112            token,
113            puppets_user_id: None,
114            last_validated: None,
115            refresh_token_id: None,
116            is_used: false,
117            expired_at: None,
118            created_at: UnixMillis::now(),
119        }
120    }
121}
122
123/// Returns an iterator over all rooms this user joined.
124pub fn joined_rooms(user_id: &UserId, since_sn: Seqnum) -> DataResult<Vec<OwnedRoomId>> {
125    room_users::table
126        .filter(room_users::user_id.eq(user_id))
127        .filter(room_users::membership.eq("join"))
128        .filter(room_users::event_sn.ge(since_sn))
129        .select(room_users::room_id)
130        .load(&mut connect()?)
131        .map_err(Into::into)
132}
133/// Returns an iterator over all rooms a user was invited to.
134pub fn invited_rooms(
135    user_id: &UserId,
136    since_sn: i64,
137) -> DataResult<Vec<(OwnedRoomId, Vec<RawJson<AnyStrippedStateEvent>>)>> {
138    let list = room_users::table
139        .filter(room_users::user_id.eq(user_id))
140        .filter(room_users::membership.eq("invite"))
141        .filter(room_users::event_sn.ge(since_sn))
142        .select((room_users::room_id, room_users::state_data))
143        .load::<(OwnedRoomId, Option<JsonValue>)>(&mut *connect()?)?
144        .into_iter()
145        .filter_map(|(room_id, state_data)| {
146            if let Some(state_data) = state_data
147                .map(|state_data| serde_json::from_value(state_data).ok())
148                .flatten()
149            {
150                Some((room_id, state_data))
151            } else {
152                None
153            }
154        })
155        .collect();
156    Ok(list)
157}
158
159pub fn knocked_rooms(
160    user_id: &UserId,
161    since_sn: i64,
162) -> DataResult<Vec<(OwnedRoomId, Vec<RawJson<AnyStrippedStateEvent>>)>> {
163    let list = room_users::table
164        .filter(room_users::user_id.eq(user_id))
165        .filter(room_users::membership.eq("knock"))
166        .filter(room_users::event_sn.ge(since_sn))
167        .select((room_users::room_id, room_users::state_data))
168        .load::<(OwnedRoomId, Option<JsonValue>)>(&mut *connect()?)?
169        .into_iter()
170        .filter_map(|(room_id, state_data)| {
171            if let Some(state_data) = state_data
172                .map(|state_data| serde_json::from_value(state_data).ok())
173                .flatten()
174            {
175                Some((room_id, state_data))
176            } else {
177                None
178            }
179        })
180        .collect();
181    Ok(list)
182}
183
184/// Check if a user has an account on this homeserver.
185pub fn user_exists(user_id: &UserId) -> DataResult<bool> {
186    let query = users::table.find(user_id);
187    diesel_exists!(query, &mut *connect()?).map_err(Into::into)
188}
189
190pub fn get_user(user_id: &UserId) -> DataResult<Option<DbUser>> {
191    users::table
192        .find(user_id)
193        .first::<DbUser>(&mut *connect()?)
194        .optional()
195        .map_err(Into::into)
196}
197
198/// Returns the number of users registered on this server.
199pub fn count() -> DataResult<u64> {
200    let count = user_passwords::table
201        .select(count_distinct(user_passwords::user_id))
202        .first::<i64>(&mut *connect()?)?;
203    Ok(count as u64)
204}
205
206/// Returns a list of local users as list of usernames.
207///
208/// A user account is considered `local` if the length of it's password is greater then zero.
209pub fn list_local_users() -> DataResult<Vec<OwnedUserId>> {
210    user_passwords::table
211        .select(user_passwords::user_id)
212        .load::<OwnedUserId>(&mut *connect()?)
213        .map_err(Into::into)
214}
215
216/// Returns the display_name of a user on this homeserver.
217pub fn display_name(user_id: &UserId) -> DataResult<Option<String>> {
218    user_profiles::table
219        .filter(user_profiles::user_id.eq(user_id.as_str()))
220        .filter(user_profiles::room_id.is_null())
221        .select(user_profiles::display_name)
222        .first::<Option<String>>(&mut *connect()?)
223        .map_err(Into::into)
224}
225
226pub fn set_display_name(user_id: &UserId, display_name: Option<&str>) -> DataResult<()> {
227    diesel::update(
228        user_profiles::table
229            .filter(user_profiles::user_id.eq(user_id.as_str()))
230            .filter(user_profiles::room_id.is_null()),
231    )
232    .set(user_profiles::display_name.eq(display_name))
233    .execute(&mut connect()?)
234    .map(|_| ())
235    .map_err(Into::into)
236}
237
238/// Get the avatar_url of a user.
239pub fn avatar_url(user_id: &UserId) -> DataResult<Option<OwnedMxcUri>> {
240    user_profiles::table
241        .filter(user_profiles::user_id.eq(user_id.as_str()))
242        .filter(user_profiles::room_id.is_null())
243        .select(user_profiles::avatar_url)
244        .first::<Option<OwnedMxcUri>>(&mut *connect()?)
245        .optional()
246        .map(Option::flatten)
247        .map_err(Into::into)
248}
249
250/// Get the blurhash of a user.
251pub fn blurhash(user_id: &UserId) -> DataResult<Option<String>> {
252    user_profiles::table
253        .filter(user_profiles::user_id.eq(user_id.as_str()))
254        .filter(user_profiles::room_id.is_null())
255        .select(user_profiles::blurhash)
256        .first::<Option<String>>(&mut *connect()?)
257        .optional()
258        .map(Option::flatten)
259        .map_err(Into::into)
260}
261
262/// Ensure that a user only sees signatures from themselves and the target user
263pub fn clean_signatures<F: Fn(&UserId) -> bool>(
264    cross_signing_key: &mut serde_json::Value,
265    sender_id: Option<&UserId>,
266    user_id: &UserId,
267    allowed_signatures: F,
268) -> DataResult<()> {
269    if let Some(signatures) = cross_signing_key.get_mut("signatures").and_then(|v| v.as_object_mut()) {
270        // Don't allocate for the full size of the current signatures, but require
271        // at most one resize if nothing is dropped
272        let new_capacity = signatures.len() / 2;
273        for (user, signature) in mem::replace(signatures, serde_json::Map::with_capacity(new_capacity)) {
274            let sid =
275                <&UserId>::try_from(user.as_str()).map_err(|_| DataError::internal("Invalid user ID in database."))?;
276            if sender_id == Some(user_id) || sid == user_id || allowed_signatures(sid) {
277                signatures.insert(user, signature);
278            }
279        }
280    }
281
282    Ok(())
283}
284
285pub fn deactivate(user_id: &UserId, doer_id: &UserId) -> DataResult<()> {
286    diesel::update(users::table.find(user_id))
287        .set((
288            users::deactivated_at.eq(UnixMillis::now()),
289            users::deactivated_by.eq(doer_id.to_owned()),
290        ))
291        .execute(&mut connect()?)?;
292
293    diesel::delete(user_threepids::table.filter(user_threepids::user_id.eq(user_id))).execute(&mut connect()?)?;
294    diesel::delete(user_access_tokens::table.filter(user_access_tokens::user_id.eq(user_id)))
295        .execute(&mut connect()?)?;
296
297    Ok(())
298}
299
300/// Returns true/false based on whether the recipient/receiving user has
301/// blocked the sender
302pub fn user_is_ignored(sender_id: &UserId, recipient_id: &UserId) -> bool {
303    if let Ok(Some(ignored)) = crate::user::data::get_global_data::<IgnoredUserListEvent>(
304        recipient_id,
305        &GlobalAccountDataEventType::IgnoredUserList.to_string(),
306    ) {
307        ignored
308            .content
309            .ignored_users
310            .keys()
311            .any(|blocked_user| blocked_user == sender_id)
312    } else {
313        false
314    }
315}