Make delete account require password.

- Fixes #301
This commit is contained in:
Dessalines 2019-10-17 21:25:23 -07:00
parent 8a2fb128a9
commit f7c9dc0b21
5 changed files with 33 additions and 9 deletions

View file

@ -105,6 +105,7 @@ pub struct GetReplies {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct DeleteAccount { pub struct DeleteAccount {
password: String,
auth: String, auth: String,
} }
@ -601,6 +602,14 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
let user_id = claims.id; let user_id = claims.id;
let user: User_ = User_::read(&conn, user_id)?;
// Verify the password
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
if !valid {
return Err(APIError::err(&self.op, "password_incorrect"))?;
}
// Comments // Comments
let comments = CommentView::list( let comments = CommentView::list(
&conn, &conn,

View file

@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse, UserSettingsForm, LoginResponse, BanUserResponse, AddAdminResponse } from '../interfaces'; import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse, UserSettingsForm, LoginResponse, BanUserResponse, AddAdminResponse, DeleteAccountForm } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter, themes, setTheme } from '../utils'; import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter, themes, setTheme } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
@ -33,6 +33,7 @@ interface UserState {
userSettingsLoading: boolean; userSettingsLoading: boolean;
deleteAccountLoading: boolean; deleteAccountLoading: boolean;
deleteAccountShowConfirm: boolean; deleteAccountShowConfirm: boolean;
deleteAccountForm: DeleteAccountForm;
} }
export class User extends Component<any, UserState> { export class User extends Component<any, UserState> {
@ -69,6 +70,9 @@ export class User extends Component<any, UserState> {
userSettingsLoading: null, userSettingsLoading: null,
deleteAccountLoading: null, deleteAccountLoading: null,
deleteAccountShowConfirm: false, deleteAccountShowConfirm: false,
deleteAccountForm: {
password: null,
}
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -316,9 +320,10 @@ export class User extends Component<any, UserState> {
<button class="btn btn-danger" onClick={linkEvent(this, this.handleDeleteAccountShowConfirmToggle)}><T i18nKey="delete_account">#</T></button> <button class="btn btn-danger" onClick={linkEvent(this, this.handleDeleteAccountShowConfirmToggle)}><T i18nKey="delete_account">#</T></button>
{this.state.deleteAccountShowConfirm && {this.state.deleteAccountShowConfirm &&
<> <>
<div class="mt-2 alert alert-danger" role="alert"><T i18nKey="delete_account_confirm">#</T></div> <div class="my-2 alert alert-danger" role="alert"><T i18nKey="delete_account_confirm">#</T></div>
<button class="btn btn-danger mr-4" onClick={linkEvent(this, this.handleDeleteAccount)}>{this.state.deleteAccountLoading ? <input type="password" value={this.state.deleteAccountForm.password} onInput={linkEvent(this, this.handleDeleteAccountPasswordChange)} class="form-control my-2" />
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : capitalizeFirstLetter(i18n.t('yes'))}</button> <button class="btn btn-danger mr-4" disabled={!this.state.deleteAccountForm.password} onClick={linkEvent(this, this.handleDeleteAccount)}>{this.state.deleteAccountLoading ?
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : capitalizeFirstLetter(i18n.t('delete'))}</button>
<button class="btn btn-secondary" onClick={linkEvent(this, this.handleDeleteAccountShowConfirmToggle)}><T i18nKey="cancel">#</T></button> <button class="btn btn-secondary" onClick={linkEvent(this, this.handleDeleteAccountShowConfirmToggle)}><T i18nKey="cancel">#</T></button>
</> </>
} }
@ -453,12 +458,17 @@ export class User extends Component<any, UserState> {
i.setState(i.state); i.setState(i.state);
} }
handleDeleteAccountPasswordChange(i: User, event: any) {
i.state.deleteAccountForm.password = event.target.value;
i.setState(i.state);
}
handleDeleteAccount(i: User, event: any) { handleDeleteAccount(i: User, event: any) {
event.preventDefault(); event.preventDefault();
i.state.deleteAccountLoading = true; i.state.deleteAccountLoading = true;
i.setState(i.state); i.setState(i.state);
WebSocketService.Instance.deleteAccount(); WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm);
} }
parseMessage(msg: any) { parseMessage(msg: any) {
@ -466,6 +476,8 @@ export class User extends Component<any, UserState> {
let op: UserOperation = msgOp(msg); let op: UserOperation = msgOp(msg);
if (msg.error) { if (msg.error) {
alert(i18n.t(msg.error)); alert(i18n.t(msg.error));
this.state.deleteAccountLoading = false;
this.setState(this.state);
return; return;
} else if (op == UserOperation.GetUserDetails) { } else if (op == UserOperation.GetUserDetails) {
let res: UserDetailsResponse = msg; let res: UserDetailsResponse = msg;

View file

@ -600,3 +600,7 @@ export interface SearchResponse {
communities: Array<Community>; communities: Array<Community>;
users: Array<UserView>; users: Array<UserView>;
} }
export interface DeleteAccountForm {
password: string;
}

View file

@ -1,5 +1,5 @@
import { wsUri } from '../env'; import { wsUri } from '../env';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, TransferCommunityForm, AddAdminForm, TransferSiteForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm, UserSettingsForm } from '../interfaces'; import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, TransferCommunityForm, AddAdminForm, TransferSiteForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm, UserSettingsForm, DeleteAccountForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket'; import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
@ -199,8 +199,7 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.SaveUserSettings, userSettingsForm)); this.subject.next(this.wsSendWrapper(UserOperation.SaveUserSettings, userSettingsForm));
} }
public deleteAccount() { public deleteAccount(form: DeleteAccountForm) {
let form = {};
this.setAuth(form); this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.DeleteAccount, form)); this.subject.next(this.wsSendWrapper(UserOperation.DeleteAccount, form));
} }

View file

@ -56,7 +56,7 @@ export const en = {
delete: 'delete', delete: 'delete',
deleted: 'deleted', deleted: 'deleted',
delete_account: 'Delete Account', delete_account: 'Delete Account',
delete_account_confirm: 'Warning: this will permanently delete all your data. Are you sure?', delete_account_confirm: 'Warning: this will permanently delete all your data. Enter your password to confirm.',
restore: 'restore', restore: 'restore',
ban: 'ban', ban: 'ban',
ban_from_site: 'ban from site', ban_from_site: 'ban from site',