autter_core/
model.rs

1use serde::{Deserialize, Serialize};
2use serde_valid::Validate;
3pub use tetratto_core::model::{ApiReturn, Error, Result};
4use tetratto_shared::{
5    hash::{hash_salted, salt},
6    snow::Snowflake,
7    unix_epoch_timestamp,
8};
9use totp_rs::TOTP;
10
11#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
12pub enum ThemePreference {
13    #[default]
14    Auto,
15    Light,
16    Dark,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
20pub enum UserPermission {
21    /// Manage other users.
22    ManageUsers,
23    /// Verify and unverify users.
24    ManageVerified,
25    /// Manage audit log entries.
26    ManageAuditLog,
27    /// Manage reports.
28    ManageReports,
29    /// Manage miscellaneous app data (e.g. Askall asks).
30    ManageMiscAppData,
31    /// Manage user notifications.
32    ManageNotifications,
33    /// User is banned.
34    Banned,
35    /// Ban and unban users.
36    ManageBans,
37    /// Delete other users.
38    DeleteUsers,
39    /// Manage user warnings.
40    ManageWarnings,
41    /// User is a premium user.
42    Premium,
43    /// User is a premium plus user.
44    PremiumPlus,
45    /// Manage user properties.
46    ManageProperties,
47    /// Manage user organizations.
48    ManageOrganizations,
49    /// Manage organization permission lists.
50    ManagePermissionsLists,
51}
52
53#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
54pub enum UserBadge {
55    /// User is a site moderator.
56    Staff,
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize, Validate, Default)]
60pub struct UserSettings {
61    #[serde(default)]
62    #[validate(max_length = 32)]
63    pub display_name: String,
64    #[serde(default)]
65    #[validate(max_length = 4096)]
66    pub biography: String,
67    /// External links for the user's other profiles on other websites.
68    #[serde(default)]
69    #[validate(max_items = 15)]
70    #[validate(unique_items)]
71    pub links: Vec<(String, String)>,
72    /// The user's contacts.
73    #[serde(default)]
74    #[validate(max_items = 5)]
75    #[validate(unique_items)]
76    pub contacts: Vec<(String, String)>,
77    /// The user's theme used on their profile.
78    #[serde(default)]
79    pub profile_theme: ThemePreference,
80}
81
82impl UserSettings {
83    pub fn verify_values(&self) -> Result<()> {
84        if let Err(e) = self.validate() {
85            return Err(Error::MiscError(e.to_string()));
86        }
87
88        Ok(())
89    }
90}
91
92#[derive(Clone, Debug, Serialize, Deserialize)]
93pub struct TetrattoLinkedAccount {
94    pub id: usize,
95    pub username: String,
96    pub added: usize,
97}
98
99#[derive(Clone, Debug, Serialize, Deserialize, Default)]
100pub struct UserLinkedAccounts {
101    #[serde(default)]
102    pub tetratto_multiple: Vec<TetrattoLinkedAccount>,
103}
104
105/// `(ip, token, creation timestamp)`
106pub type Token = (String, String, usize);
107
108#[derive(Clone, Debug, Serialize, Deserialize)]
109pub struct User {
110    pub id: usize,
111    pub created: usize,
112    pub username: String,
113    pub password: String,
114    pub salt: String,
115    pub settings: UserSettings,
116    pub tokens: Vec<Token>,
117    pub permissions: Vec<UserPermission>,
118    pub is_verified: bool,
119    pub notification_count: usize,
120    /// The TOTP secret for this profile. An empty value means the user has TOTP disabled.
121    #[serde(default)]
122    pub totp: String,
123    /// The TOTP recovery codes for this profile.
124    #[serde(default)]
125    pub recovery_codes: Vec<String>,
126    /// The user's Stripe customer ID.
127    #[serde(default)]
128    pub stripe_id: String,
129    /// The reason the user was banned.
130    #[serde(default)]
131    pub ban_reason: String,
132    /// The time at which the user's ban will automatically expire.
133    #[serde(default)]
134    pub ban_expire: usize,
135    /// If the user is deactivated. Deactivated users act almost like deleted
136    /// users, but their data is not wiped.
137    #[serde(default)]
138    pub is_deactivated: bool,
139    /// The IDs of Stripe checkout sessions that this user has successfully completed.
140    ///
141    /// This should be checked BEFORE applying purchases to ensure that the user hasn't
142    /// already applied this purchase.
143    #[serde(default)]
144    pub checkouts: Vec<String>,
145    /// The time in which the user last consented to the site's policies.
146    #[serde(default)]
147    pub last_policy_consent: usize,
148    /// The other accounts in the database that are linked to this account.
149    #[serde(default)]
150    pub linked_accounts: UserLinkedAccounts,
151    /// A list of the user's profile badges.
152    #[serde(default)]
153    pub badges: Vec<UserBadge>,
154    /// The ID of the user's principal organization (i.e. the organization shown on their profile).
155    #[serde(default)]
156    pub principal_org: usize,
157    /// If the user is locked into their principal organization. Their organization's
158    /// owner can manage their account if this is true.
159    #[serde(default)]
160    pub org_as_tenant: bool,
161    /// The number of organizations the user can create.
162    #[serde(default)]
163    pub org_creation_credits: i32,
164}
165
166impl Default for User {
167    fn default() -> Self {
168        Self::new("<unknown>".to_string(), String::new())
169    }
170}
171
172impl User {
173    /// Create a new [`User`].
174    pub fn new(username: String, password: String) -> Self {
175        let salt = salt();
176        let password = hash_salted(password, salt.clone());
177        let created = unix_epoch_timestamp();
178
179        Self {
180            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
181            created,
182            username,
183            password,
184            salt,
185            settings: UserSettings::default(),
186            tokens: Vec::new(),
187            permissions: Vec::new(),
188            is_verified: false,
189            notification_count: 0,
190            totp: String::new(),
191            recovery_codes: Vec::new(),
192            stripe_id: String::new(),
193            ban_reason: String::new(),
194            is_deactivated: false,
195            ban_expire: 0,
196            checkouts: Vec::new(),
197            last_policy_consent: created,
198            linked_accounts: UserLinkedAccounts::default(),
199            badges: Vec::new(),
200            principal_org: 0,
201            org_as_tenant: false,
202            org_creation_credits: 0,
203        }
204    }
205
206    /// Deleted user profile.
207    pub fn deleted() -> Self {
208        Self {
209            username: "<deleted>".to_string(),
210            id: 0,
211            ..Default::default()
212        }
213    }
214
215    /// Banned user profile.
216    pub fn banned() -> Self {
217        Self {
218            username: "<banned>".to_string(),
219            id: 0,
220            permissions: vec![UserPermission::Banned],
221            ..Default::default()
222        }
223    }
224
225    /// Create a new token
226    ///
227    /// # Returns
228    /// `(unhashed id, token)`
229    pub fn create_token(ip: &str) -> (String, Token) {
230        let unhashed = tetratto_shared::hash::uuid();
231        (
232            unhashed.clone(),
233            (
234                ip.to_string(),
235                tetratto_shared::hash::hash(unhashed),
236                unix_epoch_timestamp(),
237            ),
238        )
239    }
240
241    /// Check if the given password is correct for the user.
242    pub fn check_password(&self, against: String) -> bool {
243        self.password == hash_salted(against, self.salt.clone())
244    }
245
246    /// Get a [`TOTP`] from the profile's `totp` secret value.
247    pub fn totp(&self, issuer: Option<String>) -> Option<TOTP> {
248        if self.totp.is_empty() {
249            return None;
250        }
251
252        TOTP::new(
253            totp_rs::Algorithm::SHA1,
254            6,
255            1,
256            30,
257            self.totp.as_bytes().to_owned(),
258            Some(issuer.unwrap_or("tetratto!".to_string())),
259            self.username.clone(),
260        )
261        .ok()
262    }
263
264    /// Clean the struct for public viewing.
265    pub fn clean(&mut self) {
266        self.password = String::new();
267        self.salt = String::new();
268
269        self.tokens = Vec::new();
270
271        self.recovery_codes = Vec::new();
272        self.totp = String::new();
273
274        self.settings = UserSettings::default();
275        self.stripe_id = String::new();
276    }
277}
278
279#[derive(Debug, Serialize, Deserialize)]
280pub struct Notification {
281    pub id: usize,
282    pub created: usize,
283    pub title: String,
284    pub content: String,
285    pub owner: usize,
286    pub read: bool,
287    pub tag: String,
288}
289
290impl Notification {
291    /// Create a new [`Notification`].
292    pub fn new(title: String, content: String, owner: usize) -> Self {
293        Self {
294            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
295            created: unix_epoch_timestamp(),
296            title,
297            content,
298            owner,
299            read: false,
300            tag: String::new(),
301        }
302    }
303}
304
305#[derive(Serialize, Deserialize)]
306pub struct AuditLogEntry {
307    pub id: usize,
308    pub created: usize,
309    pub moderator: usize,
310    pub content: String,
311}
312
313impl AuditLogEntry {
314    /// Create a new [`AuditLogEntry`].
315    pub fn new(moderator: usize, content: String) -> Self {
316        Self {
317            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
318            created: unix_epoch_timestamp(),
319            moderator,
320            content,
321        }
322    }
323}
324
325#[derive(Serialize, Deserialize)]
326pub struct IpBan {
327    pub ip: String,
328    pub created: usize,
329    pub reason: String,
330    pub moderator: usize,
331}
332
333impl IpBan {
334    /// Create a new [`IpBan`].
335    pub fn new(ip: String, moderator: usize, reason: String) -> Self {
336        Self {
337            ip,
338            created: unix_epoch_timestamp(),
339            reason,
340            moderator,
341        }
342    }
343}
344
345#[derive(Serialize, Deserialize)]
346pub struct UserWarning {
347    pub id: usize,
348    pub created: usize,
349    pub receiver: usize,
350    pub moderator: usize,
351    pub content: String,
352}
353
354impl UserWarning {
355    /// Create a new [`UserWarning`].
356    pub fn new(user: usize, moderator: usize, content: String) -> Self {
357        Self {
358            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
359            created: unix_epoch_timestamp(),
360            receiver: user,
361            moderator,
362            content,
363        }
364    }
365}
366
367// socket
368pub mod socket {
369    use serde::{Deserialize, Serialize, de::DeserializeOwned};
370
371    #[derive(Serialize, Deserialize, PartialEq, Eq)]
372    pub enum CrudMessageType {
373        Create,
374        Delete,
375    }
376
377    #[derive(Serialize, Deserialize, PartialEq, Eq)]
378    pub enum PacketType {
379        /// A regular check to ensure the connection is still alive.
380        Ping,
381        /// General text which can be ignored.
382        Text,
383        /// A CRUD operation.
384        Crud(CrudMessageType),
385        /// A text key which identifies the socket.
386        Key,
387    }
388
389    #[derive(Serialize, Deserialize, PartialEq, Eq)]
390    pub enum SocketMethod {
391        /// Authentication and channel identification.
392        Headers,
393        /// A message was sent in the channel.
394        Message,
395        /// A message was deleted in the channel.
396        Delete,
397        /// Forward message from server to client. (Redis pubsub to ws)
398        Forward(PacketType),
399        /// A general packet from client to server. (ws to Redis pubsub)
400        Misc(PacketType),
401        /// A general packet from client to server. (ws to Redis pubsub)
402        Packet(PacketType),
403    }
404
405    #[derive(Serialize, Deserialize)]
406    pub struct SocketMessage {
407        pub method: SocketMethod,
408        pub data: String,
409    }
410
411    impl SocketMessage {
412        pub fn data<T: DeserializeOwned>(&self) -> T {
413            serde_json::from_str(&self.data).unwrap()
414        }
415    }
416
417    /// [`PacketType::Text`]
418    #[derive(Serialize, Deserialize, PartialEq, Eq)]
419    pub struct TextMessage {
420        pub text: String,
421    }
422}
423
424// properties
425pub mod properties {
426    use serde::{Deserialize, Serialize};
427    use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
428
429    #[derive(Serialize, Deserialize, PartialEq, Eq)]
430    pub enum Product {
431        /// A single Tetratto account.
432        Tetratto,
433        /// Ability to create a Shrimpcamp organization.
434        ///
435        /// $200 once.
436        Organization,
437    }
438
439    #[derive(Serialize, Deserialize)]
440    pub struct Property {
441        pub id: usize,
442        pub created: usize,
443        pub owner: usize,
444        /// A unique name for the instance. Used in the subdomain for the CNAME
445        /// record's host (aka the URL for when the instance doesn't have a custom domain).
446        pub name: String,
447        pub product: Product,
448        pub custom_domain: String,
449    }
450
451    impl Property {
452        /// Create a new [`Property`].
453        pub fn new(owner: usize, name: String, product: Product) -> Self {
454            Self {
455                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
456                created: unix_epoch_timestamp(),
457                owner,
458                name,
459                product,
460                custom_domain: String::new(),
461            }
462        }
463    }
464}
465
466// organizations
467pub mod organizations {
468    use serde::{Deserialize, Serialize};
469    use std::collections::HashMap;
470    use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
471
472    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
473    pub enum OrganizationRole {
474        Owner,
475        Custom(usize),
476        #[default]
477        Member,
478    }
479
480    #[derive(Debug, Clone, Serialize, Deserialize)]
481    pub struct Organization {
482        pub id: usize,
483        pub created: usize,
484        pub owner: usize,
485        pub name: String,
486        pub members: i32,
487        pub roles: HashMap<usize, String>,
488    }
489
490    impl Organization {
491        /// Create a new [`Organization`].
492        pub fn new(name: String, owner: usize) -> Self {
493            Self {
494                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
495                created: unix_epoch_timestamp(),
496                owner,
497                name,
498                members: 0,
499                roles: HashMap::new(),
500            }
501        }
502    }
503
504    #[derive(Debug, Clone, Serialize, Deserialize)]
505    pub struct OrganizationMembership {
506        pub id: usize,
507        pub created: usize,
508        pub organization: usize,
509        pub owner: usize,
510        pub role: OrganizationRole,
511    }
512
513    impl OrganizationMembership {
514        /// Create a new [`OrganizationMembership`].
515        pub fn new(organization: usize, owner: usize) -> Self {
516            Self {
517                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
518                created: unix_epoch_timestamp(),
519                organization,
520                owner,
521                role: OrganizationRole::Member,
522            }
523        }
524    }
525
526    #[derive(Debug, Clone, Serialize, Deserialize)]
527    pub struct PermissionsList {
528        pub id: usize,
529        pub created: usize,
530        pub owner_org: usize,
531        pub name: String,
532        pub roles: Vec<usize>,
533    }
534
535    impl PermissionsList {
536        /// Create a new [`PermissionsList`].
537        pub fn new(owner_org: usize, name: String) -> Self {
538            Self {
539                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
540                created: unix_epoch_timestamp(),
541                owner_org,
542                name,
543                roles: Vec::new(),
544            }
545        }
546    }
547}