1 use actix_web::{web, web::Data};
2 use lemmy_api_structs::{
11 use lemmy_db_queries::{
13 community::{CommunityModerator_, Community_},
19 use lemmy_db_schema::{
21 community::{Community, CommunityModerator},
30 use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
31 use lemmy_db_views_actor::{
32 community_person_ban_view::CommunityPersonBanView,
33 community_view::CommunityView,
37 settings::structs::Settings,
42 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
43 use serde::Deserialize;
44 use std::{env, process::Command};
55 #[async_trait::async_trait(?Send)]
57 type Response: serde::ser::Serialize + Send;
61 context: &Data<LemmyContext>,
62 websocket_id: Option<ConnectionId>,
63 ) -> Result<Self::Response, LemmyError>;
66 pub(crate) async fn is_mod_or_admin(
69 community_id: CommunityId,
70 ) -> Result<(), LemmyError> {
71 let is_mod_or_admin = blocking(pool, move |conn| {
72 CommunityView::is_mod_or_admin(conn, person_id, community_id)
76 return Err(ApiError::err("not_a_mod_or_admin").into());
81 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
82 if !local_user_view.local_user.admin {
83 return Err(ApiError::err("not_an_admin").into());
88 pub(crate) async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
89 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
91 Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
95 pub(crate) async fn get_local_user_view_from_jwt(
98 ) -> Result<LocalUserView, LemmyError> {
99 let claims = match Claims::decode(&jwt) {
100 Ok(claims) => claims.claims,
101 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
103 let local_user_id = LocalUserId(claims.sub);
104 let local_user_view =
105 blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
106 // Check for a site ban
107 if local_user_view.person.banned {
108 return Err(ApiError::err("site_ban").into());
111 check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
116 /// Checks if user's token was issued before user's password reset.
117 pub(crate) fn check_validator_time(
118 validator_time: &chrono::NaiveDateTime,
120 ) -> Result<(), LemmyError> {
121 let user_validation_time = validator_time.timestamp();
122 if user_validation_time > claims.iat {
123 Err(ApiError::err("not_logged_in").into())
129 pub(crate) async fn get_local_user_view_from_jwt_opt(
130 jwt: &Option<String>,
132 ) -> Result<Option<LocalUserView>, LemmyError> {
134 Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
139 pub(crate) async fn get_local_user_settings_view_from_jwt(
142 ) -> Result<LocalUserSettingsView, LemmyError> {
143 let claims = match Claims::decode(&jwt) {
144 Ok(claims) => claims.claims,
145 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
147 let local_user_id = LocalUserId(claims.sub);
148 let local_user_view = blocking(pool, move |conn| {
149 LocalUserSettingsView::read(conn, local_user_id)
152 // Check for a site ban
153 if local_user_view.person.banned {
154 return Err(ApiError::err("site_ban").into());
157 check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
162 pub(crate) async fn get_local_user_settings_view_from_jwt_opt(
163 jwt: &Option<String>,
165 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
167 Some(jwt) => Ok(Some(
168 get_local_user_settings_view_from_jwt(jwt, pool).await?,
174 pub(crate) async fn check_community_ban(
176 community_id: CommunityId,
178 ) -> Result<(), LemmyError> {
180 move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
181 if blocking(pool, is_banned).await? {
182 Err(ApiError::err("community_ban").into())
188 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
190 let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
191 if !site.enable_downvotes {
192 return Err(ApiError::err("downvotes_disabled").into());
198 /// Returns a list of communities that the user moderates
199 /// or if a community_id is supplied validates the user is a moderator
200 /// of that community and returns the community id in a vec
202 /// * `person_id` - the person id of the moderator
203 /// * `community_id` - optional community id to check for moderator privileges
204 /// * `pool` - the diesel db pool
205 pub(crate) async fn collect_moderated_communities(
207 community_id: Option<CommunityId>,
209 ) -> Result<Vec<CommunityId>, LemmyError> {
210 if let Some(community_id) = community_id {
211 // if the user provides a community_id, just check for mod/admin privileges
212 is_mod_or_admin(pool, person_id, community_id).await?;
213 Ok(vec![community_id])
215 let ids = blocking(pool, move |conn: &'_ _| {
216 CommunityModerator::get_person_moderated_communities(conn, person_id)
223 pub(crate) async fn build_federated_instances(
225 ) -> Result<Option<FederatedInstances>, LemmyError> {
226 if Settings::get().federation().enabled {
227 let distinct_communities = blocking(pool, move |conn| {
228 Community::distinct_federated_communities(conn)
232 let allowed = Settings::get().get_allowed_instances();
233 let blocked = Settings::get().get_blocked_instances();
235 let mut linked = distinct_communities
237 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
238 .collect::<Result<Vec<String>, LemmyError>>()?;
240 if let Some(allowed) = allowed.as_ref() {
241 linked.extend_from_slice(allowed);
244 if let Some(blocked) = blocked.as_ref() {
245 linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
248 // Sort and remove dupes
249 linked.sort_unstable();
252 Ok(Some(FederatedInstances {
262 pub async fn match_websocket_operation(
263 context: LemmyContext,
267 ) -> Result<String, LemmyError> {
270 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
271 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
272 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
273 UserOperation::GetPersonDetails => {
274 do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
276 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
277 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
278 UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
279 UserOperation::GetPersonMentions => {
280 do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
282 UserOperation::MarkPersonMentionAsRead => {
283 do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
285 UserOperation::MarkAllAsRead => {
286 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
288 UserOperation::DeleteAccount => {
289 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
291 UserOperation::PasswordReset => {
292 do_websocket_operation::<PasswordReset>(context, id, op, data).await
294 UserOperation::PasswordChange => {
295 do_websocket_operation::<PasswordChange>(context, id, op, data).await
297 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
298 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
299 UserOperation::CommunityJoin => {
300 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
302 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
303 UserOperation::SaveUserSettings => {
304 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
306 UserOperation::GetReportCount => {
307 do_websocket_operation::<GetReportCount>(context, id, op, data).await
310 // Private Message ops
311 UserOperation::CreatePrivateMessage => {
312 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
314 UserOperation::EditPrivateMessage => {
315 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
317 UserOperation::DeletePrivateMessage => {
318 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
320 UserOperation::MarkPrivateMessageAsRead => {
321 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
323 UserOperation::GetPrivateMessages => {
324 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
328 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
329 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
330 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
331 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
332 UserOperation::GetSiteConfig => {
333 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
335 UserOperation::SaveSiteConfig => {
336 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
338 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
339 UserOperation::TransferCommunity => {
340 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
342 UserOperation::TransferSite => {
343 do_websocket_operation::<TransferSite>(context, id, op, data).await
347 UserOperation::GetCommunity => {
348 do_websocket_operation::<GetCommunity>(context, id, op, data).await
350 UserOperation::ListCommunities => {
351 do_websocket_operation::<ListCommunities>(context, id, op, data).await
353 UserOperation::CreateCommunity => {
354 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
356 UserOperation::EditCommunity => {
357 do_websocket_operation::<EditCommunity>(context, id, op, data).await
359 UserOperation::DeleteCommunity => {
360 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
362 UserOperation::RemoveCommunity => {
363 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
365 UserOperation::FollowCommunity => {
366 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
368 UserOperation::GetFollowedCommunities => {
369 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
371 UserOperation::BanFromCommunity => {
372 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
374 UserOperation::AddModToCommunity => {
375 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
379 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
380 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
381 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
382 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
383 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
384 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
385 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
386 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
387 UserOperation::CreatePostLike => {
388 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
390 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
391 UserOperation::CreatePostReport => {
392 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
394 UserOperation::ListPostReports => {
395 do_websocket_operation::<ListPostReports>(context, id, op, data).await
397 UserOperation::ResolvePostReport => {
398 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
402 UserOperation::CreateComment => {
403 do_websocket_operation::<CreateComment>(context, id, op, data).await
405 UserOperation::EditComment => {
406 do_websocket_operation::<EditComment>(context, id, op, data).await
408 UserOperation::DeleteComment => {
409 do_websocket_operation::<DeleteComment>(context, id, op, data).await
411 UserOperation::RemoveComment => {
412 do_websocket_operation::<RemoveComment>(context, id, op, data).await
414 UserOperation::MarkCommentAsRead => {
415 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
417 UserOperation::SaveComment => {
418 do_websocket_operation::<SaveComment>(context, id, op, data).await
420 UserOperation::GetComments => {
421 do_websocket_operation::<GetComments>(context, id, op, data).await
423 UserOperation::CreateCommentLike => {
424 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
426 UserOperation::CreateCommentReport => {
427 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
429 UserOperation::ListCommentReports => {
430 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
432 UserOperation::ResolveCommentReport => {
433 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
438 async fn do_websocket_operation<'a, 'b, Data>(
439 context: LemmyContext,
443 ) -> Result<String, LemmyError>
445 for<'de> Data: Deserialize<'de> + 'a,
448 let parsed_data: Data = serde_json::from_str(&data)?;
449 let res = parsed_data
450 .perform(&web::Data::new(context), Some(id))
452 serialize_websocket_message(&op, &res)
455 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
456 let mut built_text = String::new();
458 // Building proper speech text for espeak
459 for mut c in captcha.chars() {
460 let new_str = if c.is_alphabetic() {
461 if c.is_lowercase() {
462 c.make_ascii_uppercase();
463 format!("lower case {} ... ", c)
465 c.make_ascii_uppercase();
466 format!("capital {} ... ", c)
472 built_text.push_str(&new_str);
475 espeak_wav_base64(&built_text)
478 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
479 // Make a temp file path
480 let uuid = uuid::Uuid::new_v4().to_string();
481 let file_path = format!(
482 "{}/lemmy_espeak_{}.wav",
483 env::temp_dir().to_string_lossy(),
487 // Write the wav file
488 Command::new("espeak")
494 // Read the wav file bytes
495 let bytes = std::fs::read(&file_path)?;
498 std::fs::remove_file(file_path)?;
501 let base64 = base64::encode(bytes);
506 /// Checks the password length
507 pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
509 Err(ApiError::err("invalid_password").into())
517 use crate::{captcha_espeak_wav_base64, check_validator_time};
518 use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
519 use lemmy_db_schema::source::{
520 local_user::{LocalUser, LocalUserForm},
521 person::{Person, PersonForm},
523 use lemmy_utils::claims::Claims;
526 fn test_should_not_validate_user_token_after_password_change() {
527 let conn = establish_unpooled_connection();
529 let new_person = PersonForm {
530 name: "Gerry9812".into(),
531 preferred_username: None,
543 last_refreshed_at: None,
545 shared_inbox_url: None,
548 let inserted_person = Person::create(&conn, &new_person).unwrap();
550 let local_user_form = LocalUserForm {
551 person_id: inserted_person.id,
553 matrix_user_id: None,
554 password_encrypted: "123456".to_string(),
558 default_sort_type: None,
559 default_listing_type: None,
562 send_notifications_to_email: None,
565 let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
567 let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
568 let claims = Claims::decode(&jwt).unwrap().claims;
569 let check = check_validator_time(&inserted_local_user.validator_time, &claims);
570 assert!(check.is_ok());
572 // The check should fail, since the validator time is now newer than the jwt issue time
573 let updated_local_user =
574 LocalUser::update_password(&conn, inserted_local_user.id, &"password111").unwrap();
575 let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
576 assert!(check_after.is_err());
578 let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
579 assert_eq!(1, num_deleted);
584 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())