use actix_web::{web, web::Data};
+ use captcha::Captcha;
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize;
- use std::{env, process::Command};
mod comment;
mod comment_report;
UserOperation::SaveUserSettings => {
do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
}
+ UserOperation::ChangePassword => {
+ do_websocket_operation::<ChangePassword>(context, id, op, data).await
+ }
UserOperation::GetReportCount => {
do_websocket_operation::<GetReportCount>(context, id, op, data).await
}
serialize_websocket_message(&op, &res)
}
- pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
- let mut built_text = String::new();
+ /// Converts the captcha to a base64 encoded wav audio file
+ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
+ let letters = captcha.as_wav();
- // Building proper speech text for espeak
- for mut c in captcha.chars() {
- let new_str = if c.is_alphabetic() {
- if c.is_lowercase() {
- c.make_ascii_uppercase();
- format!("lower case {} ... ", c)
- } else {
- c.make_ascii_uppercase();
- format!("capital {} ... ", c)
- }
- } else {
- format!("{} ...", c)
- };
+ let mut concat_letters: Vec<u8> = Vec::new();
- built_text.push_str(&new_str);
+ for letter in letters {
+ let bytes = letter.unwrap_or_default();
+ concat_letters.extend(bytes);
}
- espeak_wav_base64(&built_text)
- }
-
- pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
- // Make a temp file path
- let uuid = uuid::Uuid::new_v4().to_string();
- let file_path = format!(
- "{}/lemmy_espeak_{}.wav",
- env::temp_dir().to_string_lossy(),
- &uuid
- );
-
- // Write the wav file
- Command::new("espeak")
- .arg("-w")
- .arg(&file_path)
- .arg(text)
- .status()?;
-
- // Read the wav file bytes
- let bytes = std::fs::read(&file_path)?;
-
- // Delete the file
- std::fs::remove_file(file_path)?;
-
// Convert to base64
- let base64 = base64::encode(bytes);
-
- Ok(base64)
+ base64::encode(concat_letters)
}
#[cfg(test)]
mod tests {
- use crate::captcha_espeak_wav_base64;
use lemmy_api_common::check_validator_time;
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
use lemmy_db_schema::source::{
let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, num_deleted);
}
-
- #[test]
- fn test_espeak() {
- assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
- }
}
- use crate::{captcha_espeak_wav_base64, Perform};
+ use crate::{captcha_as_wav_base64, Perform};
use actix_web::web::Data;
use anyhow::Context;
use bcrypt::verify;
email::send_email,
location_info,
settings::structs::Settings,
- utils::{generate_random_string, is_valid_preferred_username, naive_from_unix},
+ utils::{generate_random_string, is_valid_display_name, naive_from_unix},
ApiError,
ConnectionId,
LemmyError,
let answer = captcha.chars_as_string();
- let png_byte_array = captcha.as_png().expect("failed to generate captcha");
-
- let png = base64::encode(png_byte_array);
+ let png = captcha.as_base64().expect("failed to generate captcha");
let uuid = uuid::Uuid::new_v4().to_string();
- let wav = captcha_espeak_wav_base64(&answer).ok();
+ let wav = captcha_as_wav_base64(&captcha);
let captcha_item = CaptchaItem {
answer,
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let email = diesel_option_overwrite(&data.email);
let bio = diesel_option_overwrite(&data.bio);
- let preferred_username = diesel_option_overwrite(&data.preferred_username);
+ let display_name = diesel_option_overwrite(&data.display_name);
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
if let Some(Some(bio)) = &bio {
}
}
- if let Some(Some(preferred_username)) = &preferred_username {
- if !is_valid_preferred_username(preferred_username.trim()) {
+ if let Some(Some(display_name)) = &display_name {
+ if !is_valid_display_name(display_name.trim()) {
return Err(ApiError::err("invalid_username").into());
}
}
let local_user_id = local_user_view.local_user.id;
let person_id = local_user_view.person.id;
- let password_encrypted = match &data.new_password {
- Some(new_password) => {
- match &data.new_password_verify {
- Some(new_password_verify) => {
- password_length_check(&new_password)?;
-
- // Make sure passwords match
- if new_password != new_password_verify {
- return Err(ApiError::err("passwords_dont_match").into());
- }
-
- // Check the old password
- match &data.old_password {
- Some(old_password) => {
- let valid: bool =
- verify(old_password, &local_user_view.local_user.password_encrypted)
- .unwrap_or(false);
- if !valid {
- return Err(ApiError::err("password_incorrect").into());
- }
- let new_password = new_password.to_owned();
- let user = blocking(context.pool(), move |conn| {
- LocalUser::update_password(conn, local_user_id, &new_password)
- })
- .await??;
- user.password_encrypted
- }
- None => return Err(ApiError::err("password_incorrect").into()),
- }
- }
- None => return Err(ApiError::err("passwords_dont_match").into()),
- }
- }
- None => local_user_view.local_user.password_encrypted,
- };
-
let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type;
+ let password_encrypted = local_user_view.local_user.password_encrypted;
let person_form = PersonForm {
name: local_user_view.person.name,
avatar,
banner,
inbox_url: None,
- preferred_username,
+ display_name,
published: None,
updated: Some(naive_now()),
banned: None,
email,
password_encrypted,
show_nsfw: data.show_nsfw,
+ show_scores: data.show_scores,
theme: data.theme.to_owned(),
default_sort_type,
default_listing_type,
}
}
+#[async_trait::async_trait(?Send)]
+impl Perform for ChangePassword {
+ type Response = LoginResponse;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<LoginResponse, LemmyError> {
+ let data: &ChangePassword = &self;
+ let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
+
+ password_length_check(&data.new_password)?;
+
+ // Make sure passwords match
+ if data.new_password != data.new_password_verify {
+ return Err(ApiError::err("passwords_dont_match").into());
+ }
+
+ // Check the old password
+ let valid: bool = verify(
+ &data.old_password,
+ &local_user_view.local_user.password_encrypted,
+ )
+ .unwrap_or(false);
+ if !valid {
+ return Err(ApiError::err("password_incorrect").into());
+ }
+
+ let local_user_id = local_user_view.local_user.id;
+ let new_password = data.new_password.to_owned();
+ let updated_local_user = blocking(context.pool(), move |conn| {
+ LocalUser::update_password(conn, local_user_id, &new_password)
+ })
+ .await??;
+
+ // Return the jwt
+ Ok(LoginResponse {
+ jwt: Claims::jwt(updated_local_user.id.0)?,
+ })
+ }
+}
+
#[async_trait::async_trait(?Send)]
impl Perform for AddAdmin {
type Response = AddAdminResponse;
#[derive(Serialize)]
pub struct CaptchaResponse {
- pub png: String, // A Base64 encoded png
- pub wav: Option<String>, // A Base64 encoded wav audio
+ pub png: String, // A Base64 encoded png
+ pub wav: String, // A Base64 encoded wav audio
pub uuid: String,
}
#[derive(Deserialize)]
pub struct SaveUserSettings {
pub show_nsfw: Option<bool>,
- pub show_avatars: Option<bool>,
+ pub show_scores: Option<bool>,
pub theme: Option<String>,
pub default_sort_type: Option<i16>,
pub default_listing_type: Option<i16>,
pub lang: Option<String>,
pub avatar: Option<String>,
pub banner: Option<String>,
- pub preferred_username: Option<String>,
+ pub display_name: Option<String>,
pub email: Option<String>,
pub bio: Option<String>,
pub matrix_user_id: Option<String>,
+ pub show_avatars: Option<bool>,
+ pub new_password: Option<String>,
+ pub new_password_verify: Option<String>,
+ pub old_password: Option<String>,
pub send_notifications_to_email: Option<bool>,
pub auth: String,
}
+#[derive(Deserialize)]
+pub struct ChangePassword {
+ pub new_password: String,
+ pub new_password_verify: String,
+ pub old_password: String,
+ pub auth: String,
+}
+
#[derive(Serialize)]
pub struct LoginResponse {
pub jwt: String,