Hexbear is the engine that powers Chapochat. It is a customization of the Lemmy project.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1781 lines
48 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
2 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. use crate::{
  2. api::{claims::Claims, APIError, Oper, Perform},
  3. apub::ApubObjectType,
  4. blocking,
  5. is_within_message_char_limit,
  6. websocket::{
  7. server::{JoinUserRoom, SendAllMessage, SendUserRoomMessage},
  8. UserOperation,
  9. WebsocketInfo,
  10. },
  11. DbPool,
  12. LemmyError,
  13. };
  14. use bcrypt::verify;
  15. use lemmy_db::{
  16. comment::*,
  17. comment_view::*,
  18. community::*,
  19. community_settings::{CommunitySettings, CommunitySettingsForm},
  20. community_view::*,
  21. moderator::*,
  22. naive_now,
  23. password_reset_request::*,
  24. post::*,
  25. post_view::*,
  26. private_message::*,
  27. private_message_view::*,
  28. site::*,
  29. site_view::*,
  30. user::*,
  31. user_mention::*,
  32. user_mention_view::*,
  33. user_tag::*,
  34. user_view::*,
  35. Crud,
  36. Followable,
  37. Joinable,
  38. ListingType,
  39. SortType,
  40. };
  41. use lemmy_utils::{
  42. generate_actor_keypair,
  43. generate_random_string,
  44. is_valid_username,
  45. make_apub_endpoint,
  46. naive_from_unix,
  47. remove_slurs,
  48. send_email,
  49. settings::Settings,
  50. slur_check,
  51. slurs_vec_to_str,
  52. EndpointType,
  53. };
  54. use log::{error, info};
  55. use serde::{Deserialize, Serialize};
  56. use std::{collections::BTreeMap, env, str::FromStr};
  57. #[derive(Serialize, Deserialize, Debug)]
  58. pub struct Login {
  59. username_or_email: String,
  60. password: String,
  61. captcha_id: String,
  62. }
  63. #[derive(Serialize, Deserialize)]
  64. pub struct Register {
  65. pub username: String,
  66. pub email: Option<String>,
  67. pub password: String,
  68. pub password_verify: String,
  69. pub admin: bool,
  70. pub sitemod: bool,
  71. pub show_nsfw: bool,
  72. pub captcha_id: String,
  73. pub pronouns: Option<String>,
  74. }
  75. #[derive(Serialize, Deserialize)]
  76. pub struct SaveUserSettings {
  77. show_nsfw: bool,
  78. theme: String,
  79. default_sort_type: i16,
  80. default_listing_type: i16,
  81. lang: String,
  82. avatar: Option<String>,
  83. email: Option<String>,
  84. matrix_user_id: Option<String>,
  85. new_password: Option<String>,
  86. new_password_verify: Option<String>,
  87. old_password: Option<String>,
  88. show_avatars: bool,
  89. send_notifications_to_email: bool,
  90. auth: String,
  91. }
  92. #[derive(Serialize, Deserialize)]
  93. pub struct LoginResponse {
  94. pub jwt: String,
  95. }
  96. #[derive(Serialize, Deserialize)]
  97. pub struct GetUserDetails {
  98. user_id: Option<i32>,
  99. username: Option<String>,
  100. sort: String,
  101. page: Option<i64>,
  102. limit: Option<i64>,
  103. community_id: Option<i32>,
  104. saved_only: bool,
  105. auth: Option<String>,
  106. }
  107. #[derive(Serialize, Deserialize)]
  108. pub struct GetUserDetailsResponse {
  109. user: UserView,
  110. follows: Vec<CommunityFollowerView>,
  111. moderates: Vec<CommunityModeratorView>,
  112. comments: Vec<CommentView>,
  113. posts: Vec<PostView>,
  114. admins: Vec<UserView>,
  115. sitemods: Vec<UserView>,
  116. }
  117. #[derive(Serialize, Deserialize)]
  118. pub struct GetRepliesResponse {
  119. replies: Vec<ReplyView>,
  120. }
  121. #[derive(Serialize, Deserialize)]
  122. pub struct GetUserMentionsResponse {
  123. mentions: Vec<UserMentionView>,
  124. }
  125. #[derive(Serialize, Deserialize)]
  126. pub struct MarkAllAsRead {
  127. auth: String,
  128. }
  129. #[derive(Serialize, Deserialize)]
  130. pub struct AddAdmin {
  131. user_id: i32,
  132. added: bool,
  133. auth: String,
  134. }
  135. #[derive(Serialize, Deserialize, Clone)]
  136. pub struct AddAdminResponse {
  137. admins: Vec<UserView>,
  138. }
  139. #[derive(Serialize, Deserialize)]
  140. pub struct AddSitemod {
  141. user_id: i32,
  142. added: bool,
  143. auth: String,
  144. }
  145. #[derive(Serialize, Deserialize, Clone)]
  146. pub struct AddSitemodResponse {
  147. sitemods: Vec<UserView>,
  148. }
  149. #[derive(Serialize, Deserialize)]
  150. pub struct BanUser {
  151. user_id: i32,
  152. ban: bool,
  153. reason: Option<String>,
  154. expires: Option<i64>,
  155. auth: String,
  156. }
  157. #[derive(Serialize, Deserialize, Clone)]
  158. pub struct BanUserResponse {
  159. user: UserView,
  160. banned: bool,
  161. }
  162. #[derive(Serialize, Deserialize)]
  163. pub struct GetReplies {
  164. sort: String,
  165. page: Option<i64>,
  166. limit: Option<i64>,
  167. unread_only: bool,
  168. auth: String,
  169. }
  170. #[derive(Serialize, Deserialize)]
  171. pub struct GetUserMentions {
  172. sort: String,
  173. page: Option<i64>,
  174. limit: Option<i64>,
  175. unread_only: bool,
  176. auth: String,
  177. }
  178. #[derive(Serialize, Deserialize)]
  179. pub struct EditUserMention {
  180. user_mention_id: i32,
  181. read: Option<bool>,
  182. auth: String,
  183. }
  184. #[derive(Serialize, Deserialize, Clone)]
  185. pub struct UserMentionResponse {
  186. mention: UserMentionView,
  187. }
  188. #[derive(Serialize, Deserialize)]
  189. pub struct DeleteAccount {
  190. password: String,
  191. auth: String,
  192. }
  193. #[derive(Serialize, Deserialize)]
  194. pub struct PasswordReset {
  195. email: String,
  196. }
  197. #[derive(Serialize, Deserialize, Clone)]
  198. pub struct PasswordResetResponse {}
  199. #[derive(Serialize, Deserialize)]
  200. pub struct PasswordChange {
  201. token: String,
  202. password: String,
  203. password_verify: String,
  204. }
  205. #[derive(Serialize, Deserialize)]
  206. pub struct CreatePrivateMessage {
  207. content: String,
  208. pub recipient_id: i32,
  209. auth: String,
  210. }
  211. #[derive(Serialize, Deserialize)]
  212. pub struct EditPrivateMessage {
  213. edit_id: i32,
  214. content: Option<String>,
  215. deleted: Option<bool>,
  216. read: Option<bool>,
  217. auth: String,
  218. }
  219. #[derive(Serialize, Deserialize)]
  220. pub struct GetPrivateMessages {
  221. unread_only: bool,
  222. page: Option<i64>,
  223. limit: Option<i64>,
  224. auth: String,
  225. }
  226. #[derive(Serialize, Deserialize, Clone)]
  227. pub struct PrivateMessagesResponse {
  228. messages: Vec<PrivateMessageView>,
  229. }
  230. #[derive(Serialize, Deserialize, Clone)]
  231. pub struct PrivateMessageResponse {
  232. pub message: PrivateMessageView,
  233. }
  234. #[derive(Serialize, Deserialize, Debug)]
  235. pub struct UserJoin {
  236. auth: String,
  237. }
  238. #[derive(Serialize, Deserialize, Clone)]
  239. pub struct UserJoinResponse {
  240. pub user_id: i32,
  241. }
  242. #[derive(Deserialize)]
  243. struct CaptchaResponse {
  244. success: bool,
  245. #[serde(rename = "error-codes")]
  246. error_codes: Option<Vec<String>>,
  247. }
  248. #[derive(Serialize, Deserialize, Clone)]
  249. pub struct GetUserTag {
  250. user: i32,
  251. community: Option<i32>,
  252. }
  253. #[derive(Serialize, Deserialize, Clone)]
  254. pub struct SetUserTag {
  255. tag: String,
  256. value: Option<String>,
  257. auth: String,
  258. }
  259. #[derive(Serialize, Deserialize, Clone)]
  260. pub struct UserTagResponse {
  261. user: i32,
  262. #[serde(skip_serializing_if = "Option::is_none")]
  263. community: Option<i32>,
  264. tags: UserTagsSchema,
  265. }
  266. #[derive(Serialize, Deserialize, Clone)]
  267. struct UserTagsSchema {
  268. #[serde(skip_serializing_if = "Option::is_none")]
  269. pronouns: Option<String>,
  270. #[serde(skip_serializing_if = "Option::is_none")]
  271. tendency: Option<String>,
  272. #[serde(skip_serializing_if = "Option::is_none")]
  273. favorite_food: Option<String>,
  274. #[serde(skip_serializing_if = "Option::is_none")]
  275. flair: Option<String>,
  276. }
  277. #[async_trait::async_trait(?Send)]
  278. impl Perform for Oper<SetUserTag> {
  279. type Response = UserTagResponse;
  280. async fn perform(
  281. &self,
  282. pool: &DbPool,
  283. _websocket_info: Option<WebsocketInfo>,
  284. ) -> Result<UserTagResponse, LemmyError> {
  285. let data: &SetUserTag = &self.data;
  286. let key = data.tag.clone();
  287. let value = data.value.clone();
  288. let mut tags = BTreeMap::new();
  289. let claims = match Claims::decode(&data.auth) {
  290. Ok(claims) => claims.claims,
  291. Err(_e) => return Err(APIError::err("not_logged_in").into()),
  292. };
  293. let user = claims.id;
  294. if let Some(v) = data.value.clone() {
  295. tags.insert(key.clone(), v);
  296. }
  297. match blocking(pool, move |conn| UserTag::set_key(conn, user, key, value)).await? {
  298. Ok(usertag) => Ok(UserTagResponse {
  299. user,
  300. community: None,
  301. tags: serde_json::from_value(usertag.tags)?,
  302. }),
  303. Err(e) => Err(LemmyError::from(e)),
  304. }
  305. }
  306. }
  307. #[async_trait::async_trait(?Send)]
  308. impl Perform for Oper<GetUserTag> {
  309. type Response = UserTagResponse;
  310. async fn perform(
  311. &self,
  312. pool: &DbPool,
  313. _websocket_info: Option<WebsocketInfo>,
  314. ) -> Result<UserTagResponse, LemmyError> {
  315. let data: &GetUserTag = &self.data;
  316. let user = data.user;
  317. // Check if user exists
  318. let user_exists = blocking(pool, move |conn| User_::read(conn, user))
  319. .await?
  320. .is_ok();
  321. if !user_exists {
  322. return Err(APIError::err("user_doesnt_exist").into());
  323. }
  324. match blocking(pool, move |conn| UserTag::read(conn, user)).await? {
  325. Ok(usertag) => Ok(UserTagResponse {
  326. user,
  327. community: None,
  328. tags: serde_json::from_value(usertag.tags)?,
  329. }),
  330. Err(diesel::result::Error::NotFound) => {
  331. let empty = UserTagsSchema {
  332. // I hate this
  333. pronouns: None,
  334. tendency: None,
  335. favorite_food: None,
  336. flair: None,
  337. };
  338. Ok(UserTagResponse {
  339. user,
  340. community: None,
  341. tags: empty,
  342. })
  343. }
  344. Err(e) => Err(LemmyError::from(e)),
  345. }
  346. }
  347. }
  348. #[async_trait::async_trait(?Send)]
  349. impl Perform for Oper<Login> {
  350. type Response = LoginResponse;
  351. async fn perform(
  352. &self,
  353. pool: &DbPool,
  354. _websocket_info: Option<WebsocketInfo>,
  355. ) -> Result<LoginResponse, LemmyError> {
  356. let secret_key: String = match env::var("HCAPTCHA_SECRET_KEY") {
  357. Ok(key) => key,
  358. Err(_e) => "0x0000000000000000000000000000000000000000".to_string(),
  359. };
  360. let data: &Login = &self.data;
  361. let client = reqwest::Client::new();
  362. let body = [
  363. ("secret", secret_key),
  364. ("response", data.captcha_id.clone()),
  365. ];
  366. let res = client
  367. .post("https://hcaptcha.com/siteverify")
  368. .form(&body)
  369. .send()
  370. .await?;
  371. //println!("received {:?}", &res.text());
  372. let parsed_response: CaptchaResponse = res.json().await?;
  373. if !parsed_response.success {
  374. let err_string: String = format!(
  375. "invalid_captcha;{}",
  376. &parsed_response
  377. .error_codes
  378. .unwrap()
  379. .join(";")
  380. .replace("-", "_")
  381. );
  382. return Err(APIError::err(&err_string).into());
  383. }
  384. // Fetch that username / email
  385. let username_or_email = data.username_or_email.clone();
  386. let user = match blocking(pool, move |conn| {
  387. Claims::find_by_email_or_username(conn, &username_or_email)
  388. })
  389. .await?
  390. {
  391. Ok(user) => user,
  392. Err(_e) => return Err(APIError::err("invalid_login_credentials").into()),
  393. };
  394. // Verify the password
  395. let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
  396. if !valid {
  397. return Err(APIError::err("invalid_login_credentials").into());
  398. }
  399. // Return the jwt
  400. Ok(LoginResponse {
  401. jwt: Claims::jwt(user, Settings::get().hostname),
  402. })
  403. }
  404. }
  405. #[async_trait::async_trait(?Send)]
  406. impl Perform for Oper<Register> {
  407. type Response = LoginResponse;
  408. async fn perform(
  409. &self,
  410. pool: &DbPool,
  411. _websocket_info: Option<WebsocketInfo>,
  412. ) -> Result<LoginResponse, LemmyError> {
  413. let secret_key: String = match env::var("HCAPTCHA_SECRET_KEY") {
  414. Ok(key) => key,
  415. Err(_e) => "0x0000000000000000000000000000000000000000".to_string(),
  416. };
  417. let data: &Register = &self.data;
  418. // Make sure there are no admins
  419. // We put this first because there is no captcha when setting up the site.
  420. // We bypass captcha check if an admin is legitimately being created
  421. let any_admins = blocking(pool, move |conn| {
  422. UserView::admins(conn).map(|a| a.is_empty())
  423. })
  424. .await??;
  425. if data.admin && !any_admins {
  426. return Err(APIError::err("admin_already_created").into());
  427. }
  428. if !data.admin {
  429. let client = reqwest::Client::new();
  430. let body = [
  431. ("secret", secret_key),
  432. ("response", data.captcha_id.clone()),
  433. ];
  434. let res = client
  435. .post("https://hcaptcha.com/siteverify")
  436. .form(&body)
  437. .send()
  438. .await?;
  439. //println!("received {:?}", &res.text());
  440. let parsed_response: CaptchaResponse = res.json().await?;
  441. if !parsed_response.success {
  442. let err_string: String = format!(
  443. "invalid_captcha;{}",
  444. &parsed_response
  445. .error_codes
  446. .unwrap()
  447. .join(";")
  448. .replace("-", "_")
  449. );
  450. return Err(APIError::err(&err_string).into());
  451. }
  452. }
  453. // Make sure site has open registration
  454. if let Ok(site) = blocking(pool, move |conn| SiteView::read(conn)).await? {
  455. let site: SiteView = site;
  456. if !site.open_registration {
  457. return Err(APIError::err("registration_closed").into());
  458. }
  459. }
  460. // Make sure passwords match
  461. if data.password != data.password_verify {
  462. return Err(APIError::err("passwords_dont_match").into());
  463. }
  464. if let Err(slurs) = slur_check(&data.username) {
  465. return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
  466. }
  467. let user_keypair = generate_actor_keypair()?;
  468. if !is_valid_username(&data.username) {
  469. return Err(APIError::err("invalid_username").into());
  470. }
  471. let email = match &data.email {
  472. Some(email) => {
  473. if email.trim().is_empty() {
  474. None
  475. } else {
  476. Some(email.trim().to_lowercase())
  477. }
  478. }
  479. None => None,
  480. };
  481. // Register the new user
  482. let user_form = UserForm {
  483. name: data.username.to_owned(),
  484. email,
  485. matrix_user_id: None,
  486. avatar: None,
  487. password_encrypted: data.password.to_owned(),
  488. preferred_username: None,
  489. updated: None,
  490. admin: data.admin,
  491. sitemod: data.sitemod,
  492. banned: false,
  493. show_nsfw: data.show_nsfw,
  494. theme: "darkly".into(),
  495. default_sort_type: SortType::Hot as i16,
  496. default_listing_type: ListingType::Subscribed as i16,
  497. lang: "browser".into(),
  498. show_avatars: true,
  499. send_notifications_to_email: false,
  500. actor_id: make_apub_endpoint(EndpointType::User, &data.username).to_string(),
  501. bio: None,
  502. local: true,
  503. private_key: Some(user_keypair.private_key),
  504. public_key: Some(user_keypair.public_key),
  505. last_refreshed_at: None,
  506. };
  507. // Create the user
  508. let inserted_user = match blocking(pool, move |conn| User_::register(conn, &user_form)).await? {
  509. Ok(user) => user,
  510. Err(e) => {
  511. let err_type = if e.to_string()
  512. == "duplicate key value violates unique constraint \"user__email_key\""
  513. {
  514. "email_already_exists"
  515. } else {
  516. "user_already_exists"
  517. };
  518. return Err(APIError::err(err_type).into());
  519. }
  520. };
  521. let main_community_keypair = generate_actor_keypair()?;
  522. // Create the main community if it doesn't exist
  523. let main_community = match blocking(pool, move |conn| Community::read(conn, 2)).await? {
  524. Ok(c) => c,
  525. Err(_e) => {
  526. let default_community_name = "main";
  527. let community_form = CommunityForm {
  528. name: default_community_name.to_string(),
  529. title: "The Default Community".to_string(),
  530. description: Some("The Default Community".