]> Untitled Git - lemmy.git/blob - crates/db_queries/src/source/user.rs
User token revocation upon password change
[lemmy.git] / crates / db_queries / src / source / user.rs
1 use crate::{is_email_regex, ApubObject, Crud, ToSafeSettings};
2 use bcrypt::{hash, DEFAULT_COST};
3 use diesel::{dsl::*, result::Error, *};
4 use lemmy_db_schema::{
5   naive_now,
6   schema::user_::dsl::*,
7   source::user::{UserForm, UserSafeSettings, User_},
8   Url,
9 };
10 use lemmy_utils::settings::Settings;
11
12 mod safe_type {
13   use crate::ToSafe;
14   use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
15
16   type Columns = (
17     id,
18     name,
19     preferred_username,
20     avatar,
21     admin,
22     banned,
23     published,
24     updated,
25     matrix_user_id,
26     actor_id,
27     bio,
28     local,
29     banner,
30     deleted,
31     inbox_url,
32     shared_inbox_url,
33   );
34
35   impl ToSafe for User_ {
36     type SafeColumns = Columns;
37     fn safe_columns_tuple() -> Self::SafeColumns {
38       (
39         id,
40         name,
41         preferred_username,
42         avatar,
43         admin,
44         banned,
45         published,
46         updated,
47         matrix_user_id,
48         actor_id,
49         bio,
50         local,
51         banner,
52         deleted,
53         inbox_url,
54         shared_inbox_url,
55       )
56     }
57   }
58 }
59
60 mod safe_type_alias_1 {
61   use crate::ToSafe;
62   use lemmy_db_schema::{schema::user_alias_1::columns::*, source::user::UserAlias1};
63
64   type Columns = (
65     id,
66     name,
67     preferred_username,
68     avatar,
69     admin,
70     banned,
71     published,
72     updated,
73     matrix_user_id,
74     actor_id,
75     bio,
76     local,
77     banner,
78     deleted,
79   );
80
81   impl ToSafe for UserAlias1 {
82     type SafeColumns = Columns;
83     fn safe_columns_tuple() -> Self::SafeColumns {
84       (
85         id,
86         name,
87         preferred_username,
88         avatar,
89         admin,
90         banned,
91         published,
92         updated,
93         matrix_user_id,
94         actor_id,
95         bio,
96         local,
97         banner,
98         deleted,
99       )
100     }
101   }
102 }
103
104 mod safe_type_alias_2 {
105   use crate::ToSafe;
106   use lemmy_db_schema::{schema::user_alias_2::columns::*, source::user::UserAlias2};
107
108   type Columns = (
109     id,
110     name,
111     preferred_username,
112     avatar,
113     admin,
114     banned,
115     published,
116     updated,
117     matrix_user_id,
118     actor_id,
119     bio,
120     local,
121     banner,
122     deleted,
123   );
124
125   impl ToSafe for UserAlias2 {
126     type SafeColumns = Columns;
127     fn safe_columns_tuple() -> Self::SafeColumns {
128       (
129         id,
130         name,
131         preferred_username,
132         avatar,
133         admin,
134         banned,
135         published,
136         updated,
137         matrix_user_id,
138         actor_id,
139         bio,
140         local,
141         banner,
142         deleted,
143       )
144     }
145   }
146 }
147
148 mod safe_settings_type {
149   use crate::ToSafeSettings;
150   use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
151
152   type Columns = (
153     id,
154     name,
155     preferred_username,
156     email,
157     avatar,
158     admin,
159     banned,
160     published,
161     updated,
162     show_nsfw,
163     theme,
164     default_sort_type,
165     default_listing_type,
166     lang,
167     show_avatars,
168     send_notifications_to_email,
169     matrix_user_id,
170     actor_id,
171     bio,
172     local,
173     last_refreshed_at,
174     banner,
175     deleted,
176     validator_time,
177   );
178
179   impl ToSafeSettings for User_ {
180     type SafeSettingsColumns = Columns;
181     fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns {
182       (
183         id,
184         name,
185         preferred_username,
186         email,
187         avatar,
188         admin,
189         banned,
190         published,
191         updated,
192         show_nsfw,
193         theme,
194         default_sort_type,
195         default_listing_type,
196         lang,
197         show_avatars,
198         send_notifications_to_email,
199         matrix_user_id,
200         actor_id,
201         bio,
202         local,
203         last_refreshed_at,
204         banner,
205         deleted,
206         validator_time,
207       )
208     }
209   }
210 }
211
212 pub trait UserSafeSettings_ {
213   fn read(conn: &PgConnection, user_id: i32) -> Result<UserSafeSettings, Error>;
214 }
215
216 impl UserSafeSettings_ for UserSafeSettings {
217   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
218     user_
219       .select(User_::safe_settings_columns_tuple())
220       .filter(deleted.eq(false))
221       .find(user_id)
222       .first::<Self>(conn)
223   }
224 }
225
226 impl Crud<UserForm> for User_ {
227   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
228     user_
229       .filter(deleted.eq(false))
230       .find(user_id)
231       .first::<Self>(conn)
232   }
233   fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
234     diesel::delete(user_.find(user_id)).execute(conn)
235   }
236   fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
237     insert_into(user_).values(form).get_result::<Self>(conn)
238   }
239   fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
240     diesel::update(user_.find(user_id))
241       .set(form)
242       .get_result::<Self>(conn)
243   }
244 }
245
246 impl ApubObject<UserForm> for User_ {
247   fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> {
248     use lemmy_db_schema::schema::user_::dsl::*;
249     user_
250       .filter(deleted.eq(false))
251       .filter(actor_id.eq(object_id))
252       .first::<Self>(conn)
253   }
254
255   fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
256     insert_into(user_)
257       .values(user_form)
258       .on_conflict(actor_id)
259       .do_update()
260       .set(user_form)
261       .get_result::<Self>(conn)
262   }
263 }
264
265 pub trait User {
266   fn register(conn: &PgConnection, form: &UserForm) -> Result<User_, Error>;
267   fn update_password(conn: &PgConnection, user_id: i32, new_password: &str)
268     -> Result<User_, Error>;
269   fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<User_, Error>;
270   fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<User_, Error>;
271   fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<User_, Error>;
272   fn find_by_email_or_username(
273     conn: &PgConnection,
274     username_or_email: &str,
275   ) -> Result<User_, Error>;
276   fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error>;
277   fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error>;
278   fn get_profile_url(&self, hostname: &str) -> String;
279   fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
280   fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
281 }
282
283 impl User for User_ {
284   fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
285     let mut edited_user = form.clone();
286     let password_hash =
287       hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
288     edited_user.password_encrypted = password_hash;
289
290     Self::create(&conn, &edited_user)
291   }
292
293   // TODO do more individual updates like these
294   fn update_password(conn: &PgConnection, user_id: i32, new_password: &str) -> Result<Self, Error> {
295     let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
296
297     diesel::update(user_.find(user_id))
298       .set((
299         password_encrypted.eq(password_hash),
300         updated.eq(naive_now()),
301         validator_time.eq(naive_now()),
302       ))
303       .get_result::<Self>(conn)
304   }
305
306   fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<Self, Error> {
307     user_
308       .filter(local.eq(true))
309       .filter(deleted.eq(false))
310       .filter(name.eq(from_user_name))
311       .first::<Self>(conn)
312   }
313
314   fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<Self, Error> {
315     diesel::update(user_.find(user_id))
316       .set(admin.eq(added))
317       .get_result::<Self>(conn)
318   }
319
320   fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<Self, Error> {
321     diesel::update(user_.find(user_id))
322       .set(banned.eq(ban))
323       .get_result::<Self>(conn)
324   }
325
326   fn find_by_email_or_username(
327     conn: &PgConnection,
328     username_or_email: &str,
329   ) -> Result<Self, Error> {
330     if is_email_regex(username_or_email) {
331       Self::find_by_email(conn, username_or_email)
332     } else {
333       Self::find_by_username(conn, username_or_email)
334     }
335   }
336
337   fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
338     user_
339       .filter(deleted.eq(false))
340       .filter(local.eq(true))
341       .filter(name.ilike(username))
342       .first::<User_>(conn)
343   }
344
345   fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
346     user_
347       .filter(deleted.eq(false))
348       .filter(local.eq(true))
349       .filter(email.eq(from_email))
350       .first::<User_>(conn)
351   }
352
353   fn get_profile_url(&self, hostname: &str) -> String {
354     format!(
355       "{}://{}/u/{}",
356       Settings::get().get_protocol_string(),
357       hostname,
358       self.name
359     )
360   }
361
362   fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
363     diesel::update(user_.find(user_id))
364       .set((last_refreshed_at.eq(naive_now()),))
365       .get_result::<Self>(conn)
366   }
367
368   fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
369     diesel::update(user_.find(user_id))
370       .set((
371         preferred_username.eq::<Option<String>>(None),
372         email.eq::<Option<String>>(None),
373         matrix_user_id.eq::<Option<String>>(None),
374         bio.eq::<Option<String>>(None),
375         deleted.eq(true),
376         updated.eq(naive_now()),
377       ))
378       .get_result::<Self>(conn)
379   }
380 }
381
382 #[cfg(test)]
383 mod tests {
384   use crate::{establish_unpooled_connection, source::user::*, ListingType, SortType};
385   use serial_test::serial;
386
387   #[test]
388   #[serial]
389   fn test_crud() {
390     let conn = establish_unpooled_connection();
391
392     let new_user = UserForm {
393       name: "thommy".into(),
394       preferred_username: None,
395       password_encrypted: "nope".into(),
396       email: None,
397       matrix_user_id: None,
398       avatar: None,
399       banner: None,
400       admin: false,
401       banned: Some(false),
402       published: None,
403       updated: None,
404       show_nsfw: false,
405       theme: "browser".into(),
406       default_sort_type: SortType::Hot as i16,
407       default_listing_type: ListingType::Subscribed as i16,
408       lang: "browser".into(),
409       show_avatars: true,
410       send_notifications_to_email: false,
411       actor_id: None,
412       bio: None,
413       local: true,
414       private_key: None,
415       public_key: None,
416       last_refreshed_at: None,
417       inbox_url: None,
418       shared_inbox_url: None,
419     };
420
421     let inserted_user = User_::create(&conn, &new_user).unwrap();
422
423     let expected_user = User_ {
424       id: inserted_user.id,
425       name: "thommy".into(),
426       preferred_username: None,
427       password_encrypted: "nope".into(),
428       email: None,
429       matrix_user_id: None,
430       avatar: None,
431       banner: None,
432       admin: false,
433       banned: false,
434       published: inserted_user.published,
435       updated: None,
436       show_nsfw: false,
437       theme: "browser".into(),
438       default_sort_type: SortType::Hot as i16,
439       default_listing_type: ListingType::Subscribed as i16,
440       lang: "browser".into(),
441       show_avatars: true,
442       send_notifications_to_email: false,
443       actor_id: inserted_user.actor_id.to_owned(),
444       bio: None,
445       local: true,
446       private_key: None,
447       public_key: None,
448       last_refreshed_at: inserted_user.published,
449       deleted: false,
450       inbox_url: inserted_user.inbox_url.to_owned(),
451       shared_inbox_url: None,
452       validator_time: inserted_user.published,
453     };
454
455     let read_user = User_::read(&conn, inserted_user.id).unwrap();
456     let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
457     let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
458
459     assert_eq!(expected_user, read_user);
460     assert_eq!(expected_user, inserted_user);
461     assert_eq!(expected_user, updated_user);
462     assert_eq!(1, num_deleted);
463   }
464 }