1 use actix_web::{web, web::Data};
2 use lemmy_db_queries::{
4 community::{CommunityModerator_, Community_},
6 user::UserSafeSettings_,
11 use lemmy_db_schema::source::{
12 community::{Community, CommunityModerator},
15 user::{UserSafeSettings, User_},
17 use lemmy_db_views_actor::{
18 community_user_ban_view::CommunityUserBanView,
19 community_view::CommunityView,
21 use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*, websocket::*};
22 use lemmy_utils::{claims::Claims, settings::Settings, ApiError, ConnectionId, LemmyError};
23 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
24 use serde::Deserialize;
25 use std::{env, process::Command};
36 #[async_trait::async_trait(?Send)]
38 type Response: serde::ser::Serialize + Send;
42 context: &Data<LemmyContext>,
43 websocket_id: Option<ConnectionId>,
44 ) -> Result<Self::Response, LemmyError>;
47 pub(crate) async fn is_mod_or_admin(
51 ) -> Result<(), LemmyError> {
52 let is_mod_or_admin = blocking(pool, move |conn| {
53 CommunityView::is_mod_or_admin(conn, user_id, community_id)
57 return Err(ApiError::err("not_a_mod_or_admin").into());
61 pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
62 let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
64 return Err(ApiError::err("not_an_admin").into());
69 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
70 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
72 Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
76 pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
77 let claims = match Claims::decode(&jwt) {
78 Ok(claims) => claims.claims,
79 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
81 let user_id = claims.id;
82 let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
83 // Check for a site ban
85 return Err(ApiError::err("site_ban").into());
87 // if user's token was issued before user's password reset.
88 let user_validation_time = user.validator_time.timestamp_millis() / 1000;
89 if user_validation_time > claims.iat {
90 return Err(ApiError::err("not_logged_in").into());
95 pub(crate) async fn get_user_from_jwt_opt(
98 ) -> Result<Option<User_>, LemmyError> {
100 Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
105 pub(crate) async fn get_user_safe_settings_from_jwt(
108 ) -> Result<UserSafeSettings, LemmyError> {
109 let claims = match Claims::decode(&jwt) {
110 Ok(claims) => claims.claims,
111 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
113 let user_id = claims.id;
114 let user = blocking(pool, move |conn| UserSafeSettings::read(conn, user_id)).await??;
115 // Check for a site ban
117 return Err(ApiError::err("site_ban").into());
119 // if user's token was issued before user's password reset.
120 let user_validation_time = user.validator_time.timestamp_millis() / 1000;
121 if user_validation_time >= claims.iat {
122 return Err(ApiError::err("not_logged_in").into());
127 pub(crate) async fn get_user_safe_settings_from_jwt_opt(
128 jwt: &Option<String>,
130 ) -> Result<Option<UserSafeSettings>, LemmyError> {
132 Some(jwt) => Ok(Some(get_user_safe_settings_from_jwt(jwt, pool).await?)),
137 pub(crate) async fn check_community_ban(
141 ) -> Result<(), LemmyError> {
142 let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
143 if blocking(pool, is_banned).await? {
144 Err(ApiError::err("community_ban").into())
150 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
152 let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
153 if !site.enable_downvotes {
154 return Err(ApiError::err("downvotes_disabled").into());
160 /// Returns a list of communities that the user moderates
161 /// or if a community_id is supplied validates the user is a moderator
162 /// of that community and returns the community id in a vec
164 /// * `user_id` - the user id of the moderator
165 /// * `community_id` - optional community id to check for moderator privileges
166 /// * `pool` - the diesel db pool
167 pub(crate) async fn collect_moderated_communities(
169 community_id: Option<i32>,
171 ) -> Result<Vec<i32>, LemmyError> {
172 if let Some(community_id) = community_id {
173 // if the user provides a community_id, just check for mod/admin privileges
174 is_mod_or_admin(pool, user_id, community_id).await?;
175 Ok(vec![community_id])
177 let ids = blocking(pool, move |conn: &'_ _| {
178 CommunityModerator::get_user_moderated_communities(conn, user_id)
185 pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
186 if let Some(Some(item)) = &item {
187 if Url::parse(item).is_err() {
188 return Err(ApiError::err("invalid_url").into());
194 pub(crate) async fn build_federated_instances(
196 ) -> Result<Option<FederatedInstances>, LemmyError> {
197 if Settings::get().federation.enabled {
198 let distinct_communities = blocking(pool, move |conn| {
199 Community::distinct_federated_communities(conn)
203 let allowed = Settings::get().get_allowed_instances();
204 let blocked = Settings::get().get_blocked_instances();
206 let mut linked = distinct_communities
208 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
209 .collect::<Result<Vec<String>, LemmyError>>()?;
211 linked.extend_from_slice(&allowed);
212 linked.retain(|a| !blocked.contains(a) && !a.eq("") && !a.eq(&Settings::get().hostname));
214 // Sort and remove dupes
215 linked.sort_unstable();
218 Ok(Some(FederatedInstances {
228 pub async fn match_websocket_operation(
229 context: LemmyContext,
233 ) -> Result<String, LemmyError> {
236 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
237 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
238 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
239 UserOperation::GetUserDetails => {
240 do_websocket_operation::<GetUserDetails>(context, id, op, data).await
242 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
243 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
244 UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
245 UserOperation::GetUserMentions => {
246 do_websocket_operation::<GetUserMentions>(context, id, op, data).await
248 UserOperation::MarkUserMentionAsRead => {
249 do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
251 UserOperation::MarkAllAsRead => {
252 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
254 UserOperation::DeleteAccount => {
255 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
257 UserOperation::PasswordReset => {
258 do_websocket_operation::<PasswordReset>(context, id, op, data).await
260 UserOperation::PasswordChange => {
261 do_websocket_operation::<PasswordChange>(context, id, op, data).await
263 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
264 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
265 UserOperation::CommunityJoin => {
266 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
268 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
269 UserOperation::SaveUserSettings => {
270 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
272 UserOperation::GetReportCount => {
273 do_websocket_operation::<GetReportCount>(context, id, op, data).await
276 // Private Message ops
277 UserOperation::CreatePrivateMessage => {
278 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
280 UserOperation::EditPrivateMessage => {
281 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
283 UserOperation::DeletePrivateMessage => {
284 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
286 UserOperation::MarkPrivateMessageAsRead => {
287 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
289 UserOperation::GetPrivateMessages => {
290 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
294 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
295 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
296 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
297 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
298 UserOperation::GetSiteConfig => {
299 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
301 UserOperation::SaveSiteConfig => {
302 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
304 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
305 UserOperation::TransferCommunity => {
306 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
308 UserOperation::TransferSite => {
309 do_websocket_operation::<TransferSite>(context, id, op, data).await
313 UserOperation::GetCommunity => {
314 do_websocket_operation::<GetCommunity>(context, id, op, data).await
316 UserOperation::ListCommunities => {
317 do_websocket_operation::<ListCommunities>(context, id, op, data).await
319 UserOperation::CreateCommunity => {
320 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
322 UserOperation::EditCommunity => {
323 do_websocket_operation::<EditCommunity>(context, id, op, data).await
325 UserOperation::DeleteCommunity => {
326 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
328 UserOperation::RemoveCommunity => {
329 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
331 UserOperation::FollowCommunity => {
332 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
334 UserOperation::GetFollowedCommunities => {
335 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
337 UserOperation::BanFromCommunity => {
338 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
340 UserOperation::AddModToCommunity => {
341 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
345 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
346 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
347 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
348 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
349 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
350 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
351 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
352 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
353 UserOperation::CreatePostLike => {
354 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
356 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
357 UserOperation::CreatePostReport => {
358 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
360 UserOperation::ListPostReports => {
361 do_websocket_operation::<ListPostReports>(context, id, op, data).await
363 UserOperation::ResolvePostReport => {
364 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
368 UserOperation::CreateComment => {
369 do_websocket_operation::<CreateComment>(context, id, op, data).await
371 UserOperation::EditComment => {
372 do_websocket_operation::<EditComment>(context, id, op, data).await
374 UserOperation::DeleteComment => {
375 do_websocket_operation::<DeleteComment>(context, id, op, data).await
377 UserOperation::RemoveComment => {
378 do_websocket_operation::<RemoveComment>(context, id, op, data).await
380 UserOperation::MarkCommentAsRead => {
381 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
383 UserOperation::SaveComment => {
384 do_websocket_operation::<SaveComment>(context, id, op, data).await
386 UserOperation::GetComments => {
387 do_websocket_operation::<GetComments>(context, id, op, data).await
389 UserOperation::CreateCommentLike => {
390 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
392 UserOperation::CreateCommentReport => {
393 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
395 UserOperation::ListCommentReports => {
396 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
398 UserOperation::ResolveCommentReport => {
399 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
404 async fn do_websocket_operation<'a, 'b, Data>(
405 context: LemmyContext,
409 ) -> Result<String, LemmyError>
411 for<'de> Data: Deserialize<'de> + 'a,
414 let parsed_data: Data = serde_json::from_str(&data)?;
415 let res = parsed_data
416 .perform(&web::Data::new(context), Some(id))
418 serialize_websocket_message(&op, &res)
421 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
422 let mut built_text = String::new();
424 // Building proper speech text for espeak
425 for mut c in captcha.chars() {
426 let new_str = if c.is_alphabetic() {
427 if c.is_lowercase() {
428 c.make_ascii_uppercase();
429 format!("lower case {} ... ", c)
431 c.make_ascii_uppercase();
432 format!("capital {} ... ", c)
438 built_text.push_str(&new_str);
441 espeak_wav_base64(&built_text)
444 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
445 // Make a temp file path
446 let uuid = uuid::Uuid::new_v4().to_string();
447 let file_path = format!(
448 "{}/lemmy_espeak_{}.wav",
449 env::temp_dir().to_string_lossy(),
453 // Write the wav file
454 Command::new("espeak")
460 // Read the wav file bytes
461 let bytes = std::fs::read(&file_path)?;
464 std::fs::remove_file(file_path)?;
467 let base64 = base64::encode(bytes);
474 use crate::{captcha_espeak_wav_base64, get_user_from_jwt};
475 use lemmy_db_queries::{
476 establish_pooled_connection,
482 use lemmy_db_schema::source::user::{UserForm, User_};
483 use lemmy_utils::claims::Claims;
485 env::{current_dir, set_current_dir},
490 async fn test_should_not_validate_user_token_after_password_change() {
491 struct CwdGuard(PathBuf);
492 impl Drop for CwdGuard {
494 let _ = set_current_dir(&self.0);
498 let _dir_bkp = CwdGuard(current_dir().unwrap());
500 // so configs could be read
501 let _ = set_current_dir("../..");
503 let conn = establish_pooled_connection();
505 let new_user = UserForm {
506 name: "user_df342sgf".into(),
507 preferred_username: None,
508 password_encrypted: "nope".into(),
510 matrix_user_id: None,
518 theme: "browser".into(),
519 default_sort_type: SortType::Hot as i16,
520 default_listing_type: ListingType::Subscribed as i16,
521 lang: "browser".into(),
523 send_notifications_to_email: false,
529 last_refreshed_at: None,
531 shared_inbox_url: None,
534 let inserted_user: User_ = User_::create(&conn.get().unwrap(), &new_user).unwrap();
536 let jwt_token = Claims::jwt(inserted_user.id, String::from("my-host.com")).unwrap();
538 get_user_from_jwt(&jwt_token, &conn)
540 .expect("User should be decoded");
542 std::thread::sleep(std::time::Duration::from_secs(1));
544 User_::update_password(&conn.get().unwrap(), inserted_user.id, &"password111").unwrap();
546 let jwt_decode_res = get_user_from_jwt(&jwt_token, &conn).await;
548 jwt_decode_res.expect_err("JWT decode should fail after password change");
553 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())