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