tetratto_core/database/
auth.rs

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                // check permission
36                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            // ...
54            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            // create audit log entry
72            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            // ...
84            Ok(())
85        }
86    };
87}
88
89impl DataManager {
90    /// Get a [`User`] from an SQL row.
91    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                // we're a little too close to the maximum count, clearly something's gone wrong
105                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    /// Get a user given just their ID. Returns the void user if the user doesn't exist.
144    ///
145    /// # Arguments
146    /// * `id` - the ID of the user
147    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            // return Err(Error::UserNotFound);
163        }
164
165        Ok(res.unwrap())
166    }
167
168    /// Get a user given just their auth token.
169    ///
170    /// # Arguments
171    /// * `token` - the token of the user
172    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    /// Get a user given just their grant token.
193    ///
194    /// Also returns the auth grant this token is associated with from the user.
195    ///
196    /// # Arguments
197    /// * `token` - the token of the user
198    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        // check token expiry
228        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        // ...
238        Ok((grant, user))
239    }
240
241    /// Create a new user in the database.
242    ///
243    /// # Arguments
244    /// * `data` - a mock [`User`] object to insert
245    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        // check values
253        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        // make sure username isn't taken
279        if self.get_user_by_username(&data.username).await.is_ok() {
280            return Err(Error::UsernameInUse);
281        }
282
283        // ...
284        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    /// Delete an existing user in the database.
339    ///
340    /// # Arguments
341    /// * `id` - the ID of the user
342    /// * `password` - the current password of the user
343    /// * `force` - if we should delete even if the given password is incorrect
344    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        // delete communities
365        for community in self.get_communities_by_owner(user.id).await? {
366            self.delete_community(community.id, &user).await?;
367        }
368
369        // delete memberships
370        // member counts will remain the same... but that should probably be changed
371        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        // delete notifications
382        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        // delete requests
393        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        // delete warnings
404        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        // delete blocks
415        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        // delete reactions
436        // reactions counts will remain the same :)
437        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        // delete stacks
448        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        // delete drafts
459        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        // delete posts
470        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        // delete polls
477        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        // delete poll votes
484        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        // delete stackblocks
495        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        // delete journals
506        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        // delete notes
517        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        // delete invite codes
524        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        // delete message reactions
535        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        // delete transfers
546        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        // delete products
557        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        // delete domains
568        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        // delete services
579        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        // delete letters
590        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        // delete ads
601        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        // delete user follows... individually since it requires updating user counts
608        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        // delete apps
617        for app in self.get_apps_by_owner(id).await? {
618            self.delete_app(app.id, &user).await?;
619        }
620
621        // remove images
622        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        // delete uploads
651        for upload in self.get_uploads_by_owner_all(user.id).await? {
652            self.delete_upload(upload.id).await?;
653        }
654
655        // delete polls
656        for poll in self.get_polls_by_owner_all(user.id).await? {
657            self.delete_poll(poll.id, &user).await?;
658        }
659
660        // free up invite code
661        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                // we're checking if the code is ok because the owner might've deleted their account,
664                // deleting all of their invite codes as well
665                self.update_invite_code_is_used(user.invite_code, false)
666                    .await?;
667            }
668        }
669
670        // ...
671        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        // create audit log entry
699        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        // ...
709        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        // create audit log entry
737        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        // ...
747        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        // verify password
759        if !user.check_password(from.clone()) && !force {
760            return Err(Error::MiscError("Password does not match".to_string()));
761        }
762
763        // ...
764        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        // check value
787        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        // ...
809        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        // create audit log entry
861        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        // ...
873        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    /// Add an achievement to a user.
898    ///
899    /// Still returns `Ok` if the user already has the achievement.
900    #[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        // send notif
921        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        // add achievement
932        user.achievements.push(achievement);
933        self.update_user_achievements(user.id, user.achievements.to_owned())
934            .await?;
935
936        // check for final
937        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        // ...
945        Ok(())
946    }
947
948    /// Fill achievements with their title and description.
949    ///
950    /// # Returns
951    /// `(name, description, rarity, achievement)`
952    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        // sort by unlocked desc
959        list.sort_by(|a, b| a.unlocked.cmp(&b.unlocked));
960        list.reverse();
961
962        // ...
963        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    /// Validate a given TOTP code for the given profile.
976    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    /// Generate 8 random recovery codes for TOTP.
996    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    /// Update the profile's TOTP secret.
1007    ///
1008    /// # Arguments
1009    /// * `id` - the ID of the user
1010    /// * `secret` - the TOTP secret
1011    /// * `recovery` - the TOTP recovery codes
1012    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        // update
1021        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    /// Enable TOTP for a profile.
1045    ///
1046    /// # Arguments
1047    /// * `id` - the ID of the user to enable TOTP for
1048    /// * `user` - the user doing this
1049    ///
1050    /// # Returns
1051    /// `Result<(secret, qr base64)>`
1052    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                // create audit log entry
1062                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        // fetch profile again (with totp information)
1077        let other_user = self.get_user_by_id(id).await?;
1078
1079        // get totp
1080        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        // generate qr
1096        let qr = match totp.get_qr_base64() {
1097            Ok(q) => q,
1098            Err(e) => return Err(Error::MiscError(e.to_string())),
1099        };
1100
1101        // return
1102        Ok((totp.get_secret_base32(), qr, recovery))
1103    }
1104
1105    /// Get all applied configurations as a vector of strings from the given user.
1106    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("<", "&lt;").replace(">", "&gt;")
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}