Skip to content

Commit

Permalink
use LoginToken::invalidate_all, cleanup save_settings
Browse files Browse the repository at this point in the history
  • Loading branch information
Nutomic committed Aug 4, 2023
1 parent 09f553f commit b664f25
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 144 deletions.
9 changes: 9 additions & 0 deletions crates/api/src/local_user/ban_person.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
local_user::LocalUser,
login_token::LoginToken,
moderator::{ModBan, ModBanForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
Expand Down Expand Up @@ -43,6 +46,12 @@ pub async fn ban_from_site(
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;

let local_user_id = LocalUserView::read_person(&mut context.pool(), data.person_id)
.await?
.local_user
.id;
LoginToken::invalidate_all(&mut context.pool(), local_user_id).await?;

// Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false);
if remove_data {
Expand Down
4 changes: 3 additions & 1 deletion crates/api/src/local_user/change_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use lemmy_api_common::{
person::{ChangePassword, LoginResponse},
utils::{local_user_view_from_jwt, password_length_check},
};
use lemmy_db_schema::source::local_user::LocalUser;
use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken};
use lemmy_utils::error::{LemmyError, LemmyErrorType};

#[async_trait::async_trait(?Send)]
Expand Down Expand Up @@ -41,6 +41,8 @@ impl Perform for ChangePassword {
let updated_local_user =
LocalUser::update_password(&mut context.pool(), local_user_id, &new_password).await?;

LoginToken::invalidate_all(&mut context.pool(), local_user_view.local_user.id).await?;

// Return the jwt
Ok(LoginResponse {
jwt: Some(Claims::generate(updated_local_user.id, context).await?),
Expand Down
3 changes: 3 additions & 0 deletions crates/api/src/local_user/change_password_after_reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::source::{
local_user::LocalUser,
login_token::LoginToken,
password_reset_request::PasswordResetRequest,
};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
Expand Down Expand Up @@ -38,6 +39,8 @@ impl Perform for PasswordChangeAfterReset {
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;

LoginToken::invalidate_all(&mut context.pool(), local_user_id).await?;

Ok(LoginResponse {
jwt: None,
verify_email_sent: false,
Expand Down
1 change: 1 addition & 0 deletions crates/api/src/local_user/reset_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl Perform for PasswordReset {

// Email the pure token to the user.
send_password_reset_email(&local_user_view, &mut context.pool(), context.settings()).await?;

Ok(PasswordResetResponse {})
}
}
245 changes: 112 additions & 133 deletions crates/api/src/local_user/save_settings.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::Perform;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
claims::Claims,
context::LemmyContext,
person::{LoginResponse, SaveUserSettings},
person::SaveUserSettings,
utils::{local_user_view_from_jwt, sanitize_html_opt, send_verification_email},
};
use lemmy_db_schema::{
Expand All @@ -27,143 +26,123 @@ use lemmy_utils::{
},
};

#[async_trait::async_trait(?Send)]
impl Perform for SaveUserSettings {
type Response = LoginResponse;

#[tracing::instrument(skip(context))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<LoginResponse, LemmyError> {
let data: &SaveUserSettings = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let site_view = SiteView::read_local(&mut context.pool()).await?;

let bio = sanitize_html_opt(&data.bio);
let display_name = sanitize_html_opt(&data.display_name);

let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let bio = diesel_option_overwrite(bio);
let display_name = diesel_option_overwrite(display_name);
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
let email_deref = data.email.as_deref().map(str::to_lowercase);
let email = diesel_option_overwrite(email_deref.clone());

if let Some(Some(email)) = &email {
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
// Only send the verification email if there was an email change
if previous_email.ne(email) {
send_verification_email(
&local_user_view,
email,
&mut context.pool(),
context.settings(),
)
.await?;
#[tracing::instrument(skip(context))]
pub async fn save_user_settings(
data: Json<SaveUserSettings>,
context: Data<LemmyContext>,
) -> Result<Json<()>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let site_view = SiteView::read_local(&mut context.pool()).await?;

let bio = sanitize_html_opt(&data.bio);
let display_name = sanitize_html_opt(&data.display_name);

let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let bio = diesel_option_overwrite(bio);
let display_name = diesel_option_overwrite(display_name);
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
let email_deref = data.email.as_deref().map(str::to_lowercase);
let email = diesel_option_overwrite(email_deref.clone());

if let Some(Some(email)) = &email {
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
// if email was changed, check that it is not taken and send verification mail
if &previous_email != email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
send_verification_email(
&local_user_view,
email,
&mut context.pool(),
context.settings(),
)
.await?;
}
}

// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
if let Some(email) = &email {
if email.is_none() && site_view.local_site.require_email_verification {
return Err(LemmyErrorType::EmailRequired)?;
}
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
if let Some(email) = &email {
if email.is_none() && site_view.local_site.require_email_verification {
return Err(LemmyErrorType::EmailRequired)?;
}
}

if let Some(Some(bio)) = &bio {
is_valid_bio_field(bio)?;
}
if let Some(Some(bio)) = &bio {
is_valid_bio_field(bio)?;
}

if let Some(Some(display_name)) = &display_name {
is_valid_display_name(
display_name.trim(),
site_view.local_site.actor_name_max_length as usize,
)?;
}
if let Some(Some(display_name)) = &display_name {
is_valid_display_name(
display_name.trim(),
site_view.local_site.actor_name_max_length as usize,
)?;
}

if let Some(Some(matrix_user_id)) = &matrix_user_id {
is_valid_matrix_id(matrix_user_id)?;
}
if let Some(Some(matrix_user_id)) = &matrix_user_id {
is_valid_matrix_id(matrix_user_id)?;
}

let local_user_id = local_user_view.local_user.id;
let person_id = local_user_view.person.id;
let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type;
let theme = sanitize_html_opt(&data.theme);

let person_form = PersonUpdateForm::builder()
.display_name(display_name)
.bio(bio)
.matrix_user_id(matrix_user_id)
.bot_account(data.bot_account)
.avatar(avatar)
.banner(banner)
.build();

Person::update(&mut context.pool(), person_id, &person_form)
.await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;

if let Some(discussion_languages) = data.discussion_languages.clone() {
LocalUserLanguage::update(&mut context.pool(), discussion_languages, local_user_id).await?;
}
let local_user_id = local_user_view.local_user.id;
let person_id = local_user_view.person.id;
let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type;
let theme = sanitize_html_opt(&data.theme);

let person_form = PersonUpdateForm::builder()
.display_name(display_name)
.bio(bio)
.matrix_user_id(matrix_user_id)
.bot_account(data.bot_account)
.avatar(avatar)
.banner(banner)
.build();

Person::update(&mut context.pool(), person_id, &person_form)
.await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;

if let Some(discussion_languages) = data.discussion_languages.clone() {
LocalUserLanguage::update(&mut context.pool(), discussion_languages, local_user_id).await?;
}

// If generate_totp is Some(false), this will clear it out from the database.
let (totp_2fa_secret, totp_2fa_url) = if let Some(generate) = data.generate_totp_2fa {
if generate {
let secret = generate_totp_2fa_secret();
let url =
build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url();
(Some(Some(secret)), Some(Some(url)))
} else {
(Some(None), Some(None))
}
// If generate_totp is Some(false), this will clear it out from the database.
let (totp_2fa_secret, totp_2fa_url) = if let Some(generate) = data.generate_totp_2fa {
if generate {
let secret = generate_totp_2fa_secret();
let url =
build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url();
(Some(Some(secret)), Some(Some(url)))
} else {
(None, None)
};

let local_user_form = LocalUserUpdateForm::builder()
.email(email)
.show_avatars(data.show_avatars)
.show_read_posts(data.show_read_posts)
.show_new_post_notifs(data.show_new_post_notifs)
.send_notifications_to_email(data.send_notifications_to_email)
.show_nsfw(data.show_nsfw)
.blur_nsfw(data.blur_nsfw)
.auto_expand(data.auto_expand)
.show_bot_accounts(data.show_bot_accounts)
.show_scores(data.show_scores)
.default_sort_type(default_sort_type)
.default_listing_type(default_listing_type)
.theme(theme)
.interface_language(data.interface_language.clone())
.totp_2fa_secret(totp_2fa_secret)
.totp_2fa_url(totp_2fa_url)
.open_links_in_new_tab(data.open_links_in_new_tab)
.infinite_scroll_enabled(data.infinite_scroll_enabled)
.build();

let local_user_res =
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await;
let updated_local_user = match local_user_res {
Ok(u) => u,
Err(e) => {
let err_type = if e.to_string()
== "duplicate key value violates unique constraint \"local_user_email_key\""
{
LemmyErrorType::EmailAlreadyExists
} else {
LemmyErrorType::UserAlreadyExists
};

return Err(e).with_lemmy_type(err_type);
}
};

// Return the jwt
Ok(LoginResponse {
jwt: Some(Claims::generate(updated_local_user.id, &context).await?),
verify_email_sent: false,
registration_created: false,
})
}
(Some(None), Some(None))
}
} else {
(None, None)
};

let local_user_form = LocalUserUpdateForm::builder()
.email(email)
.show_avatars(data.show_avatars)
.show_read_posts(data.show_read_posts)
.show_new_post_notifs(data.show_new_post_notifs)
.send_notifications_to_email(data.send_notifications_to_email)
.show_nsfw(data.show_nsfw)
.blur_nsfw(data.blur_nsfw)
.auto_expand(data.auto_expand)
.show_bot_accounts(data.show_bot_accounts)
.show_scores(data.show_scores)
.default_sort_type(default_sort_type)
.default_listing_type(default_listing_type)
.theme(theme)
.interface_language(data.interface_language.clone())
.totp_2fa_secret(totp_2fa_secret)
.totp_2fa_url(totp_2fa_url)
.open_links_in_new_tab(data.open_links_in_new_tab)
.infinite_scroll_enabled(data.infinite_scroll_enabled)
.build();

LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;

Ok(Json(()))
}
1 change: 0 additions & 1 deletion crates/api_common/src/claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub struct Claims {

impl Claims {
pub async fn validate(jwt: &str, context: &LemmyContext) -> LemmyResult<LocalUserId> {
// TODO: check db
let mut validation = Validation::default();
validation.validate_exp = false;
validation.required_spec_claims.remove("exp");
Expand Down
3 changes: 3 additions & 0 deletions crates/api_crud/src/user/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use lemmy_api_common::{
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::source::login_token::LoginToken;
use lemmy_utils::error::{LemmyError, LemmyErrorType};

#[tracing::instrument(skip(context))]
Expand All @@ -26,6 +27,8 @@ pub async fn delete_account(
return Err(LemmyErrorType::IncorrectLogin)?;
}

LoginToken::invalidate_all(&mut context.pool(), local_user_view.local_user.id).await?;

ActivityChannel::submit_activity(
SendActivityData::DeleteUser(local_user_view.person),
&context,
Expand Down
18 changes: 13 additions & 5 deletions crates/db_schema/src/impls/login_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl LoginToken {
.await
}

/// Check if the given token is valid for user.
pub async fn validate(
pool: &mut DbPool<'_>,
user_id_: LocalUserId,
Expand All @@ -32,15 +33,22 @@ impl LoginToken {
.await
}

pub async fn invalidate(pool: &mut DbPool<'_>, token_: &str) -> Result<Self, Error> {
/// Invalidate specific token on user logout.
pub async fn invalidate(pool: &mut DbPool<'_>, token_: &str) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
delete(login_token.filter(token.eq(token_)))
.get_result(conn)
.execute(conn)
.await
}

pub async fn invalidate_all() {
// TODO: call on password reset/change, account deletion, site ban
todo!()
/// Invalidate all logins of given user on password reset/change, account deletion or site ban.
pub async fn invalidate_all(
pool: &mut DbPool<'_>,
user_id_: LocalUserId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
delete(login_token.filter(user_id.eq(user_id_)))
.execute(conn)
.await
}
}
Loading

0 comments on commit b664f25

Please sign in to comment.