]> Untitled Git - lemmy.git/blob - crates/api/src/lib.rs
Add allowed and blocked instances to the federated_instances response. (#1398)
[lemmy.git] / crates / api / src / lib.rs
1 use crate::claims::Claims;
2 use actix_web::{web, web::Data};
3 use lemmy_db_queries::{
4   source::{
5     community::{CommunityModerator_, Community_},
6     site::Site_,
7     user::UserSafeSettings_,
8   },
9   Crud,
10   DbPool,
11 };
12 use lemmy_db_schema::source::{
13   community::{Community, CommunityModerator},
14   post::Post,
15   site::Site,
16   user::{UserSafeSettings, User_},
17 };
18 use lemmy_db_views_actor::{
19   community_user_ban_view::CommunityUserBanView,
20   community_view::CommunityView,
21 };
22 use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*, websocket::*};
23 use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError};
24 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
25 use serde::Deserialize;
26 use std::process::Command;
27 use url::Url;
28
29 pub mod claims;
30 pub mod comment;
31 pub mod community;
32 pub mod post;
33 pub mod site;
34 pub mod user;
35 pub mod version;
36 pub mod websocket;
37
38 #[async_trait::async_trait(?Send)]
39 pub trait Perform {
40   type Response: serde::ser::Serialize + Send;
41
42   async fn perform(
43     &self,
44     context: &Data<LemmyContext>,
45     websocket_id: Option<ConnectionId>,
46   ) -> Result<Self::Response, LemmyError>;
47 }
48
49 pub(crate) async fn is_mod_or_admin(
50   pool: &DbPool,
51   user_id: i32,
52   community_id: i32,
53 ) -> Result<(), LemmyError> {
54   let is_mod_or_admin = blocking(pool, move |conn| {
55     CommunityView::is_mod_or_admin(conn, user_id, community_id)
56   })
57   .await?;
58   if !is_mod_or_admin {
59     return Err(APIError::err("not_a_mod_or_admin").into());
60   }
61   Ok(())
62 }
63 pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
64   let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
65   if !user.admin {
66     return Err(APIError::err("not_an_admin").into());
67   }
68   Ok(())
69 }
70
71 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
72   match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
73     Ok(post) => Ok(post),
74     Err(_e) => Err(APIError::err("couldnt_find_post").into()),
75   }
76 }
77
78 pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
79   let claims = match Claims::decode(&jwt) {
80     Ok(claims) => claims.claims,
81     Err(_e) => return Err(APIError::err("not_logged_in").into()),
82   };
83   let user_id = claims.id;
84   let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
85   // Check for a site ban
86   if user.banned {
87     return Err(APIError::err("site_ban").into());
88   }
89   Ok(user)
90 }
91
92 pub(crate) async fn get_user_from_jwt_opt(
93   jwt: &Option<String>,
94   pool: &DbPool,
95 ) -> Result<Option<User_>, LemmyError> {
96   match jwt {
97     Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
98     None => Ok(None),
99   }
100 }
101
102 pub(crate) async fn get_user_safe_settings_from_jwt(
103   jwt: &str,
104   pool: &DbPool,
105 ) -> Result<UserSafeSettings, LemmyError> {
106   let claims = match Claims::decode(&jwt) {
107     Ok(claims) => claims.claims,
108     Err(_e) => return Err(APIError::err("not_logged_in").into()),
109   };
110   let user_id = claims.id;
111   let user = blocking(pool, move |conn| UserSafeSettings::read(conn, user_id)).await??;
112   // Check for a site ban
113   if user.banned {
114     return Err(APIError::err("site_ban").into());
115   }
116   Ok(user)
117 }
118
119 pub(crate) async fn get_user_safe_settings_from_jwt_opt(
120   jwt: &Option<String>,
121   pool: &DbPool,
122 ) -> Result<Option<UserSafeSettings>, LemmyError> {
123   match jwt {
124     Some(jwt) => Ok(Some(get_user_safe_settings_from_jwt(jwt, pool).await?)),
125     None => Ok(None),
126   }
127 }
128
129 pub(crate) async fn check_community_ban(
130   user_id: i32,
131   community_id: i32,
132   pool: &DbPool,
133 ) -> Result<(), LemmyError> {
134   let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
135   if blocking(pool, is_banned).await? {
136     Err(APIError::err("community_ban").into())
137   } else {
138     Ok(())
139   }
140 }
141
142 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
143   if score == -1 {
144     let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
145     if !site.enable_downvotes {
146       return Err(APIError::err("downvotes_disabled").into());
147     }
148   }
149   Ok(())
150 }
151
152 /// Returns a list of communities that the user moderates
153 /// or if a community_id is supplied validates the user is a moderator
154 /// of that community and returns the community id in a vec
155 ///
156 /// * `user_id` - the user id of the moderator
157 /// * `community_id` - optional community id to check for moderator privileges
158 /// * `pool` - the diesel db pool
159 pub(crate) async fn collect_moderated_communities(
160   user_id: i32,
161   community_id: Option<i32>,
162   pool: &DbPool,
163 ) -> Result<Vec<i32>, LemmyError> {
164   if let Some(community_id) = community_id {
165     // if the user provides a community_id, just check for mod/admin privileges
166     is_mod_or_admin(pool, user_id, community_id).await?;
167     Ok(vec![community_id])
168   } else {
169     let ids = blocking(pool, move |conn: &'_ _| {
170       CommunityModerator::get_user_moderated_communities(conn, user_id)
171     })
172     .await??;
173     Ok(ids)
174   }
175 }
176
177 pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
178   if let Some(Some(item)) = &item {
179     if Url::parse(item).is_err() {
180       return Err(APIError::err("invalid_url").into());
181     }
182   }
183   Ok(())
184 }
185
186 pub(crate) async fn build_federated_instances(
187   pool: &DbPool,
188 ) -> Result<Option<FederatedInstances>, LemmyError> {
189   if Settings::get().federation.enabled {
190     let distinct_communities = blocking(pool, move |conn| {
191       Community::distinct_federated_communities(conn)
192     })
193     .await??;
194
195     let allowed = Settings::get().get_allowed_instances();
196     let blocked = Settings::get().get_blocked_instances();
197
198     let mut linked = distinct_communities
199       .iter()
200       .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
201       .collect::<Result<Vec<String>, LemmyError>>()?;
202
203     linked.extend_from_slice(&allowed);
204     linked.retain(|a| !blocked.contains(a) && !a.eq("") && !a.eq(&Settings::get().hostname));
205
206     // Sort and remove dupes
207     linked.sort_unstable();
208     linked.dedup();
209
210     Ok(Some(FederatedInstances {
211       linked,
212       allowed,
213       blocked,
214     }))
215   } else {
216     Ok(None)
217   }
218 }
219
220 pub async fn match_websocket_operation(
221   context: LemmyContext,
222   id: ConnectionId,
223   op: UserOperation,
224   data: &str,
225 ) -> Result<String, LemmyError> {
226   match op {
227     // User ops
228     UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
229     UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
230     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
231     UserOperation::GetUserDetails => {
232       do_websocket_operation::<GetUserDetails>(context, id, op, data).await
233     }
234     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
235     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
236     UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
237     UserOperation::GetUserMentions => {
238       do_websocket_operation::<GetUserMentions>(context, id, op, data).await
239     }
240     UserOperation::MarkUserMentionAsRead => {
241       do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
242     }
243     UserOperation::MarkAllAsRead => {
244       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
245     }
246     UserOperation::DeleteAccount => {
247       do_websocket_operation::<DeleteAccount>(context, id, op, data).await
248     }
249     UserOperation::PasswordReset => {
250       do_websocket_operation::<PasswordReset>(context, id, op, data).await
251     }
252     UserOperation::PasswordChange => {
253       do_websocket_operation::<PasswordChange>(context, id, op, data).await
254     }
255     UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
256     UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
257     UserOperation::CommunityJoin => {
258       do_websocket_operation::<CommunityJoin>(context, id, op, data).await
259     }
260     UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
261     UserOperation::SaveUserSettings => {
262       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
263     }
264     UserOperation::GetReportCount => {
265       do_websocket_operation::<GetReportCount>(context, id, op, data).await
266     }
267
268     // Private Message ops
269     UserOperation::CreatePrivateMessage => {
270       do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
271     }
272     UserOperation::EditPrivateMessage => {
273       do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
274     }
275     UserOperation::DeletePrivateMessage => {
276       do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
277     }
278     UserOperation::MarkPrivateMessageAsRead => {
279       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
280     }
281     UserOperation::GetPrivateMessages => {
282       do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
283     }
284
285     // Site ops
286     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
287     UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
288     UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
289     UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
290     UserOperation::GetSiteConfig => {
291       do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
292     }
293     UserOperation::SaveSiteConfig => {
294       do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
295     }
296     UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
297     UserOperation::TransferCommunity => {
298       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
299     }
300     UserOperation::TransferSite => {
301       do_websocket_operation::<TransferSite>(context, id, op, data).await
302     }
303     UserOperation::ListCategories => {
304       do_websocket_operation::<ListCategories>(context, id, op, data).await
305     }
306
307     // Community ops
308     UserOperation::GetCommunity => {
309       do_websocket_operation::<GetCommunity>(context, id, op, data).await
310     }
311     UserOperation::ListCommunities => {
312       do_websocket_operation::<ListCommunities>(context, id, op, data).await
313     }
314     UserOperation::CreateCommunity => {
315       do_websocket_operation::<CreateCommunity>(context, id, op, data).await
316     }
317     UserOperation::EditCommunity => {
318       do_websocket_operation::<EditCommunity>(context, id, op, data).await
319     }
320     UserOperation::DeleteCommunity => {
321       do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
322     }
323     UserOperation::RemoveCommunity => {
324       do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
325     }
326     UserOperation::FollowCommunity => {
327       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
328     }
329     UserOperation::GetFollowedCommunities => {
330       do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
331     }
332     UserOperation::BanFromCommunity => {
333       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
334     }
335     UserOperation::AddModToCommunity => {
336       do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
337     }
338
339     // Post ops
340     UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
341     UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
342     UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
343     UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
344     UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
345     UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
346     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
347     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
348     UserOperation::CreatePostLike => {
349       do_websocket_operation::<CreatePostLike>(context, id, op, data).await
350     }
351     UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
352     UserOperation::CreatePostReport => {
353       do_websocket_operation::<CreatePostReport>(context, id, op, data).await
354     }
355     UserOperation::ListPostReports => {
356       do_websocket_operation::<ListPostReports>(context, id, op, data).await
357     }
358     UserOperation::ResolvePostReport => {
359       do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
360     }
361
362     // Comment ops
363     UserOperation::CreateComment => {
364       do_websocket_operation::<CreateComment>(context, id, op, data).await
365     }
366     UserOperation::EditComment => {
367       do_websocket_operation::<EditComment>(context, id, op, data).await
368     }
369     UserOperation::DeleteComment => {
370       do_websocket_operation::<DeleteComment>(context, id, op, data).await
371     }
372     UserOperation::RemoveComment => {
373       do_websocket_operation::<RemoveComment>(context, id, op, data).await
374     }
375     UserOperation::MarkCommentAsRead => {
376       do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
377     }
378     UserOperation::SaveComment => {
379       do_websocket_operation::<SaveComment>(context, id, op, data).await
380     }
381     UserOperation::GetComments => {
382       do_websocket_operation::<GetComments>(context, id, op, data).await
383     }
384     UserOperation::CreateCommentLike => {
385       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
386     }
387     UserOperation::CreateCommentReport => {
388       do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
389     }
390     UserOperation::ListCommentReports => {
391       do_websocket_operation::<ListCommentReports>(context, id, op, data).await
392     }
393     UserOperation::ResolveCommentReport => {
394       do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
395     }
396   }
397 }
398
399 async fn do_websocket_operation<'a, 'b, Data>(
400   context: LemmyContext,
401   id: ConnectionId,
402   op: UserOperation,
403   data: &str,
404 ) -> Result<String, LemmyError>
405 where
406   for<'de> Data: Deserialize<'de> + 'a,
407   Data: Perform,
408 {
409   let parsed_data: Data = serde_json::from_str(&data)?;
410   let res = parsed_data
411     .perform(&web::Data::new(context), Some(id))
412     .await?;
413   serialize_websocket_message(&op, &res)
414 }
415
416 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
417   let mut built_text = String::new();
418
419   // Building proper speech text for espeak
420   for mut c in captcha.chars() {
421     let new_str = if c.is_alphabetic() {
422       if c.is_lowercase() {
423         c.make_ascii_uppercase();
424         format!("lower case {} ... ", c)
425       } else {
426         c.make_ascii_uppercase();
427         format!("capital {} ... ", c)
428       }
429     } else {
430       format!("{} ...", c)
431     };
432
433     built_text.push_str(&new_str);
434   }
435
436   espeak_wav_base64(&built_text)
437 }
438
439 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
440   // Make a temp file path
441   let uuid = uuid::Uuid::new_v4().to_string();
442   let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
443
444   // Write the wav file
445   Command::new("espeak")
446     .arg("-w")
447     .arg(&file_path)
448     .arg(text)
449     .status()?;
450
451   // Read the wav file bytes
452   let bytes = std::fs::read(&file_path)?;
453
454   // Delete the file
455   std::fs::remove_file(file_path)?;
456
457   // Convert to base64
458   let base64 = base64::encode(bytes);
459
460   Ok(base64)
461 }
462
463 #[cfg(test)]
464 mod tests {
465   use crate::captcha_espeak_wav_base64;
466
467   #[test]
468   fn test_espeak() {
469     assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
470   }
471 }