1use hcaptcha_no_wasm::Hcaptcha;
2use std::collections::{BTreeMap, HashMap};
3use totp_rs::TOTP;
4
5use axum::{
6 http::StatusCode,
7 response::{IntoResponse, Response},
8 Json,
9};
10
11use serde::{Serialize, Deserialize};
12use databeam::prelude::DefaultReturn;
13
14use crate::layout::LayoutComponent;
15
16#[derive(Serialize, Deserialize, Clone, Debug)]
18pub struct Profile {
19 pub id: String,
21 pub username: String,
23 pub password: String,
25 pub salt: String,
27 pub tokens: Vec<String>,
29 pub ips: Vec<String>,
31 pub token_context: Vec<TokenContext>,
33 pub metadata: ProfileMetadata,
35 pub badges: Vec<(String, String, String)>,
39 pub group: i32,
41 pub joined: u128,
43 pub tier: i32,
45 pub labels: Vec<i64>,
49 pub coins: i32,
51 pub links: BTreeMap<String, String>,
53 pub layout: LayoutComponent,
55 pub question_count: usize,
57 pub response_count: usize,
59 #[serde(default)]
61 pub totp: String,
62 #[serde(default)]
64 pub recovery_codes: Vec<String>,
65 pub notification_count: usize,
67 pub inbox_count: usize,
69}
70
71impl Profile {
72 pub fn global() -> Self {
74 Self {
75 username: "@".to_string(),
76 id: "@".to_string(),
77 ..Default::default()
78 }
79 }
80
81 pub fn system() -> Self {
83 Self {
84 username: "system".to_string(),
85 id: "0".to_string(),
86 ..Default::default()
87 }
88 }
89
90 pub fn anonymous(tag: String) -> Self {
92 Self {
93 username: "anonymous".to_string(),
94 id: tag,
95 ..Default::default()
96 }
97 }
98
99 pub fn anonymous_tag(input: &str) -> (bool, String, String, String) {
104 if (input != "anonymous") && !input.starts_with("anonymous#") {
105 return (false, String::new(), String::new(), input.to_string());
107 }
108
109 let split: Vec<&str> = input.split("#").collect();
111 (
112 true,
113 split.get(1).unwrap_or(&"unknown").to_string(),
114 split.get(0).unwrap().to_string(),
115 input.to_string(),
116 )
117 }
118
119 pub fn clean(&mut self) -> () {
121 self.ips = Vec::new();
122 self.tokens = Vec::new();
123 self.token_context = Vec::new();
124 self.salt = String::new();
125 self.password = String::new();
126 self.metadata = ProfileMetadata::default();
127 self.totp = String::new();
128 self.recovery_codes = Vec::new();
129 }
130
131 pub fn token_context_from_token(&self, token: &str) -> TokenContext {
133 let token = databeam::utility::hash(token.to_string());
134
135 if let Some(pos) = self.tokens.iter().position(|t| *t == token) {
136 if let Some(ctx) = self.token_context.get(pos) {
137 return ctx.to_owned();
138 }
139
140 return TokenContext::default();
141 }
142
143 return TokenContext::default();
144 }
145
146 pub fn has_label(&self, id: i64) -> bool {
150 self.labels.contains(&id)
151 }
152
153 pub fn totp(&self, issuer: Option<String>) -> Option<TOTP> {
157 if self.totp.is_empty() {
158 return None;
159 }
160
161 match TOTP::new(
162 totp_rs::Algorithm::SHA1,
163 6,
164 1,
165 30,
166 self.totp.as_bytes().to_owned(),
167 Some(issuer.unwrap_or("Rainbeam".to_string())),
168 self.username.clone(),
169 ) {
170 Ok(t) => Some(t),
171 Err(_) => None,
172 }
173 }
174}
175
176impl Default for Profile {
177 fn default() -> Self {
178 Self {
179 id: String::new(),
180 username: String::new(),
181 password: String::new(),
182 salt: String::new(),
183 tokens: Vec::new(),
184 ips: Vec::new(),
185 token_context: Vec::new(),
186 metadata: ProfileMetadata::default(),
187 badges: Vec::new(),
188 group: 0,
189 joined: databeam::utility::unix_epoch_timestamp(),
190 tier: 0,
191 labels: Vec::new(),
192 coins: 0,
193 links: BTreeMap::new(),
194 layout: LayoutComponent::default(),
195 question_count: 0,
196 response_count: 0,
197 totp: String::new(),
198 recovery_codes: Vec::new(),
199 notification_count: 0,
200 inbox_count: 0,
201 }
202 }
203}
204
205#[derive(Serialize, Deserialize, Clone, Debug)]
206pub struct TokenContext {
207 #[serde(default)]
208 pub app: Option<String>,
209 #[serde(default)]
210 pub permissions: Option<Vec<TokenPermission>>,
211 #[serde(default)]
212 pub timestamp: u128,
213}
214
215#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
216pub enum TokenPermission {
217 ManageAssets,
219 ManageProfile,
221 ManageAccount,
223 Moderator,
225 GenerateTokens,
229}
230
231impl TokenContext {
232 pub fn app_name(&self) -> String {
236 if let Some(ref name) = self.app {
237 return name.to_string();
238 }
239
240 String::new()
241 }
242
243 pub fn can_do(&self, permission: TokenPermission) -> bool {
247 if let Some(ref permissions) = self.permissions {
248 return permissions.contains(&permission);
249 }
250
251 return true;
252 }
253}
254
255impl Default for TokenContext {
256 fn default() -> Self {
257 Self {
258 app: None,
259 permissions: None,
260 timestamp: databeam::utility::unix_epoch_timestamp(),
261 }
262 }
263}
264
265#[derive(Serialize, Deserialize, Clone, Debug)]
266pub struct ProfileMetadata {
267 #[serde(default)]
268 pub email: String,
269 #[serde(default)]
270 pub policy_consent: bool,
271 #[serde(default)]
273 pub kv: HashMap<String, String>,
274}
275
276impl ProfileMetadata {
277 pub fn exists(&self, key: &str) -> bool {
279 if let Some(ref value) = self.kv.get(key) {
280 if value.is_empty() {
281 return false;
282 }
283
284 return true;
285 }
286
287 false
288 }
289
290 pub fn is_true(&self, key: &str) -> bool {
292 if !self.exists(key) {
293 return false;
294 }
295
296 self.kv.get(key).unwrap() == "true"
297 }
298
299 pub fn soft_get(&self, key: &str) -> String {
301 if !self.exists(key) {
302 return String::new();
303 }
304
305 self.kv.get(key).unwrap().to_owned()
306 }
307
308 pub fn check(&self) -> bool {
314 for field in &self.kv {
315 if field.0 == "sparkler:custom_css" {
316 if field.1.len() > 64 * 128 {
318 return false;
319 }
320
321 continue;
322 }
323
324 if field.1.len() > 64 * 64 {
325 return false;
326 }
327 }
328
329 true
330 }
331}
332
333impl ProfileMetadata {
334 pub fn from_email(email: String) -> Self {
335 Self {
336 email,
337 policy_consent: true,
338 kv: HashMap::new(),
339 }
340 }
341}
342
343impl Default for ProfileMetadata {
344 fn default() -> Self {
345 Self {
346 email: String::new(),
347 policy_consent: true, kv: HashMap::new(),
349 }
350 }
351}
352
353#[derive(Serialize, Deserialize, Clone, Debug)]
355pub struct UserFollow {
356 pub user: String,
358 pub following: String,
360}
361
362#[derive(Serialize, Deserialize, Clone, Debug)]
364pub struct Notification {
365 pub title: String,
367 pub content: String,
369 pub address: String,
371 pub timestamp: u128,
373 pub id: String,
375 pub recipient: String,
377}
378
379#[derive(Serialize, Deserialize, Clone, Debug)]
381pub struct Warning {
382 pub id: String,
384 pub content: String,
386 pub timestamp: u128,
388 pub recipient: String,
390 pub moderator: Box<Profile>,
392}
393
394#[derive(Serialize, Deserialize, Clone, Debug)]
396pub struct IpBan {
397 pub id: String,
399 pub ip: String,
401 pub reason: String,
403 pub moderator: Box<Profile>,
405 pub timestamp: u128,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
411pub enum RelationshipStatus {
412 Unknown,
414 Blocked,
416 Pending,
418 Friends,
420}
421
422impl Default for RelationshipStatus {
423 fn default() -> Self {
424 Self::Unknown
425 }
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct Relationship {
434 pub one: Profile,
436 pub two: Profile,
438 pub status: RelationshipStatus,
440 pub timestamp: u128,
442}
443
444#[derive(Serialize, Deserialize, Clone, Debug)]
446pub struct IpBlock {
447 pub id: String,
449 pub ip: String,
451 pub user: String,
453 pub context: String,
455 pub timestamp: u128,
457}
458
459pub use crate::permissions::FinePermission;
460
461#[derive(Serialize, Deserialize, Clone, Debug)]
463pub struct Group {
464 pub name: String,
465 pub id: i32,
466 pub permissions: FinePermission,
467}
468
469impl Default for Group {
470 fn default() -> Self {
471 Self {
472 name: "default".to_string(),
473 id: 0,
474 permissions: FinePermission::default(),
475 }
476 }
477}
478
479pub const RESERVED_LABEL_QUARANTINE: i64 = -1;
480
481#[derive(Serialize, Deserialize, Clone, Debug)]
483pub struct UserLabel {
484 pub id: i64,
486 pub name: String,
488 pub timestamp: u128,
490 pub creator: String,
492}
493
494#[derive(Serialize, Deserialize, Clone, Debug)]
496pub struct Transaction {
497 pub id: String,
499 pub amount: i32,
501 pub item: String,
503 pub timestamp: u128,
505 pub customer: String,
507 pub merchant: String,
509}
510
511#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
513pub enum ItemType {
514 #[serde(alias = "text")]
515 Text,
516 #[serde(alias = "usertheme")]
517 UserTheme,
518 #[serde(alias = "module")]
519 Module,
520 #[serde(alias = "layout")]
521 Layout,
522}
523
524impl Default for ItemType {
525 fn default() -> Self {
526 Self::Text
527 }
528}
529
530impl ToString for ItemType {
531 fn to_string(&self) -> String {
532 format!("{:?}", self)
539 }
540}
541
542#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
544pub enum ItemStatus {
545 Rejected,
547 Pending,
549 Approved,
551 Featured,
553}
554
555impl Default for ItemStatus {
556 fn default() -> Self {
557 Self::Approved
558 }
559}
560
561impl ToString for ItemStatus {
562 fn to_string(&self) -> String {
563 match self {
564 ItemStatus::Rejected => "Rejected".to_string(),
565 ItemStatus::Pending => "Pending".to_string(),
566 ItemStatus::Approved => "Approved".to_string(),
567 ItemStatus::Featured => "Featured".to_string(),
568 }
569 }
570}
571
572#[derive(Serialize, Deserialize, Clone, Debug)]
574pub struct Item {
575 pub id: String,
577 pub name: String,
579 pub description: String,
581 pub cost: i32,
586 pub content: String,
588 pub r#type: ItemType,
590 pub status: ItemStatus,
592 pub timestamp: u128,
594 pub creator: String,
596}
597
598#[derive(Serialize, Deserialize, Debug, Hcaptcha)]
600pub struct ProfileCreate {
601 pub username: String,
602 pub password: String,
603 pub policy_consent: bool,
604 #[captcha]
605 pub token: String,
606}
607
608#[derive(Serialize, Deserialize, Debug, Hcaptcha)]
609pub struct ProfileLogin {
610 pub username: String,
611 pub password: String,
612 #[captcha]
613 pub token: String,
614 #[serde(default)]
615 pub totp: String,
616}
617
618#[derive(Serialize, Deserialize, Debug)]
619pub struct SetProfileMetadata {
620 pub metadata: ProfileMetadata,
621}
622
623#[derive(Serialize, Deserialize, Debug)]
624pub struct SetProfileBadges {
625 pub badges: Vec<(String, String, String)>,
626}
627
628#[derive(Serialize, Deserialize, Debug)]
629pub struct SetProfileLabels {
630 pub labels: Vec<i64>,
631}
632
633#[derive(Serialize, Deserialize, Debug)]
634pub struct SetProfileLinks {
635 pub links: BTreeMap<String, String>,
636}
637
638#[derive(Serialize, Deserialize, Debug)]
639pub struct SetProfileLayout {
640 pub layout: LayoutComponent,
641}
642
643#[derive(Serialize, Deserialize, Debug)]
644pub struct RenderLayout {
645 pub layout: LayoutComponent,
646}
647
648#[derive(Serialize, Deserialize, Debug)]
649pub struct SetProfileGroup {
650 pub group: i32,
651}
652
653#[derive(Serialize, Deserialize, Debug)]
654pub struct SetProfileTier {
655 pub tier: i32,
656}
657
658#[derive(Serialize, Deserialize, Debug)]
659pub struct SetProfileCoins {
660 pub coins: i32,
661}
662
663#[derive(Serialize, Deserialize, Debug)]
664pub struct SetProfilePassword {
665 pub password: String,
666 pub new_password: String,
667}
668
669#[derive(Serialize, Deserialize, Debug)]
670pub struct SetProfileUsername {
671 pub password: String,
672 pub new_name: String,
673}
674
675#[derive(Serialize, Deserialize, Debug)]
676pub struct NotificationCreate {
677 pub title: String,
678 pub content: String,
679 pub address: String,
680 pub recipient: String,
681}
682
683#[derive(Serialize, Deserialize, Debug)]
684pub struct WarningCreate {
685 pub content: String,
686 pub recipient: String,
687}
688
689#[derive(Serialize, Deserialize, Debug)]
690pub struct IpBanCreate {
691 pub ip: String,
692 pub reason: String,
693}
694
695#[derive(Serialize, Deserialize, Debug)]
696pub struct IpBlockCreate {
697 pub ip: String,
698 pub context: String,
699}
700
701#[derive(Serialize, Deserialize, Debug)]
702pub struct TransactionCreate {
703 pub merchant: String,
705 pub item: String,
706 pub amount: i32,
707}
708
709#[derive(Serialize, Deserialize, Debug)]
710pub struct ItemCreate {
711 pub name: String,
712 pub description: String,
713 pub content: String,
714 pub cost: i32,
715 pub r#type: ItemType,
716}
717
718#[derive(Serialize, Deserialize, Debug)]
719pub struct ItemEdit {
720 pub name: String,
721 pub description: String,
722 pub cost: i32,
723}
724
725#[derive(Serialize, Deserialize, Debug)]
726pub struct ItemEditContent {
727 pub content: String,
728}
729
730#[derive(Serialize, Deserialize, Debug)]
731pub struct SetItemStatus {
732 pub status: ItemStatus,
733}
734
735#[derive(Serialize, Deserialize, Debug)]
736pub struct TOTPDisable {
737 pub totp: String,
738}
739
740#[derive(Serialize, Deserialize, Debug)]
741pub struct LabelCreate {
742 pub id: i64,
743 pub name: String,
744}
745
746#[derive(Debug, PartialEq, Eq)]
748pub enum DatabaseError {
749 ModulesMustBeOffsale,
750 IncorrectPassword,
751 UsernameTaken,
752 TooExpensive,
753 MustBeUnique,
754 OutOfScope,
755 NotAllowed,
756 ValueError,
757 NotFound,
758 TooLong,
759 Other,
760}
761
762impl DatabaseError {
763 pub fn to_string(&self) -> String {
764 use DatabaseError::*;
765 match self {
766 ModulesMustBeOffsale => String::from("Modules must be off-sale."),
767 IncorrectPassword => String::from("Given password is incorrect."),
768 UsernameTaken => String::from("This username is already in use."),
769 TooExpensive => String::from("You cannot afford to do this."),
770 MustBeUnique => String::from("One of the given values must be unique."),
771 OutOfScope => String::from(
772 "Cannot generate tokens with permissions the provided token doesn't have.",
773 ),
774 NotAllowed => String::from("You are not allowed to access this resource."),
775 ValueError => String::from("One of the field values given is invalid."),
776 NotFound => String::from("No asset with this ID could be found."),
777 TooLong => String::from("Given data is too long."),
778 _ => String::from("An unspecified error has occured"),
779 }
780 }
781
782 pub fn to_json<T: Default>(&self) -> DefaultReturn<T> {
783 DefaultReturn {
784 success: false,
785 message: self.to_string(),
786 payload: T::default(),
787 }
788 }
789}
790
791impl IntoResponse for DatabaseError {
792 fn into_response(self) -> Response {
793 use crate::model::DatabaseError::*;
794 match self {
795 NotAllowed => (
796 StatusCode::UNAUTHORIZED,
797 Json(DefaultReturn::<u16> {
798 success: false,
799 message: self.to_string(),
800 payload: 401,
801 }),
802 )
803 .into_response(),
804 NotFound => (
805 StatusCode::NOT_FOUND,
806 Json(DefaultReturn::<u16> {
807 success: false,
808 message: self.to_string(),
809 payload: 404,
810 }),
811 )
812 .into_response(),
813 _ => (
814 StatusCode::INTERNAL_SERVER_ERROR,
815 Json(DefaultReturn::<u16> {
816 success: false,
817 message: self.to_string(),
818 payload: 500,
819 }),
820 )
821 .into_response(),
822 }
823 }
824}