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    /// The number of users the user can register to an organization.
165    #[serde(default)]
166    pub org_user_register_credits: i32,
167}
168
169impl Default for User {
170    fn default() -> Self {
171        Self::new("<unknown>".to_string(), String::new())
172    }
173}
174
175impl User {
176    /// Create a new [`User`].
177    pub fn new(username: String, password: String) -> Self {
178        let salt = salt();
179        let password = hash_salted(password, salt.clone());
180        let created = unix_epoch_timestamp();
181
182        Self {
183            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
184            created,
185            username,
186            password,
187            salt,
188            settings: UserSettings::default(),
189            tokens: Vec::new(),
190            permissions: Vec::new(),
191            is_verified: false,
192            notification_count: 0,
193            totp: String::new(),
194            recovery_codes: Vec::new(),
195            stripe_id: String::new(),
196            ban_reason: String::new(),
197            is_deactivated: false,
198            ban_expire: 0,
199            checkouts: Vec::new(),
200            last_policy_consent: created,
201            linked_accounts: UserLinkedAccounts::default(),
202            badges: Vec::new(),
203            principal_org: 0,
204            org_as_tenant: false,
205            org_creation_credits: 0,
206            org_user_register_credits: 0,
207        }
208    }
209
210    /// Deleted user profile.
211    pub fn deleted() -> Self {
212        Self {
213            username: "<deleted>".to_string(),
214            id: 0,
215            ..Default::default()
216        }
217    }
218
219    /// Banned user profile.
220    pub fn banned() -> Self {
221        Self {
222            username: "<banned>".to_string(),
223            id: 0,
224            permissions: vec![UserPermission::Banned],
225            ..Default::default()
226        }
227    }
228
229    /// Create a new token
230    ///
231    /// # Returns
232    /// `(unhashed id, token)`
233    pub fn create_token(ip: &str) -> (String, Token) {
234        let unhashed = tetratto_shared::hash::uuid();
235        (
236            unhashed.clone(),
237            (
238                ip.to_string(),
239                tetratto_shared::hash::hash(unhashed),
240                unix_epoch_timestamp(),
241            ),
242        )
243    }
244
245    /// Check if the given password is correct for the user.
246    pub fn check_password(&self, against: String) -> bool {
247        self.password == hash_salted(against, self.salt.clone())
248    }
249
250    /// Get a [`TOTP`] from the profile's `totp` secret value.
251    pub fn totp(&self, issuer: Option<String>) -> Option<TOTP> {
252        if self.totp.is_empty() {
253            return None;
254        }
255
256        TOTP::new(
257            totp_rs::Algorithm::SHA1,
258            6,
259            1,
260            30,
261            self.totp.as_bytes().to_owned(),
262            Some(issuer.unwrap_or("tetratto!".to_string())),
263            self.username.clone(),
264        )
265        .ok()
266    }
267
268    /// Clean the struct for public viewing.
269    pub fn clean(&mut self) {
270        self.password = String::new();
271        self.salt = String::new();
272
273        self.tokens = Vec::new();
274
275        self.recovery_codes = Vec::new();
276        self.totp = String::new();
277
278        self.settings = UserSettings::default();
279        self.stripe_id = String::new();
280    }
281}
282
283#[derive(Debug, Serialize, Deserialize)]
284pub struct Notification {
285    pub id: usize,
286    pub created: usize,
287    pub title: String,
288    pub content: String,
289    pub owner: usize,
290    pub read: bool,
291    pub tag: String,
292}
293
294impl Notification {
295    /// Create a new [`Notification`].
296    pub fn new(title: String, content: String, owner: usize) -> Self {
297        Self {
298            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
299            created: unix_epoch_timestamp(),
300            title,
301            content,
302            owner,
303            read: false,
304            tag: String::new(),
305        }
306    }
307}
308
309#[derive(Serialize, Deserialize)]
310pub struct AuditLogEntry {
311    pub id: usize,
312    pub created: usize,
313    pub moderator: usize,
314    pub content: String,
315}
316
317impl AuditLogEntry {
318    /// Create a new [`AuditLogEntry`].
319    pub fn new(moderator: usize, content: String) -> Self {
320        Self {
321            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
322            created: unix_epoch_timestamp(),
323            moderator,
324            content,
325        }
326    }
327}
328
329#[derive(Serialize, Deserialize)]
330pub struct IpBan {
331    pub ip: String,
332    pub created: usize,
333    pub reason: String,
334    pub moderator: usize,
335}
336
337impl IpBan {
338    /// Create a new [`IpBan`].
339    pub fn new(ip: String, moderator: usize, reason: String) -> Self {
340        Self {
341            ip,
342            created: unix_epoch_timestamp(),
343            reason,
344            moderator,
345        }
346    }
347}
348
349#[derive(Serialize, Deserialize)]
350pub struct UserWarning {
351    pub id: usize,
352    pub created: usize,
353    pub receiver: usize,
354    pub moderator: usize,
355    pub content: String,
356}
357
358impl UserWarning {
359    /// Create a new [`UserWarning`].
360    pub fn new(user: usize, moderator: usize, content: String) -> Self {
361        Self {
362            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
363            created: unix_epoch_timestamp(),
364            receiver: user,
365            moderator,
366            content,
367        }
368    }
369}
370
371// socket
372pub mod socket {
373    use serde::{Deserialize, Serialize, de::DeserializeOwned};
374
375    #[derive(Serialize, Deserialize, PartialEq, Eq)]
376    pub enum CrudMessageType {
377        Create,
378        Delete,
379    }
380
381    #[derive(Serialize, Deserialize, PartialEq, Eq)]
382    pub enum PacketType {
383        /// A regular check to ensure the connection is still alive.
384        Ping,
385        /// General text which can be ignored.
386        Text,
387        /// A CRUD operation.
388        Crud(CrudMessageType),
389        /// A text key which identifies the socket.
390        Key,
391    }
392
393    #[derive(Serialize, Deserialize, PartialEq, Eq)]
394    pub enum SocketMethod {
395        /// Authentication and channel identification.
396        Headers,
397        /// A message was sent in the channel.
398        Message,
399        /// A message was deleted in the channel.
400        Delete,
401        /// Forward message from server to client. (Redis pubsub to ws)
402        Forward(PacketType),
403        /// A general packet from client to server. (ws to Redis pubsub)
404        Misc(PacketType),
405        /// A general packet from client to server. (ws to Redis pubsub)
406        Packet(PacketType),
407    }
408
409    #[derive(Serialize, Deserialize)]
410    pub struct SocketMessage {
411        pub method: SocketMethod,
412        pub data: String,
413    }
414
415    impl SocketMessage {
416        pub fn data<T: DeserializeOwned>(&self) -> T {
417            serde_json::from_str(&self.data).unwrap()
418        }
419    }
420
421    /// [`PacketType::Text`]
422    #[derive(Serialize, Deserialize, PartialEq, Eq)]
423    pub struct TextMessage {
424        pub text: String,
425    }
426}
427
428// properties
429pub mod properties {
430    use serde::{Deserialize, Serialize};
431    use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
432
433    #[derive(Serialize, Deserialize, PartialEq, Eq)]
434    pub enum Product {
435        /// A single Tetratto account.
436        Tetratto,
437        /// Ability to create a Shrimpcamp organization.
438        ///
439        /// $200 once.
440        Organization,
441        /// Ability to register a user to an organization.
442        ///
443        /// $4 once.
444        UserReg,
445    }
446
447    #[derive(Serialize, Deserialize)]
448    pub struct Property {
449        pub id: usize,
450        pub created: usize,
451        pub owner: usize,
452        /// A unique name for the instance. Used in the subdomain for the CNAME
453        /// record's host (aka the URL for when the instance doesn't have a custom domain).
454        pub name: String,
455        pub product: Product,
456        pub custom_domain: String,
457    }
458
459    impl Property {
460        /// Create a new [`Property`].
461        pub fn new(owner: usize, name: String, product: Product) -> Self {
462            Self {
463                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
464                created: unix_epoch_timestamp(),
465                owner,
466                name,
467                product,
468                custom_domain: String::new(),
469            }
470        }
471    }
472}
473
474// organizations
475pub mod organizations {
476    use serde::{Deserialize, Serialize};
477    use std::collections::HashMap;
478    use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
479
480    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
481    pub enum OrganizationRole {
482        Owner,
483        Custom(usize),
484        #[default]
485        Member,
486    }
487
488    #[derive(Debug, Clone, Serialize, Deserialize)]
489    pub struct Organization {
490        pub id: usize,
491        pub created: usize,
492        pub owner: usize,
493        pub name: String,
494        pub members: i32,
495        pub roles: HashMap<usize, String>,
496    }
497
498    impl Organization {
499        /// Create a new [`Organization`].
500        pub fn new(name: String, owner: usize) -> Self {
501            Self {
502                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
503                created: unix_epoch_timestamp(),
504                owner,
505                name,
506                members: 0,
507                roles: HashMap::new(),
508            }
509        }
510    }
511
512    #[derive(Debug, Clone, Serialize, Deserialize)]
513    pub struct OrganizationMembership {
514        pub id: usize,
515        pub created: usize,
516        pub organization: usize,
517        pub owner: usize,
518        pub role: OrganizationRole,
519    }
520
521    impl OrganizationMembership {
522        /// Create a new [`OrganizationMembership`].
523        pub fn new(organization: usize, owner: usize) -> Self {
524            Self {
525                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
526                created: unix_epoch_timestamp(),
527                organization,
528                owner,
529                role: OrganizationRole::Member,
530            }
531        }
532    }
533
534    #[derive(Debug, Clone, Serialize, Deserialize)]
535    pub struct PermissionsList {
536        pub id: usize,
537        pub created: usize,
538        pub owner_org: usize,
539        pub name: String,
540        pub roles: Vec<usize>,
541    }
542
543    impl PermissionsList {
544        /// Create a new [`PermissionsList`].
545        pub fn new(owner_org: usize, name: String) -> Self {
546            Self {
547                id: Snowflake::new().to_string().parse::<usize>().unwrap(),
548                created: unix_epoch_timestamp(),
549                owner_org,
550                name,
551                roles: Vec::new(),
552            }
553        }
554    }
555}