mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-04-14 11:46:23 +00:00
* add date check * achievements modal polishing * refresh private-messages page when you are already on it * add countbadge knob to change the example * fix lint * typos * typos * typos * add toggle for achievements categories * typo * fix test * fix edit avatar modal cannot be closed * WIP(settings): subscriber page improvements * WIP(subscriptions): more design build-out * fix(css): disabled button styles * fix(css): better Amazon targeting * fix hide tooltip + align header correctly * disable perfect scroll * load messages on refresh event * fix header label + conversation actions not breaking layout on hover * WIP(g1g1): notif * WIP(g1g1): notif cont'd * fix(test): snowball change * fix(event): feature NYE card * chore(sprites): compile * fix(bgs): include TT required field * add gifting banner to the max height calculation * chore(event): enable winter customizations * WIP(gifting): partial modal implementation * feat(gifting): select giftee modal * fix(gifting): notification order, modal dismiss * Begin implementing sign in with apple # Conflicts: # package-lock.json # website/common/script/constants.js # website/server/libs/auth/social.js # website/server/models/user/schema.js * Add apple sign in button to website * fix lint errors * fix config json * fix(modals): correct some repops * fix(gifting): style updates * fix(buy): modal style changes * fix(modals): also clean out "prev" * Attempt workaround for sign in with apple on android * temporarily log everything as error * refactor(modals): hide in dismiss event * fix temporary test failure * changes to sign in with apple * fix: first batch of layout issues for private messages + auto sizing textarea * fix(modals): new dismiss logic * fix(modals): new dismiss no go?? * Only use email scope * print debugging * . * .. * ... * username second line - open profile on face-avatar/conversation name - fix textarea height * temporarily disable apple auth and just return data for debugging * Hopefully this works * ..... * WIP(subscription): unsubscribed state * . * .. * MAYBE THIS ACTUALLY WORKS??? * Implement apple sign in * fix some urls * fix urls * fix redirect and auth * attempt to also request name * fix lint error * WIP(subscription): partial subscribed * chore(sprites): compile * Change approach so that it actually works * fix config error * fix lint errors * Fix * fix lint error * lint error * WIP(subscription): finish subscribed * refresh on sync * new "you dont have any messages" style + changed min textarea height * new conversationItem style / layout * reset message unread on reload * chore(npm): update package-locks * fix styles / textarea height * feat(subscription): revised sub page RC * list optOut / chatRevoked informations for each conversation + show why its disabled * Improve apple redirect view * Fix apple icon on group task registration page * WIP(adventure): prereqs * Block / Unblock - correct disabled states - $gray-200 instead of 300/400 * canReceive not checking chatRevoked * fix: faceAvatar / userLink open the selected conversation user * check if the target user is blocking the logged-in user * fix(subs): style tweaks * fix(profiles): short circuit contributor Attempted fix for #11830 * chore(sprites): compile * fix(content): missing potion data * fix(content): missing string * WIP(drops): new modal * fix(subs): moar style tweaks * check if blocks is undefined * max-height instead of height * fix "no messages" state + canReceive on a new conversation * WIP(adventure): analytics fixes etc * Improve apple signin handling * fixed conversations width (280px on max 768 width page) * feat(adventure): random egg+potion on 2nd task * fix(lint): noworkies * fix(modal): correctly construct classes * fix(tests): expectations and escape * Fix typo * use base url from env variables * fix lint * call autosize after message is sent * fix urls * always verify token * throw error when social auth could not retrieve id * Store emails correctly for apple auth * Retrieve name when authenticating through apple * Fix lint errors * fix all lint errors * fix(content): missing strings * Revert "always verify token" This reverts commit 8ac40c76bfa880f68fa3ce350a86ce2151b9cf95. # Conflicts: # website/server/libs/auth/social.js * Correctly load name * remove extra changes * remove extra logger call * reset package and package-lock * add back missing packages * use name from apple * add support for multiple apple public keys * add some unit and integration tests * add apple auth integration test * tweak social signup buttons * pixel pushing Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com> Co-authored-by: Sabe Jones <sabrecat@gmail.com> Co-authored-by: negue <eugen.bolz@gmail.com> Co-authored-by: Phillip Thelen <phillip@habitica.com>
368 lines
10 KiB
JavaScript
368 lines
10 KiB
JavaScript
import {
|
|
find,
|
|
each,
|
|
map,
|
|
} from 'lodash';
|
|
import {
|
|
checkExistence,
|
|
createAndPopulateGroup,
|
|
generateGroup,
|
|
generateUser,
|
|
generateChallenge,
|
|
translate as t,
|
|
} from '../../../../helpers/api-integration/v3';
|
|
import {
|
|
sha1MakeSalt,
|
|
sha1Encrypt as sha1EncryptPassword,
|
|
} from '../../../../../website/server/libs/password';
|
|
import * as email from '../../../../../website/server/libs/email';
|
|
|
|
const DELETE_CONFIRMATION = 'DELETE';
|
|
|
|
describe('DELETE /user', () => {
|
|
let user;
|
|
const 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 });
|
|
});
|
|
|
|
it('returns an error if password is wrong', async () => {
|
|
await expect(user.del('/user', {
|
|
password: 'wrong-password',
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 401,
|
|
error: 'NotAuthorized',
|
|
message: t('wrongPassword'),
|
|
});
|
|
});
|
|
|
|
it('returns an error if password 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 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 () => {
|
|
const feedbackText = 'spam feedback ';
|
|
let feedback = feedbackText;
|
|
while (feedback.length < 10000) {
|
|
feedback += feedbackText;
|
|
}
|
|
|
|
await expect(user.del('/user', {
|
|
password,
|
|
feedback,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: 'Account deletion feedback is limited to 10,000 characters. For lengthy feedback, email admin@habitica.com.',
|
|
});
|
|
});
|
|
|
|
it('returns an error if user has active subscription', async () => {
|
|
const userWithSubscription = await generateUser({ 'purchased.plan.customerId': 'fake-customer-id' });
|
|
|
|
await expect(userWithSubscription.del('/user', {
|
|
password,
|
|
})).to.be.rejected.and.to.eventually.eql({
|
|
code: 401,
|
|
error: 'NotAuthorized',
|
|
message: t('cannotDeleteActiveAccount'),
|
|
});
|
|
});
|
|
|
|
it('deletes the user\'s tasks', async () => {
|
|
await user.post('/tasks/user', {
|
|
text: 'test habit',
|
|
type: 'habit',
|
|
});
|
|
await user.sync();
|
|
|
|
// gets the user's tasks ids
|
|
const ids = [];
|
|
each(user.tasksOrder, idsForOrder => {
|
|
ids.push(...idsForOrder);
|
|
});
|
|
|
|
expect(ids.length).to.be.above(0); // make sure the user has some task to delete
|
|
|
|
await user.del('/user', {
|
|
password,
|
|
});
|
|
|
|
await Promise.all(map(ids, id => expect(checkExistence('tasks', id)).to.eventually.eql(false)));
|
|
});
|
|
|
|
it('reduces memberCount in challenges user is linked to', async () => {
|
|
const populatedGroup = await createAndPopulateGroup({
|
|
members: 2,
|
|
});
|
|
|
|
const { group } = populatedGroup;
|
|
const authorizedUser = populatedGroup.members[1];
|
|
|
|
const challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
|
await populatedGroup.groupLeader.post(`/challenges/${challenge._id}/join`);
|
|
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
|
|
|
await challenge.sync();
|
|
|
|
expect(challenge.memberCount).to.eql(2);
|
|
|
|
await authorizedUser.del('/user', {
|
|
password,
|
|
});
|
|
|
|
await challenge.sync();
|
|
|
|
expect(challenge.memberCount).to.eql(1);
|
|
});
|
|
|
|
it('sends feedback to the admin email', async () => {
|
|
sandbox.spy(email, 'sendTxn');
|
|
|
|
const feedback = 'Reasons for Deletion';
|
|
await user.del('/user', {
|
|
password,
|
|
feedback,
|
|
});
|
|
|
|
expect(email.sendTxn).to.be.calledOnce;
|
|
|
|
sandbox.restore();
|
|
});
|
|
|
|
it('does not send email if no feedback is supplied', async () => {
|
|
sandbox.spy(email, 'sendTxn');
|
|
|
|
await user.del('/user', {
|
|
password,
|
|
});
|
|
|
|
expect(email.sendTxn).to.not.be.called;
|
|
|
|
sandbox.restore();
|
|
});
|
|
|
|
it('deletes the user with a legacy sha1 password', async () => {
|
|
const textPassword = 'mySecretPassword';
|
|
const salt = sha1MakeSalt();
|
|
const sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
|
|
|
|
await user.update({
|
|
'auth.local.hashed_password': sha1HashedPassword,
|
|
'auth.local.passwordHashMethod': 'sha1',
|
|
'auth.local.salt': salt,
|
|
});
|
|
|
|
await user.sync();
|
|
|
|
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
|
|
expect(user.auth.local.salt).to.equal(salt);
|
|
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
|
|
|
|
// delete the user
|
|
await user.del('/user', {
|
|
password: textPassword,
|
|
});
|
|
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
|
});
|
|
|
|
context('last member of a party', () => {
|
|
let party;
|
|
|
|
beforeEach(async () => {
|
|
party = await generateGroup(user, {
|
|
type: 'party',
|
|
privacy: 'private',
|
|
});
|
|
});
|
|
|
|
it('deletes party when user is the only member', async () => {
|
|
await user.del('/user', {
|
|
password,
|
|
});
|
|
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
|
});
|
|
});
|
|
|
|
context('last member of a private guild', () => {
|
|
let privateGuild;
|
|
|
|
beforeEach(async () => {
|
|
privateGuild = await generateGroup(user, {
|
|
type: 'guild',
|
|
privacy: 'private',
|
|
});
|
|
});
|
|
|
|
it('deletes guild when user is the only member', async () => {
|
|
await user.del('/user', {
|
|
password,
|
|
});
|
|
await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
|
|
});
|
|
});
|
|
|
|
context('groups user is leader of', () => {
|
|
let guild; let oldLeader; let
|
|
newLeader;
|
|
|
|
beforeEach(async () => {
|
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
|
groupDetails: {
|
|
type: 'guild',
|
|
privacy: 'public',
|
|
},
|
|
members: 1,
|
|
});
|
|
|
|
guild = group;
|
|
newLeader = members[0]; // eslint-disable-line prefer-destructuring
|
|
oldLeader = groupLeader;
|
|
});
|
|
|
|
it('chooses new group leader for any group user was the leader of', async () => {
|
|
await oldLeader.del('/user', {
|
|
password,
|
|
});
|
|
|
|
const updatedGuild = await newLeader.get(`/groups/${guild._id}`);
|
|
|
|
expect(updatedGuild.leader).to.exist;
|
|
expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
|
|
});
|
|
});
|
|
|
|
context('groups user is a part of', () => {
|
|
let group1; let group2; let userToDelete; let
|
|
otherUser;
|
|
|
|
beforeEach(async () => {
|
|
userToDelete = await generateUser({ balance: 10 });
|
|
|
|
group1 = await generateGroup(userToDelete, {
|
|
type: 'guild',
|
|
privacy: 'public',
|
|
});
|
|
|
|
const { group, members } = await createAndPopulateGroup({
|
|
groupDetails: {
|
|
type: 'guild',
|
|
privacy: 'public',
|
|
},
|
|
members: 3,
|
|
});
|
|
|
|
group2 = group;
|
|
otherUser = members[0]; // eslint-disable-line prefer-destructuring
|
|
|
|
await userToDelete.post(`/groups/${group2._id}/join`);
|
|
});
|
|
|
|
it('removes user from all groups user was a part of', async () => {
|
|
await userToDelete.del('/user', {
|
|
password,
|
|
});
|
|
|
|
const updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
|
|
const updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
|
|
const userInGroup = find(updatedGroup2Members, member => member._id === userToDelete._id);
|
|
|
|
expect(updatedGroup1Members).to.be.empty;
|
|
expect(updatedGroup2Members).to.not.be.empty;
|
|
expect(userInGroup).to.not.exist;
|
|
});
|
|
});
|
|
});
|
|
|
|
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', { magicWord: 'DELETE' }),
|
|
});
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
|
|
context('user with Apple auth', async () => {
|
|
beforeEach(async () => {
|
|
user = await generateUser({
|
|
auth: {
|
|
apple: {
|
|
id: 'apple-id',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it('deletes a Apple user', async () => {
|
|
await user.del('/user', {
|
|
password: DELETE_CONFIRMATION,
|
|
});
|
|
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
|
|
});
|
|
});
|
|
});
|