Delete Account with Social Auth (#8796)

* feat(accounts): delete social accts

* test(integration): social auth delete
This commit is contained in:
Sabe Jones
2017-07-21 10:55:53 -07:00
committed by GitHub
parent 88fece1422
commit e6dd0d5e82
6 changed files with 312 additions and 232 deletions

View File

@@ -18,10 +18,13 @@ import {
} from '../../../../../website/server/libs/password';
import * as email from '../../../../../website/server/libs/email';
const DELETE_CONFIRMATION = 'DELETE';
describe('DELETE /user', () => {
let user;
let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
context('user with local auth', async () => {
beforeEach(async () => {
user = await generateUser({balance: 10});
});
@@ -46,6 +49,13 @@ describe('DELETE /user', () => {
});
});
it('deletes the user', async () => {
await user.del('/user', {
password,
});
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
});
it('returns an error if excessive feedback is supplied', async () => {
let feedbackText = 'spam feedback ';
let feedback = feedbackText;
@@ -117,13 +127,6 @@ describe('DELETE /user', () => {
expect(challenge.memberCount).to.eql(1);
});
it('deletes the user', async () => {
await user.del('/user', {
password,
});
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
});
it('sends feedback to the admin email', async () => {
sandbox.spy(email, 'sendTxn');
@@ -281,3 +284,62 @@ describe('DELETE /user', () => {
});
});
});
context('user with Facebook auth', async () => {
beforeEach(async () => {
user = await generateUser({
auth: {
facebook: {
id: 'facebook-id',
},
},
});
});
it('returns an error if confirmation phrase is wrong', async () => {
await expect(user.del('/user', {
password: 'just-do-it',
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('incorrectDeletePhrase'),
});
});
it('returns an error if confirmation phrase is not supplied', async () => {
await expect(user.del('/user', {
password: '',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingPassword'),
});
});
it('deletes a Facebook user', async () => {
await user.del('/user', {
password: DELETE_CONFIRMATION,
});
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
});
});
context('user with Google auth', async () => {
beforeEach(async () => {
user = await generateUser({
auth: {
google: {
id: 'google-id',
},
},
});
});
it('deletes a Google user', async () => {
await user.del('/user', {
password: DELETE_CONFIRMATION,
});
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
});
});
});

View File

@@ -246,6 +246,7 @@
"missingNewPassword": "Missing new password.",
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
"wrongPassword": "Wrong password.",
"incorrectDeletePhrase": "Please type DELETE in all caps to delete your account.",
"notAnEmail": "Invalid email address.",
"emailTaken": "Email address is already used in an account.",
"newEmailRequired": "Missing new email address.",

View File

@@ -66,6 +66,7 @@
"resetText1": "WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
"resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
"deleteLocalAccountText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type your password into the text box below.",
"deleteSocialAccountText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type \"DELETE\" into the text box below.",
"API": "API",
"APIv3": "API v3",
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",

View File

@@ -22,6 +22,7 @@ import nconf from 'nconf';
import get from 'lodash/get';
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
const DELETE_CONFIRMATION = 'DELETE';
/**
* @apiDefine UserNotFound
@@ -303,14 +304,15 @@ api.deleteUser = {
let password = req.body.password;
if (!password) throw new BadRequest(res.t('missingPassword'));
let feedback = req.body.feedback;
if (feedback && feedback.length > 10000) throw new BadRequest(`Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email ${TECH_ASSISTANCE_EMAIL}.`);
let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
if (user.auth.local.hashed_password && user.auth.local.email) {
let isValidPassword = await passwordUtils.compare(user, password);
if (!isValidPassword) throw new NotAuthorized(res.t('wrongPassword'));
} else if ((user.auth.facebook.id || user.auth.google.id) && password !== DELETE_CONFIRMATION) {
throw new NotAuthorized(res.t('incorrectDeletePhrase'));
}
let feedback = req.body.feedback;
if (feedback && feedback.length > 10000) throw new BadRequest(`Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email ${TECH_ASSISTANCE_EMAIL}.`);
if (plan && plan.customerId && !plan.dateTerminated) {
throw new NotAuthorized(res.t('cannotDeleteActiveAccount'));

View File

@@ -183,4 +183,5 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
span=env.t('dangerZone')
.panel-body
a.btn.btn-danger(ng-click='openModal("reset", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('resetAccPop'))= env.t('resetAccount')
a.btn.btn-danger(ng-click='openModal("delete", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')
a.btn.btn-danger(ng-if='user.auth.local.email' ng-click='openModal("deletelocal", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')
a.btn.btn-danger(ng-if='!user.auth.local.email', ng-click='openModal("deletesocial", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')

View File

@@ -57,7 +57,7 @@ script(type='text/ng-template', id='modals/restore.html')
button.btn.btn-default(ng-click='$close()')=env.t('discardChanges')
button.btn.btn-primary(ng-click='restore()')=env.t('saveAndClose')
script(type='text/ng-template', id='modals/delete.html')
script(type='text/ng-template', id='modals/deletelocal.html')
.modal-header
h4=env.t('deleteAccount')
.modal-body
@@ -74,3 +74,16 @@ script(type='text/ng-template', id='modals/delete.html')
.modal-footer
button.btn.btn-default(ng-click='$close()')=env.t('neverMind')
button.btn.btn-danger(ng-disabled='!_deleteAccount', ng-click='$close(); delete(_deleteAccount, feedback)')=env.t('deleteDo')
script(type='text/ng-template', id='modals/deletesocial.html')
.modal-header
h4=env.t('deleteAccount')
.modal-body
p!=env.t('deleteSocialAccountText')
br
.row
.col-md-6
input.form-control(type='text', ng-model='_deleteAccount')
.modal-footer
button.btn.btn-default(ng-click='$close()')=env.t('neverMind')
button.btn.btn-danger(ng-disabled='!_deleteAccount', ng-click='$close(); delete(_deleteAccount, feedback)')=env.t('deleteDo')