1use super::common::NAME_REGEX;
2use oiseau::cache::Cache;
3use crate::model::{
4 Error, Result,
5 auth::{Token, User, UserSettings},
6 permissions::{FinePermission, SecondaryPermission},
7 oauth::AuthGrant,
8 moderation::AuditLogEntry,
9 auth::{
10 Achievement, AchievementName, AchievementRarity, Notification, UserConnections,
11 ACHIEVEMENTS, AppliedConfigType,
12 },
13};
14use pathbufd::PathBufD;
15use std::fs::{exists, remove_file};
16use tetratto_shared::{
17 hash::{hash_salted, salt},
18 unix_epoch_timestamp,
19};
20use crate::{auto_method, DataManager};
21use oiseau::{PostgresRow, execute, get, query_row, params};
22
23macro_rules! update_role_fn {
24 ($name:ident, $role_ty:ty, $col:literal) => {
25 pub async fn $name(
26 &self,
27 id: usize,
28 role: $role_ty,
29 user: &User,
30 force: bool,
31 ) -> Result<()> {
32 let other_user = self.get_user_by_id(id).await?;
33
34 if !force {
35 if !user.permissions.check(FinePermission::MANAGE_USERS) {
37 return Err(Error::NotAllowed);
38 }
39
40 if other_user.permissions.check_manager() && !user.permissions.check_admin() {
41 return Err(Error::MiscError(
42 "Cannot manage the role of other managers".to_string(),
43 ));
44 }
45
46 if other_user.permissions == user.permissions {
47 return Err(Error::MiscError(
48 "Cannot manage users of equal level to you".to_string(),
49 ));
50 }
51 }
52
53 let conn = match self.0.connect().await {
55 Ok(c) => c,
56 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
57 };
58
59 let res = execute!(
60 &conn,
61 &format!("UPDATE users SET {} = $1 WHERE id = $2", $col),
62 params![&(role.bits() as i32), &(id as i64)]
63 );
64
65 if let Err(e) = res {
66 return Err(Error::DatabaseError(e.to_string()));
67 }
68
69 self.cache_clear_user(&other_user).await;
70
71 self.create_audit_log_entry(AuditLogEntry::new(
73 user.id,
74 format!(
75 "invoked `{}` with x value `{}` and y value `{}`",
76 $col,
77 other_user.id,
78 role.bits()
79 ),
80 ))
81 .await?;
82
83 Ok(())
85 }
86 };
87}
88
89impl DataManager {
90 pub(crate) fn get_user_from_row(x: &PostgresRow) -> User {
92 User {
93 id: get!(x->0(i64)) as usize,
94 created: get!(x->1(i64)) as usize,
95 username: get!(x->2(String)),
96 password: get!(x->3(String)),
97 salt: get!(x->4(String)),
98 settings: serde_json::from_str(&get!(x->5(String)).to_string()).unwrap(),
99 tokens: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
100 permissions: FinePermission::from_bits(get!(x->7(i32)) as u32).unwrap(),
101 is_verified: get!(x->8(i32)) as i8 == 1,
102 notification_count: {
103 let x = get!(x->9(i32)) as usize;
104 if x > usize::MAX - 1000 { 0 } else { x }
106 },
107 follower_count: get!(x->10(i32)) as usize,
108 following_count: get!(x->11(i32)) as usize,
109 last_seen: get!(x->12(i64)) as usize,
110 totp: get!(x->13(String)),
111 recovery_codes: serde_json::from_str(&get!(x->14(String)).to_string()).unwrap(),
112 post_count: get!(x->15(i32)) as usize,
113 request_count: {
114 let x = get!(x->16(i32)) as usize;
115 if x > usize::MAX - 1000 { 0 } else { x }
116 },
117 connections: serde_json::from_str(&get!(x->17(String)).to_string()).unwrap(),
118 stripe_id: get!(x->18(String)),
119 grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(),
120 associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(),
121 invite_code: get!(x->21(i64)) as usize,
122 secondary_permissions: SecondaryPermission::from_bits(get!(x->22(i32)) as u32).unwrap(),
123 achievements: serde_json::from_str(&get!(x->23(String)).to_string()).unwrap(),
124 awaiting_purchase: get!(x->24(i32)) as i8 == 1,
125 was_purchased: get!(x->25(i32)) as i8 == 1,
126 browser_session: get!(x->26(String)),
127 ban_reason: get!(x->27(String)),
128 channel_mutes: serde_json::from_str(&get!(x->28(String)).to_string()).unwrap(),
129 is_deactivated: get!(x->29(i32)) as i8 == 1,
130 ban_expire: get!(x->30(i64)) as usize,
131 coins: get!(x->31(i32)),
132 checkouts: serde_json::from_str(&get!(x->32(String)).to_string()).unwrap(),
133 applied_configurations: serde_json::from_str(&get!(x->33(String)).to_string()).unwrap(),
134 last_policy_consent: get!(x->34(i64)) as usize,
135 }
136 }
137
138 auto_method!(get_user_by_id(usize as i64)@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
139 auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
140 auto_method!(get_user_by_username_no_cache(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User);
141 auto_method!(get_user_by_browser_session(&str)@get_user_from_row -> "SELECT * FROM users WHERE browser_session = $1" --name="user" --returns=User);
142
143 pub async fn get_user_by_id_with_void(&self, id: usize) -> Result<User> {
148 let conn = match self.0.connect().await {
149 Ok(c) => c,
150 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
151 };
152
153 let res = query_row!(
154 &conn,
155 "SELECT * FROM users WHERE id = $1",
156 &[&(id as i64)],
157 |x| Ok(Self::get_user_from_row(x))
158 );
159
160 if res.is_err() {
161 return Ok(User::deleted());
162 }
164
165 Ok(res.unwrap())
166 }
167
168 pub async fn get_user_by_token(&self, token: &str) -> Result<User> {
173 let conn = match self.0.connect().await {
174 Ok(c) => c,
175 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
176 };
177
178 let res = query_row!(
179 &conn,
180 "SELECT * FROM users WHERE tokens LIKE $1",
181 &[&format!("%\"{token}\"%")],
182 |x| Ok(Self::get_user_from_row(x))
183 );
184
185 if res.is_err() {
186 return Err(Error::UserNotFound);
187 }
188
189 Ok(res.unwrap())
190 }
191
192 pub async fn get_user_by_grant_token(
199 &self,
200 token: &str,
201 check_expiration: bool,
202 ) -> Result<(AuthGrant, User)> {
203 let conn = match self.0.connect().await {
204 Ok(c) => c,
205 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
206 };
207
208 let res = query_row!(
209 &conn,
210 "SELECT * FROM users WHERE grants LIKE $1",
211 &[&format!("%\"token\":\"{token}\"%")],
212 |x| Ok(Self::get_user_from_row(x))
213 );
214
215 if res.is_err() {
216 return Err(Error::UserNotFound);
217 }
218
219 let user = res.unwrap();
220 let grant = user
221 .grants
222 .iter()
223 .find(|x| x.token == token)
224 .unwrap()
225 .clone();
226
227 if check_expiration {
229 let now = unix_epoch_timestamp();
230 let delta = now - grant.last_updated;
231
232 if delta > 604_800_000 {
233 return Err(Error::MiscError("Token expired".to_string()));
234 }
235 }
236
237 Ok((grant, user))
239 }
240
241 pub async fn create_user(&self, mut data: User) -> Result<()> {
246 if !self.0.0.security.registration_enabled {
247 return Err(Error::RegistrationDisabled);
248 }
249
250 data.username = data.username.to_lowercase();
251
252 if data.username.len() < 2 {
254 return Err(Error::DataTooShort("username".to_string()));
255 } else if data.username.len() > 32 {
256 return Err(Error::DataTooLong("username".to_string()));
257 }
258
259 if data.password.len() < 6 {
260 return Err(Error::DataTooShort("password".to_string()));
261 }
262
263 if self.0.0.banned_usernames.contains(&data.username) {
264 return Err(Error::MiscError("This username cannot be used".to_string()));
265 }
266
267 let regex = regex::RegexBuilder::new(NAME_REGEX)
268 .multi_line(true)
269 .build()
270 .unwrap();
271
272 if regex.captures(&data.username).is_some() {
273 return Err(Error::MiscError(
274 "This username contains invalid characters".to_string(),
275 ));
276 }
277
278 if self.get_user_by_username(&data.username).await.is_ok() {
280 return Err(Error::UsernameInUse);
281 }
282
283 let conn = match self.0.connect().await {
285 Ok(c) => c,
286 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
287 };
288
289 let res = execute!(
290 &conn,
291 "INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35)",
292 params![
293 &(data.id as i64),
294 &(data.created as i64),
295 &data.username.to_lowercase(),
296 &data.password,
297 &data.salt,
298 &serde_json::to_string(&data.settings).unwrap(),
299 &serde_json::to_string(&data.tokens).unwrap(),
300 &(FinePermission::DEFAULT.bits() as i32),
301 &if data.is_verified { 1_i32 } else { 0_i32 },
302 &0_i32,
303 &0_i32,
304 &0_i32,
305 &(data.last_seen as i64),
306 &String::new(),
307 "[]",
308 &0_i32,
309 &0_i32,
310 &serde_json::to_string(&data.connections).unwrap(),
311 &"",
312 &serde_json::to_string(&data.grants).unwrap(),
313 &serde_json::to_string(&data.associated).unwrap(),
314 &(data.invite_code as i64),
315 &(SecondaryPermission::DEFAULT.bits() as i32),
316 &serde_json::to_string(&data.achievements).unwrap(),
317 &if data.awaiting_purchase { 1_i32 } else { 0_i32 },
318 &if data.was_purchased { 1_i32 } else { 0_i32 },
319 &data.browser_session,
320 &data.ban_reason,
321 &serde_json::to_string(&data.channel_mutes).unwrap(),
322 &if data.is_deactivated { 1_i32 } else { 0_i32 },
323 &(data.ban_expire as i64),
324 &(data.coins as i32),
325 &serde_json::to_string(&data.checkouts).unwrap(),
326 &serde_json::to_string(&data.applied_configurations).unwrap(),
327 &(data.last_policy_consent as i64)
328 ]
329 );
330
331 if let Err(e) = res {
332 return Err(Error::DatabaseError(e.to_string()));
333 }
334
335 Ok(())
336 }
337
338 pub async fn delete_user(&self, id: usize, password: &str, force: bool) -> Result<User> {
345 let user = self.get_user_by_id(id).await?;
346
347 if (hash_salted(password.to_string(), user.salt.clone()) != user.password) && !force {
348 return Err(Error::IncorrectPassword);
349 }
350
351 let conn = match self.0.connect().await {
352 Ok(c) => c,
353 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
354 };
355
356 let res = execute!(&conn, "DELETE FROM users WHERE id = $1", &[&(id as i64)]);
357
358 if let Err(e) = res {
359 return Err(Error::DatabaseError(e.to_string()));
360 }
361
362 self.cache_clear_user(&user).await;
363
364 for community in self.get_communities_by_owner(user.id).await? {
366 self.delete_community(community.id, &user).await?;
367 }
368
369 let res = execute!(
372 &conn,
373 "DELETE FROM memberships WHERE owner = $1",
374 &[&(id as i64)]
375 );
376
377 if let Err(e) = res {
378 return Err(Error::DatabaseError(e.to_string()));
379 }
380
381 let res = execute!(
383 &conn,
384 "DELETE FROM notifications WHERE owner = $1",
385 &[&(id as i64)]
386 );
387
388 if let Err(e) = res {
389 return Err(Error::DatabaseError(e.to_string()));
390 }
391
392 let res = execute!(
394 &conn,
395 "DELETE FROM requests WHERE owner = $1",
396 &[&(id as i64)]
397 );
398
399 if let Err(e) = res {
400 return Err(Error::DatabaseError(e.to_string()));
401 }
402
403 let res = execute!(
405 &conn,
406 "DELETE FROM user_warnings WHERE receiver = $1",
407 &[&(id as i64)]
408 );
409
410 if let Err(e) = res {
411 return Err(Error::DatabaseError(e.to_string()));
412 }
413
414 let res = execute!(
416 &conn,
417 "DELETE FROM userblocks WHERE initiator = $1 OR receiver = $1",
418 &[&(id as i64)]
419 );
420
421 if let Err(e) = res {
422 return Err(Error::DatabaseError(e.to_string()));
423 }
424
425 let res = execute!(
426 &conn,
427 "DELETE FROM ipblocks WHERE initiator = $1",
428 &[&(id as i64)]
429 );
430
431 if let Err(e) = res {
432 return Err(Error::DatabaseError(e.to_string()));
433 }
434
435 let res = execute!(
438 &conn,
439 "DELETE FROM reactions WHERE owner = $1",
440 &[&(id as i64)]
441 );
442
443 if let Err(e) = res {
444 return Err(Error::DatabaseError(e.to_string()));
445 }
446
447 let res = execute!(
449 &conn,
450 "DELETE FROM stacks WHERE owner = $1",
451 &[&(id as i64)]
452 );
453
454 if let Err(e) = res {
455 return Err(Error::DatabaseError(e.to_string()));
456 }
457
458 let res = execute!(
460 &conn,
461 "DELETE FROM drafts WHERE owner = $1",
462 &[&(id as i64)]
463 );
464
465 if let Err(e) = res {
466 return Err(Error::DatabaseError(e.to_string()));
467 }
468
469 let res = execute!(&conn, "DELETE FROM posts WHERE owner = $1", &[&(id as i64)]);
471
472 if let Err(e) = res {
473 return Err(Error::DatabaseError(e.to_string()));
474 }
475
476 let res = execute!(&conn, "DELETE FROM polls WHERE owner = $1", &[&(id as i64)]);
478
479 if let Err(e) = res {
480 return Err(Error::DatabaseError(e.to_string()));
481 }
482
483 let res = execute!(
485 &conn,
486 "DELETE FROM pollvotes WHERE owner = $1",
487 &[&(id as i64)]
488 );
489
490 if let Err(e) = res {
491 return Err(Error::DatabaseError(e.to_string()));
492 }
493
494 let res = execute!(
496 &conn,
497 "DELETE FROM stackblocks WHERE initiator = $1",
498 &[&(id as i64)]
499 );
500
501 if let Err(e) = res {
502 return Err(Error::DatabaseError(e.to_string()));
503 }
504
505 let res = execute!(
507 &conn,
508 "DELETE FROM journals WHERE owner = $1",
509 &[&(id as i64)]
510 );
511
512 if let Err(e) = res {
513 return Err(Error::DatabaseError(e.to_string()));
514 }
515
516 let res = execute!(&conn, "DELETE FROM notes WHERE owner = $1", &[&(id as i64)]);
518
519 if let Err(e) = res {
520 return Err(Error::DatabaseError(e.to_string()));
521 }
522
523 let res = execute!(
525 &conn,
526 "DELETE FROM invite_codes WHERE owner = $1",
527 &[&(id as i64)]
528 );
529
530 if let Err(e) = res {
531 return Err(Error::DatabaseError(e.to_string()));
532 }
533
534 let res = execute!(
536 &conn,
537 "DELETE FROM message_reactions WHERE owner = $1",
538 &[&(id as i64)]
539 );
540
541 if let Err(e) = res {
542 return Err(Error::DatabaseError(e.to_string()));
543 }
544
545 let res = execute!(
547 &conn,
548 "DELETE FROM transfers WHERE sender = $1 OR receiver = $1",
549 &[&(id as i64)]
550 );
551
552 if let Err(e) = res {
553 return Err(Error::DatabaseError(e.to_string()));
554 }
555
556 let res = execute!(
558 &conn,
559 "DELETE FROM products WHERE owner = $1",
560 &[&(id as i64)]
561 );
562
563 if let Err(e) = res {
564 return Err(Error::DatabaseError(e.to_string()));
565 }
566
567 let res = execute!(
569 &conn,
570 "DELETE FROM domains WHERE owner = $1",
571 &[&(id as i64)]
572 );
573
574 if let Err(e) = res {
575 return Err(Error::DatabaseError(e.to_string()));
576 }
577
578 let res = execute!(
580 &conn,
581 "DELETE FROM services WHERE owner = $1",
582 &[&(id as i64)]
583 );
584
585 if let Err(e) = res {
586 return Err(Error::DatabaseError(e.to_string()));
587 }
588
589 let res = execute!(
591 &conn,
592 "DELETE FROM letters WHERE owner = $1",
593 &[&(id as i64)]
594 );
595
596 if let Err(e) = res {
597 return Err(Error::DatabaseError(e.to_string()));
598 }
599
600 let res = execute!(&conn, "DELETE FROM ads WHERE owner = $1", &[&(id as i64)]);
602
603 if let Err(e) = res {
604 return Err(Error::DatabaseError(e.to_string()));
605 }
606
607 for follow in self.get_userfollows_by_receiver_all(id).await? {
609 self.delete_userfollow(follow.id, &user, true).await?;
610 }
611
612 for follow in self.get_userfollows_by_initiator_all(id).await? {
613 self.delete_userfollow(follow.id, &user, true).await?;
614 }
615
616 for app in self.get_apps_by_owner(id).await? {
618 self.delete_app(app.id, &user).await?;
619 }
620
621 let avatar = PathBufD::current().extend(&[
623 self.0.0.dirs.media.as_str(),
624 "avatars",
625 &format!(
626 "{}.{}",
627 &(user.id as i64),
628 user.settings.avatar_mime.replace("image/", "")
629 ),
630 ]);
631
632 let banner = PathBufD::current().extend(&[
633 self.0.0.dirs.media.as_str(),
634 "banners",
635 &format!(
636 "{}.{}",
637 &(user.id as i64),
638 user.settings.banner_mime.replace("image/", "")
639 ),
640 ]);
641
642 if exists(&avatar).unwrap() {
643 remove_file(avatar).unwrap();
644 }
645
646 if exists(&banner).unwrap() {
647 remove_file(banner).unwrap();
648 }
649
650 for upload in self.get_uploads_by_owner_all(user.id).await? {
652 self.delete_upload(upload.id).await?;
653 }
654
655 for poll in self.get_polls_by_owner_all(user.id).await? {
657 self.delete_poll(poll.id, &user).await?;
658 }
659
660 if self.0.0.security.enable_invite_codes {
662 if user.invite_code != 0 && self.get_invite_code_by_id(user.invite_code).await.is_ok() {
663 self.update_invite_code_is_used(user.invite_code, false)
666 .await?;
667 }
668 }
669
670 Ok(user)
672 }
673
674 pub async fn update_user_verified_status(&self, id: usize, x: bool, user: User) -> Result<()> {
675 if !user.permissions.check(FinePermission::MANAGE_VERIFIED) {
676 return Err(Error::NotAllowed);
677 }
678
679 let other_user = self.get_user_by_id(id).await?;
680
681 let conn = match self.0.connect().await {
682 Ok(c) => c,
683 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
684 };
685
686 let res = execute!(
687 &conn,
688 "UPDATE users SET verified = $1 WHERE id = $2",
689 params![&{ if x { 1 } else { 0 } }, &(id as i64)]
690 );
691
692 if let Err(e) = res {
693 return Err(Error::DatabaseError(e.to_string()));
694 }
695
696 self.cache_clear_user(&other_user).await;
697
698 self.create_audit_log_entry(AuditLogEntry::new(
700 user.id,
701 format!(
702 "invoked `update_user_verified_status` with x value `{}` and y value `{}`",
703 other_user.id, x
704 ),
705 ))
706 .await?;
707
708 Ok(())
710 }
711
712 pub async fn update_user_is_deactivated(&self, id: usize, x: bool, user: User) -> Result<()> {
713 if id != user.id && !user.permissions.check(FinePermission::MANAGE_USERS) {
714 return Err(Error::NotAllowed);
715 }
716
717 let other_user = self.get_user_by_id(id).await?;
718
719 let conn = match self.0.connect().await {
720 Ok(c) => c,
721 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
722 };
723
724 let res = execute!(
725 &conn,
726 "UPDATE users SET is_deactivated = $1 WHERE id = $2",
727 params![&{ if x { 1 } else { 0 } }, &(id as i64)]
728 );
729
730 if let Err(e) = res {
731 return Err(Error::DatabaseError(e.to_string()));
732 }
733
734 self.cache_clear_user(&other_user).await;
735
736 self.create_audit_log_entry(AuditLogEntry::new(
738 user.id,
739 format!(
740 "invoked `update_user_is_deactivated` with x value `{}` and y value `{}`",
741 other_user.id, x
742 ),
743 ))
744 .await?;
745
746 Ok(())
748 }
749
750 pub async fn update_user_password(
751 &self,
752 id: usize,
753 from: String,
754 to: String,
755 user: User,
756 force: bool,
757 ) -> Result<()> {
758 if !user.check_password(from.clone()) && !force {
760 return Err(Error::MiscError("Password does not match".to_string()));
761 }
762
763 let conn = match self.0.connect().await {
765 Ok(c) => c,
766 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
767 };
768
769 let new_salt = salt();
770 let new_password = hash_salted(to, new_salt.clone());
771 let res = execute!(
772 &conn,
773 "UPDATE users SET password = $1, salt = $2 WHERE id = $3",
774 params![&new_password.as_str(), &new_salt.as_str(), &(id as i64)]
775 );
776
777 if let Err(e) = res {
778 return Err(Error::DatabaseError(e.to_string()));
779 }
780
781 self.cache_clear_user(&user).await;
782 Ok(())
783 }
784
785 pub async fn update_user_username(&self, id: usize, to: String, user: User) -> Result<()> {
786 if to.len() < 2 {
788 return Err(Error::DataTooShort("username".to_string()));
789 } else if to.len() > 32 {
790 return Err(Error::DataTooLong("username".to_string()));
791 }
792
793 if self.0.0.banned_usernames.contains(&to) {
794 return Err(Error::MiscError("This username cannot be used".to_string()));
795 }
796
797 let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+")
798 .multi_line(true)
799 .build()
800 .unwrap();
801
802 if regex.captures(&to).is_some() {
803 return Err(Error::MiscError(
804 "This username contains invalid characters".to_string(),
805 ));
806 }
807
808 let conn = match self.0.connect().await {
810 Ok(c) => c,
811 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
812 };
813
814 let res = execute!(
815 &conn,
816 "UPDATE users SET username = $1 WHERE id = $2",
817 params![&to.to_lowercase(), &(id as i64)]
818 );
819
820 if let Err(e) = res {
821 return Err(Error::DatabaseError(e.to_string()));
822 }
823
824 self.cache_clear_user(&user).await;
825 Ok(())
826 }
827
828 pub async fn update_user_awaiting_purchased_status(
829 &self,
830 id: usize,
831 x: bool,
832 user: User,
833 require_permission: bool,
834 ) -> Result<()> {
835 if (user.id != id) | require_permission {
836 if !user.permissions.check(FinePermission::MANAGE_USERS) {
837 return Err(Error::NotAllowed);
838 }
839 }
840
841 let other_user = self.get_user_by_id(id).await?;
842
843 let conn = match self.0.connect().await {
844 Ok(c) => c,
845 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
846 };
847
848 let res = execute!(
849 &conn,
850 "UPDATE users SET awaiting_purchase = $1 WHERE id = $2",
851 params![&{ if x { 1 } else { 0 } }, &(id as i64)]
852 );
853
854 if let Err(e) = res {
855 return Err(Error::DatabaseError(e.to_string()));
856 }
857
858 self.cache_clear_user(&other_user).await;
859
860 if user.id != other_user.id {
862 self.create_audit_log_entry(AuditLogEntry::new(
863 user.id,
864 format!(
865 "invoked `update_user_purchased_status` with x value `{}` and y value `{}`",
866 other_user.id, x
867 ),
868 ))
869 .await?;
870 }
871
872 Ok(())
874 }
875
876 pub async fn seen_user(&self, user: &User) -> Result<()> {
877 let conn = match self.0.connect().await {
878 Ok(c) => c,
879 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
880 };
881
882 let res = execute!(
883 &conn,
884 "UPDATE users SET last_seen = $1 WHERE id = $2",
885 params![&(unix_epoch_timestamp() as i64), &(user.id as i64)]
886 );
887
888 if let Err(e) = res {
889 return Err(Error::DatabaseError(e.to_string()));
890 }
891
892 self.cache_clear_user(user).await;
893
894 Ok(())
895 }
896
897 #[async_recursion::async_recursion]
901 pub async fn add_achievement(
902 &self,
903 user: &mut User,
904 achievement: Achievement,
905 check_for_final: bool,
906 ) -> Result<()> {
907 if user.settings.disable_achievements {
908 return Ok(());
909 }
910
911 if user
912 .achievements
913 .iter()
914 .find(|x| x.name == achievement.name)
915 .is_some()
916 {
917 return Ok(());
918 }
919
920 self.create_notification(Notification::new(
922 "You've earned a new achievement!".to_string(),
923 format!(
924 "You've earned the \"{}\" [achievement](/achievements)!",
925 achievement.name.title()
926 ),
927 user.id,
928 ))
929 .await?;
930
931 user.achievements.push(achievement);
933 self.update_user_achievements(user.id, user.achievements.to_owned())
934 .await?;
935
936 if check_for_final {
938 if user.achievements.len() + 1 == ACHIEVEMENTS {
939 self.add_achievement(user, AchievementName::GetAllOtherAchievements.into(), false)
940 .await?;
941 }
942 }
943
944 Ok(())
946 }
947
948 pub fn fill_achievements(
953 &self,
954 mut list: Vec<Achievement>,
955 ) -> Vec<(String, String, AchievementRarity, Achievement)> {
956 let mut out = Vec::new();
957
958 list.sort_by(|a, b| a.unlocked.cmp(&b.unlocked));
960 list.reverse();
961
962 for x in list {
964 out.push((
965 x.name.title().to_string(),
966 x.name.description().to_string(),
967 x.name.rarity(),
968 x,
969 ))
970 }
971
972 out
973 }
974
975 pub fn check_totp(&self, ua: &User, code: &str) -> bool {
977 let totp = ua.totp(Some(
978 self.0
979 .0
980 .host
981 .replace("http://", "")
982 .replace("https://", "")
983 .replace(":", "_"),
984 ));
985
986 if let Some(totp) = totp {
987 return !code.is_empty()
988 && (totp.check_current(code).unwrap()
989 | ua.recovery_codes.contains(&code.to_string()));
990 }
991
992 true
993 }
994
995 pub fn generate_totp_recovery_codes() -> Vec<String> {
997 let mut out: Vec<String> = Vec::new();
998
999 for _ in 0..9 {
1000 out.push(salt())
1001 }
1002
1003 out
1004 }
1005
1006 pub async fn update_user_totp(
1013 &self,
1014 id: usize,
1015 secret: &str,
1016 recovery: &Vec<String>,
1017 ) -> Result<()> {
1018 let user = self.get_user_by_id(id).await?;
1019
1020 let conn = match self.0.connect().await {
1022 Ok(c) => c,
1023 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
1024 };
1025
1026 let res = execute!(
1027 &conn,
1028 "UPDATE users SET totp = $1, recovery_codes = $2 WHERE id = $3",
1029 params![
1030 &secret,
1031 &serde_json::to_string(recovery).unwrap(),
1032 &(id as i64)
1033 ]
1034 );
1035
1036 if let Err(e) = res {
1037 return Err(Error::DatabaseError(e.to_string()));
1038 }
1039
1040 self.cache_clear_user(&user).await;
1041 Ok(())
1042 }
1043
1044 pub async fn enable_totp(
1053 &self,
1054 id: usize,
1055 user: User,
1056 ) -> Result<(String, String, Vec<String>)> {
1057 let other_user = self.get_user_by_id(id).await?;
1058
1059 if other_user.id != user.id {
1060 if other_user.permissions.check(FinePermission::MANAGE_USERS) {
1061 self.create_audit_log_entry(AuditLogEntry::new(
1063 user.id,
1064 format!("invoked `enable_totp` with x value `{}`", other_user.id,),
1065 ))
1066 .await?;
1067 } else {
1068 return Err(Error::NotAllowed);
1069 }
1070 }
1071
1072 let secret = totp_rs::Secret::default().to_string();
1073 let recovery = Self::generate_totp_recovery_codes();
1074 self.update_user_totp(id, &secret, &recovery).await?;
1075
1076 let other_user = self.get_user_by_id(id).await?;
1078
1079 let totp = other_user.totp(Some(
1081 self.0
1082 .0
1083 .host
1084 .replace("http://", "")
1085 .replace("https://", "")
1086 .replace(":", "_"),
1087 ));
1088
1089 if totp.is_none() {
1090 return Err(Error::MiscError("Failed to get TOTP code".to_string()));
1091 }
1092
1093 let totp = totp.unwrap();
1094
1095 let qr = match totp.get_qr_base64() {
1097 Ok(q) => q,
1098 Err(e) => return Err(Error::MiscError(e.to_string())),
1099 };
1100
1101 Ok((totp.get_secret_base32(), qr, recovery))
1103 }
1104
1105 pub async fn get_applied_configurations(&self, user: &User) -> Result<Vec<String>> {
1107 let mut out = Vec::new();
1108
1109 for config in &user.applied_configurations {
1110 let product = self.get_product_by_id(config.1).await?;
1111 let owner = self.get_user_by_id_with_void(product.owner).await?;
1112
1113 if config.0 == AppliedConfigType::StyleSnippet
1114 && !owner.permissions.check(FinePermission::SUPPORTER)
1115 {
1116 out.push(format!(
1117 "<script>console.warn(\"{} has stopped their supporter subscription, so this applied configuration no longer works.\");</script>",
1118 owner.username
1119 ));
1120
1121 continue;
1122 }
1123
1124 out.push(match config.0 {
1125 AppliedConfigType::StyleSnippet => {
1126 format!(
1127 "<style>{}</style>",
1128 product.data.replace("<", "<").replace(">", ">")
1129 )
1130 }
1131 })
1132 }
1133
1134 Ok(out)
1135 }
1136
1137 pub async fn cache_clear_user(&self, user: &User) {
1138 self.0.1.remove(format!("atto.user:{}", user.id)).await;
1139 self.0
1140 .1
1141 .remove(format!("atto.user:{}", user.username))
1142 .await;
1143 }
1144
1145 update_role_fn!(update_user_role, FinePermission, "permissions");
1146 update_role_fn!(
1147 update_user_secondary_role,
1148 SecondaryPermission,
1149 "secondary_permissions"
1150 );
1151
1152 auto_method!(update_user_tokens(Vec<Token>)@get_user_by_id -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1153 auto_method!(update_user_grants(Vec<AuthGrant>)@get_user_by_id -> "UPDATE users SET grants = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1154 auto_method!(update_user_settings(UserSettings)@get_user_by_id -> "UPDATE users SET settings = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1155 auto_method!(update_user_connections(UserConnections)@get_user_by_id -> "UPDATE users SET connections = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1156 auto_method!(update_user_associated(Vec<usize>)@get_user_by_id -> "UPDATE users SET associated = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1157 auto_method!(update_user_achievements(Vec<Achievement>)@get_user_by_id -> "UPDATE users SET achievements = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1158 auto_method!(update_user_invite_code(i64)@get_user_by_id -> "UPDATE users SET invite_code = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1159 auto_method!(update_user_browser_session(&str)@get_user_by_id -> "UPDATE users SET browser_session = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1160 auto_method!(update_user_ban_reason(&str)@get_user_by_id -> "UPDATE users SET ban_reason = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1161 auto_method!(update_user_channel_mutes(Vec<usize>)@get_user_by_id -> "UPDATE users SET channel_mutes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1162 auto_method!(update_user_ban_expire(i64)@get_user_by_id -> "UPDATE users SET ban_expire = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1163 auto_method!(update_user_coins(i32)@get_user_by_id -> "UPDATE users SET coins = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1164 auto_method!(update_user_checkouts(Vec<String>)@get_user_by_id -> "UPDATE users SET checkouts = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1165 auto_method!(update_user_applied_configurations(Vec<(AppliedConfigType, usize)>)@get_user_by_id -> "UPDATE users SET applied_configurations = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
1166 auto_method!(update_user_last_policy_consent(i64)@get_user_by_id -> "UPDATE users SET last_policy_consent = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1167
1168 auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
1169 auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1170
1171 auto_method!(update_user_notification_count(i32)@get_user_by_id -> "UPDATE users SET notification_count = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1172 auto_method!(incr_user_notifications()@get_user_by_id -> "UPDATE users SET notification_count = notification_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
1173 auto_method!(decr_user_notifications()@get_user_by_id -> "UPDATE users SET notification_count = notification_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=notification_count);
1174
1175 auto_method!(incr_user_follower_count()@get_user_by_id -> "UPDATE users SET follower_count = follower_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
1176 auto_method!(decr_user_follower_count()@get_user_by_id -> "UPDATE users SET follower_count = follower_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=follower_count);
1177
1178 auto_method!(incr_user_following_count()@get_user_by_id -> "UPDATE users SET following_count = following_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
1179 auto_method!(decr_user_following_count()@get_user_by_id -> "UPDATE users SET following_count = following_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=following_count);
1180
1181 auto_method!(incr_user_post_count()@get_user_by_id -> "UPDATE users SET post_count = post_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
1182 auto_method!(decr_user_post_count()@get_user_by_id -> "UPDATE users SET post_count = post_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=post_count);
1183
1184 auto_method!(update_user_request_count(i32)@get_user_by_id -> "UPDATE users SET request_count = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
1185 auto_method!(incr_user_request_count()@get_user_by_id -> "UPDATE users SET request_count = request_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
1186 auto_method!(decr_user_request_count()@get_user_by_id -> "UPDATE users SET request_count = request_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=request_count);
1187
1188 auto_method!(get_user_by_invite_code(i64)@get_user_from_row -> "SELECT * FROM users WHERE invite_code = $1" --name="user" --returns=User);
1189}