mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17:22 +01: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 8ac40c76bf.
# 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>
369 lines
10 KiB
JavaScript
369 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);
|
|
});
|
|
});
|
|
});
|