mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Fix resetting account for social accounts (#15087)
* Fix resetting account for social accounts * added integration tests * chore(packages): reinstall modules * only enable reset button if user typed RESET * fix enabling reset button --------- Co-authored-by: negue <eugen.bolz@gmail.com> Co-authored-by: Sabe Jones <sabe@habitica.com>
This commit is contained in:
@@ -6,6 +6,8 @@ import {
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
const RESET_CONFIRMATION = 'RESET';
|
||||
|
||||
describe('POST /user/reset', () => {
|
||||
let user;
|
||||
|
||||
@@ -172,4 +174,68 @@ describe('POST /user/reset', () => {
|
||||
expect(heroRes.secret).to.exist;
|
||||
expect(heroRes.secret.text).to.be.eq('Super-Hero');
|
||||
});
|
||||
|
||||
context('user with Google auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
google: {
|
||||
id: 'google-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('resets a Google user', async () => {
|
||||
const task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: RESET_CONFIRMATION,
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageTaskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
context('user with Apple auth', async () => {
|
||||
beforeEach(async () => {
|
||||
user = await generateUser({
|
||||
auth: {
|
||||
apple: {
|
||||
id: 'apple-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('resets an Apple user', async () => {
|
||||
const task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post('/user/reset', {
|
||||
password: RESET_CONFIRMATION,
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageTaskNotFound'),
|
||||
});
|
||||
|
||||
expect(user.tasksOrder.habits).to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
2
website/client/package-lock.json
generated
2
website/client/package-lock.json
generated
@@ -18676,7 +18676,7 @@
|
||||
"strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
|
||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
|
||||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
v-html="$t('resetText1')"
|
||||
>
|
||||
</div>
|
||||
<div class="split-lists my-3 ">
|
||||
<div class="split-lists my-3">
|
||||
<ul>
|
||||
<li
|
||||
v-once
|
||||
@@ -60,18 +60,49 @@
|
||||
<div
|
||||
v-once
|
||||
v-html="$t('resetText2')"
|
||||
class="mb-3"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasPassword"
|
||||
v-html="$t('resetTextLocal')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-html="$t('resetTextSocial', {magicWord: 'RESET'})"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<current-password-input
|
||||
v-if="hasPassword"
|
||||
:show-forget-password="true"
|
||||
:is-valid="mixinData.currentPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.currentPasswordIssues"
|
||||
@passwordValue="passwordValue = $event"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="input-area"
|
||||
>
|
||||
<div class="form">
|
||||
<div class="settings-label">
|
||||
{{ $t("confirm") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="passwordValue"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<save-cancel-buttons
|
||||
:disable-save="passwordValue === ''"
|
||||
:disable-save="!enableReset"
|
||||
primary-button-color="btn-danger"
|
||||
primary-button-label="resetAccount"
|
||||
@saveClicked="reset()"
|
||||
@@ -118,6 +149,12 @@ export default {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
hasPassword () {
|
||||
return this.user.auth.local.has_password;
|
||||
},
|
||||
enableReset () {
|
||||
return this.hasPassword ? Boolean(this.passwordValue) : this.passwordValue === 'RESET';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async reset () {
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||
"wrongPassword": "Password is incorrect. If you forgot your password, click \"Forgot Password.\"",
|
||||
"incorrectDeletePhrase": "Please type <%= magicWord %> in all capital letters to delete your account.",
|
||||
"incorrectResetPhrase": "Please type <%= magicWord %> in all capital letters to reset your account.",
|
||||
"notAnEmail": "Invalid email address.",
|
||||
"emailTaken": "Email address is already used in an account.",
|
||||
"newEmailRequired": "Missing new email address.",
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
"resetDetail3": "All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data.",
|
||||
"resetDetail4": "You will lose all your equipment except Subscriber Mystery Items and free commemorative items. You will be able to buy the deleted items back, including all limited edition equipment (you will need to be in the correct class to re-buy class-specific gear).",
|
||||
"resetText2": "Another option is using an <b>Orb of Rebirth</b>, which will reset everything else while preserving your Tasks and Equipment.",
|
||||
"resetTextLocal": "If you're absolutely certain, type your password into the text box below.",
|
||||
"resetTextSocial": "If you're absolutely certain, type <b>\"<%= magicWord %>\"</b> into the text box below.",
|
||||
"deleteLocalAccountText": "<b>Are you sure?</b> 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": "<b>Are you sure?</b> 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 <b>\"<%= magicWord %>\"</b> into the text box below.",
|
||||
"API": "API",
|
||||
|
||||
@@ -285,7 +285,7 @@ api.deleteUser = {
|
||||
(user.auth.facebook.id || user.auth.google.id || user.auth.apple.id)
|
||||
&& password !== DELETE_CONFIRMATION
|
||||
) {
|
||||
throw new NotAuthorized(res.t('incorrectDeletePhrase', { magicWord: 'DELETE' }));
|
||||
throw new NotAuthorized(res.t('incorrectDeletePhrase', { magicWord: DELETE_CONFIRMATION }));
|
||||
}
|
||||
|
||||
const { feedback } = req.body;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { BadRequest, NotAuthorized } from '../../libs/errors';
|
||||
import * as passwordUtils from '../../libs/password';
|
||||
|
||||
const api = {};
|
||||
const RESET_CONFIRMATION = 'RESET';
|
||||
|
||||
/*
|
||||
* NOTE most user routes are still in the v3 controller
|
||||
@@ -224,9 +225,14 @@ api.userReset = {
|
||||
throw new BadRequest(res.t('missingPassword'));
|
||||
}
|
||||
|
||||
const isValidPassword = await passwordUtils.compare(user, password);
|
||||
if (!isValidPassword) {
|
||||
throw new NotAuthorized(res.t('wrongPassword'));
|
||||
if (user.auth.local.hashed_password && user.auth.local.email) {
|
||||
const isValidPassword = await passwordUtils.compare(user, password);
|
||||
if (!isValidPassword) throw new NotAuthorized(res.t('wrongPassword'));
|
||||
} else if (
|
||||
(user.auth.facebook.id || user.auth.google.id || user.auth.apple.id)
|
||||
&& password !== RESET_CONFIRMATION
|
||||
) {
|
||||
throw new NotAuthorized(res.t('incorrectResetPhrase', { magicWord: RESET_CONFIRMATION }));
|
||||
}
|
||||
|
||||
await userLib.reset(req, res, { isV3: false });
|
||||
|
||||
Reference in New Issue
Block a user