]> Untitled Git - lemmy.git/blob - crates/api/src/lib.rs
44e7a76b59c458a25af7e4facc8ac4192b5a7b82
[lemmy.git] / crates / api / src / lib.rs
1 use actix_web::web::Data;
2 use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
3 use captcha::Captcha;
4 use lemmy_api_common::{context::LemmyContext, utils::local_site_to_slur_regex};
5 use lemmy_db_schema::source::local_site::LocalSite;
6 use lemmy_utils::{error::LemmyError, utils::slurs::check_slurs};
7 use std::io::Cursor;
8
9 mod comment;
10 mod comment_report;
11 mod community;
12 mod local_user;
13 mod post;
14 mod post_report;
15 mod private_message;
16 mod private_message_report;
17 mod site;
18
19 #[async_trait::async_trait(?Send)]
20 pub trait Perform {
21   type Response: serde::ser::Serialize + Send + Clone + Sync;
22
23   async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError>;
24 }
25
26 /// Converts the captcha to a base64 encoded wav audio file
27 pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyError> {
28   let letters = captcha.as_wav();
29
30   // Decode each wav file, concatenate the samples
31   let mut concat_samples: Vec<i16> = Vec::new();
32   let mut any_header: Option<wav::Header> = None;
33   for letter in letters {
34     let mut cursor = Cursor::new(letter.unwrap_or_default());
35     let (header, samples) = wav::read(&mut cursor)?;
36     any_header = Some(header);
37     if let Some(samples16) = samples.as_sixteen() {
38       concat_samples.extend(samples16);
39     } else {
40       return Err(LemmyError::from_message("couldnt_create_audio_captcha"));
41     }
42   }
43
44   // Encode the concatenated result as a wav file
45   let mut output_buffer = Cursor::new(vec![]);
46   let header = match any_header {
47     Some(header) => header,
48     None => return Err(LemmyError::from_message("couldnt_create_audio_captcha")),
49   };
50   let wav_write_result = wav::write(
51     header,
52     &wav::BitDepth::Sixteen(concat_samples),
53     &mut output_buffer,
54   );
55   if let Err(e) = wav_write_result {
56     return Err(LemmyError::from_error_message(
57       e,
58       "couldnt_create_audio_captcha",
59     ));
60   }
61
62   Ok(base64.encode(output_buffer.into_inner()))
63 }
64
65 /// Check size of report and remove whitespace
66 pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
67   let slur_regex = &local_site_to_slur_regex(local_site);
68
69   check_slurs(reason, slur_regex)?;
70   if reason.is_empty() {
71     return Err(LemmyError::from_message("report_reason_required"));
72   }
73   if reason.chars().count() > 1000 {
74     return Err(LemmyError::from_message("report_too_long"));
75   }
76   Ok(())
77 }
78
79 #[cfg(test)]
80 mod tests {
81   use lemmy_api_common::utils::check_validator_time;
82   use lemmy_db_schema::{
83     source::{
84       instance::Instance,
85       local_user::{LocalUser, LocalUserInsertForm},
86       person::{Person, PersonInsertForm},
87       secret::Secret,
88     },
89     traits::Crud,
90     utils::build_db_pool_for_tests,
91   };
92   use lemmy_utils::{claims::Claims, settings::SETTINGS};
93   use serial_test::serial;
94
95   #[tokio::test]
96   #[serial]
97   async fn test_should_not_validate_user_token_after_password_change() {
98     let pool = &build_db_pool_for_tests().await;
99     let secret = Secret::init(pool).await.unwrap();
100     let settings = &SETTINGS.to_owned();
101
102     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
103       .await
104       .unwrap();
105
106     let new_person = PersonInsertForm::builder()
107       .name("Gerry9812".into())
108       .public_key("pubkey".to_string())
109       .instance_id(inserted_instance.id)
110       .build();
111
112     let inserted_person = Person::create(pool, &new_person).await.unwrap();
113
114     let local_user_form = LocalUserInsertForm::builder()
115       .person_id(inserted_person.id)
116       .password_encrypted("123456".to_string())
117       .build();
118
119     let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
120
121     let jwt = Claims::jwt(
122       inserted_local_user.id.0,
123       &secret.jwt_secret,
124       &settings.hostname,
125     )
126     .unwrap();
127     let claims = Claims::decode(&jwt, &secret.jwt_secret).unwrap().claims;
128     let check = check_validator_time(&inserted_local_user.validator_time, &claims);
129     assert!(check.is_ok());
130
131     // The check should fail, since the validator time is now newer than the jwt issue time
132     let updated_local_user =
133       LocalUser::update_password(pool, inserted_local_user.id, "password111")
134         .await
135         .unwrap();
136     let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
137     assert!(check_after.is_err());
138
139     let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
140     assert_eq!(1, num_deleted);
141   }
142 }