mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 21:27:23 +01:00
Prerequisites to removing Facebook authentication (#13683)
* Don't sign in user when trying to connect a social account that was already created * Log social users into matching local auth accounts If the social account has an email that already exists as a local user, instead of creating a new account log them into their account and add the social auth to the account * If possible set local authentication email for social users * Allow password reset emails to be sent to social login users * lint fixes * Fix issues and tests * fix tests * Fix lint error.
This commit is contained in:
@@ -32,8 +32,8 @@
|
|||||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||||
"LOGGLY_TOKEN": "example-token",
|
"LOGGLY_TOKEN": "example-token",
|
||||||
"MAINTENANCE_MODE": "false",
|
"MAINTENANCE_MODE": "false",
|
||||||
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev",
|
||||||
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test",
|
||||||
"MONGODB_POOL_SIZE": "10",
|
"MONGODB_POOL_SIZE": "10",
|
||||||
"NODE_ENV": "development",
|
"NODE_ENV": "development",
|
||||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
requester,
|
requester,
|
||||||
@@ -9,15 +10,18 @@ describe('GET /user/auth/apple', () => {
|
|||||||
let api;
|
let api;
|
||||||
let user;
|
let user;
|
||||||
const appleEndpoint = '/user/auth/apple';
|
const appleEndpoint = '/user/auth/apple';
|
||||||
|
let randomAppleId = '123456';
|
||||||
before(async () => {
|
|
||||||
const expectedResult = { id: 'appleId', name: 'an apple user' };
|
|
||||||
sandbox.stub(appleAuth, 'appleProfile').returns(Promise.resolve(expectedResult));
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
api = requester();
|
api = requester();
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
|
randomAppleId = generateUUID();
|
||||||
|
const expectedResult = { id: randomAppleId, name: 'an apple user' };
|
||||||
|
sandbox.stub(appleAuth, 'appleProfile').returns(Promise.resolve(expectedResult));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
appleAuth.appleProfile.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('registers a new user', async () => {
|
it('registers a new user', async () => {
|
||||||
@@ -26,7 +30,7 @@ describe('GET /user/auth/apple', () => {
|
|||||||
expect(response.apiToken).to.exist;
|
expect(response.apiToken).to.exist;
|
||||||
expect(response.id).to.exist;
|
expect(response.id).to.exist;
|
||||||
expect(response.newUser).to.be.true;
|
expect(response.newUser).to.be.true;
|
||||||
await expect(getProperty('users', response.id, 'auth.apple.id')).to.eventually.equal('appleId');
|
await expect(getProperty('users', response.id, 'auth.apple.id')).to.eventually.equal(randomAppleId);
|
||||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('an apple user');
|
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('an apple user');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
requester,
|
requester,
|
||||||
@@ -10,14 +11,15 @@ describe('POST /user/auth/social', () => {
|
|||||||
let api;
|
let api;
|
||||||
let user;
|
let user;
|
||||||
const endpoint = '/user/auth/social';
|
const endpoint = '/user/auth/social';
|
||||||
const randomAccessToken = '123456';
|
let randomAccessToken = '123456';
|
||||||
const facebookId = 'facebookId';
|
let randomFacebookId = 'facebookId';
|
||||||
const googleId = 'googleId';
|
let randomGoogleId = 'googleId';
|
||||||
let network = 'NoNetwork';
|
let network = 'NoNetwork';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
api = requester();
|
api = requester();
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
|
randomAccessToken = generateUUID();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if network is not supported', async () => {
|
it('fails if network is not supported', async () => {
|
||||||
@@ -32,12 +34,23 @@ describe('POST /user/auth/social', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('facebook', () => {
|
describe('facebook', () => {
|
||||||
before(async () => {
|
beforeEach(async () => {
|
||||||
const expectedResult = { id: facebookId, displayName: 'a facebook user' };
|
randomFacebookId = generateUUID();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomFacebookId,
|
||||||
|
displayName: 'a facebook user',
|
||||||
|
emails: [
|
||||||
|
{ value: `${user.auth.local.username}+facebook@example.com` },
|
||||||
|
],
|
||||||
|
};
|
||||||
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
||||||
network = 'facebook';
|
network = 'facebook';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
passport._strategies.facebook.userProfile.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('registers a new user', async () => {
|
it('registers a new user', async () => {
|
||||||
const response = await api.post(endpoint, {
|
const response = await api.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
@@ -51,7 +64,8 @@ describe('POST /user/auth/social', () => {
|
|||||||
|
|
||||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user');
|
||||||
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist;
|
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist;
|
||||||
await expect(getProperty('users', response.id, 'auth.facebook.id')).to.eventually.equal(facebookId);
|
await expect(getProperty('users', response.id, 'auth.local.email')).to.eventually.equal(`${user.auth.local.username}+facebook@example.com`);
|
||||||
|
await expect(getProperty('users', response.id, 'auth.facebook.id')).to.eventually.equal(randomFacebookId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs an existing user in', async () => {
|
it('logs an existing user in', async () => {
|
||||||
@@ -68,6 +82,57 @@ describe('POST /user/auth/social', () => {
|
|||||||
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
expect(response.id).to.eql(registerResponse.id);
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
expect(response.newUser).to.be.false;
|
expect(response.newUser).to.be.false;
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs an existing user in if they have local auth with matching email', async () => {
|
||||||
|
passport._strategies.facebook.userProfile.restore();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomFacebookId,
|
||||||
|
displayName: 'a facebook user',
|
||||||
|
emails: [
|
||||||
|
{ value: user.auth.local.email },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
||||||
|
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.eql(user.apiToken);
|
||||||
|
expect(response.id).to.eql(user._id);
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs an existing user into their social account if they have local auth with matching email', async () => {
|
||||||
|
const registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
// This is important for existing accounts before the new social handling
|
||||||
|
passport._strategies.facebook.userProfile.restore();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomFacebookId,
|
||||||
|
displayName: 'a facebook user',
|
||||||
|
emails: [
|
||||||
|
{ value: user.auth.local.email },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
||||||
|
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
|
expect(response.apiToken).not.to.eql(user.apiToken);
|
||||||
|
expect(response.id).not.to.eql(user._id);
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add social auth to an existing user', async () => {
|
it('add social auth to an existing user', async () => {
|
||||||
@@ -76,11 +141,28 @@ describe('POST /user/auth/social', () => {
|
|||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.apiToken).to.exist;
|
expect(response.apiToken).to.eql(user.apiToken);
|
||||||
expect(response.id).to.exist;
|
expect(response.id).to.eql(user._id);
|
||||||
expect(response.newUser).to.be.false;
|
expect(response.newUser).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not log into other account if social auth already exists', async () => {
|
||||||
|
const registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
|
||||||
|
await expect(user.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('socialAlreadyExists'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
xit('enrolls a new user in an A/B test', async () => {
|
xit('enrolls a new user in an A/B test', async () => {
|
||||||
await api.post(endpoint, {
|
await api.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
@@ -92,12 +174,23 @@ describe('POST /user/auth/social', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('google', () => {
|
describe('google', () => {
|
||||||
before(async () => {
|
beforeEach(async () => {
|
||||||
const expectedResult = { id: googleId, displayName: 'a google user' };
|
randomGoogleId = generateUUID();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomGoogleId,
|
||||||
|
displayName: 'a google user',
|
||||||
|
emails: [
|
||||||
|
{ value: `${user.auth.local.username}+google@example.com` },
|
||||||
|
],
|
||||||
|
};
|
||||||
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||||
network = 'google';
|
network = 'google';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
passport._strategies.google.userProfile.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('registers a new user', async () => {
|
it('registers a new user', async () => {
|
||||||
const response = await api.post(endpoint, {
|
const response = await api.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
@@ -107,7 +200,8 @@ describe('POST /user/auth/social', () => {
|
|||||||
expect(response.apiToken).to.exist;
|
expect(response.apiToken).to.exist;
|
||||||
expect(response.id).to.exist;
|
expect(response.id).to.exist;
|
||||||
expect(response.newUser).to.be.true;
|
expect(response.newUser).to.be.true;
|
||||||
await expect(getProperty('users', response.id, 'auth.google.id')).to.eventually.equal(googleId);
|
await expect(getProperty('users', response.id, 'auth.google.id')).to.eventually.equal(randomGoogleId);
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.email')).to.eventually.equal(`${user.auth.local.username}+google@example.com`);
|
||||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,6 +219,57 @@ describe('POST /user/auth/social', () => {
|
|||||||
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
expect(response.id).to.eql(registerResponse.id);
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
expect(response.newUser).to.be.false;
|
expect(response.newUser).to.be.false;
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs an existing user in if they have local auth with matching email', async () => {
|
||||||
|
passport._strategies.google.userProfile.restore();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomGoogleId,
|
||||||
|
displayName: 'a google user',
|
||||||
|
emails: [
|
||||||
|
{ value: user.auth.local.email },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||||
|
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.eql(user.apiToken);
|
||||||
|
expect(response.id).to.eql(user._id);
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs an existing user into their social account if they have local auth with matching email', async () => {
|
||||||
|
const registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
// This is important for existing accounts before the new social handling
|
||||||
|
passport._strategies.google.userProfile.restore();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomGoogleId,
|
||||||
|
displayName: 'a google user',
|
||||||
|
emails: [
|
||||||
|
{ value: user.auth.local.email },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||||
|
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
|
expect(response.apiToken).not.to.eql(user.apiToken);
|
||||||
|
expect(response.id).not.to.eql(user._id);
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add social auth to an existing user', async () => {
|
it('add social auth to an existing user', async () => {
|
||||||
@@ -133,11 +278,28 @@ describe('POST /user/auth/social', () => {
|
|||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.apiToken).to.exist;
|
expect(response.apiToken).to.eql(user.apiToken);
|
||||||
expect(response.id).to.exist;
|
expect(response.id).to.eql(user._id);
|
||||||
expect(response.newUser).to.be.false;
|
expect(response.newUser).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not log into other account if social auth already exists', async () => {
|
||||||
|
const registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
|
||||||
|
await expect(user.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('socialAlreadyExists'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
xit('enrolls a new user in an A/B test', async () => {
|
xit('enrolls a new user in an A/B test', async () => {
|
||||||
await api.post(endpoint, {
|
await api.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
|||||||
@@ -25,6 +25,19 @@ describe('POST /user/reset-password', async () => {
|
|||||||
expect(user.auth.local.hashed_password).to.not.eql(previousPassword);
|
expect(user.auth.local.hashed_password).to.not.eql(previousPassword);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resets password for social users', async () => {
|
||||||
|
const email = `${user.auth.local.username}+google@example.com`;
|
||||||
|
await user.update({ 'auth.google.emails': [{ value: email }] });
|
||||||
|
await user.sync();
|
||||||
|
const previousPassword = user.auth.local.passwordResetCode;
|
||||||
|
const response = await user.post(endpoint, {
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
expect(response).to.eql({ data: {}, message: t('passwordReset') });
|
||||||
|
await user.sync();
|
||||||
|
expect(user.auth.local.passwordResetCode).to.not.eql(previousPassword);
|
||||||
|
});
|
||||||
|
|
||||||
it('same message on error as on success', async () => {
|
it('same message on error as on success', async () => {
|
||||||
const response = await user.post(endpoint, {
|
const response = await user.post(endpoint, {
|
||||||
email: 'nonExistent@email.com',
|
email: 'nonExistent@email.com',
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="form-group row text-center"
|
|
||||||
v-if="!registering"
|
v-if="!registering"
|
||||||
|
class="form-group row text-center"
|
||||||
>
|
>
|
||||||
<div class="col-12 col-md-12">
|
<div class="col-12 col-md-12">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -178,12 +178,12 @@ import sword from '@/assets/svg/sword.svg';
|
|||||||
import { worldStateMixin } from '@/mixins/worldState';
|
import { worldStateMixin } from '@/mixins/worldState';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [
|
|
||||||
worldStateMixin,
|
|
||||||
],
|
|
||||||
components: {
|
components: {
|
||||||
BaseNotification,
|
BaseNotification,
|
||||||
},
|
},
|
||||||
|
mixins: [
|
||||||
|
worldStateMixin,
|
||||||
|
],
|
||||||
data () {
|
data () {
|
||||||
const questData = quests.quests.dysheartener;
|
const questData = quests.quests.dysheartener;
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,10 @@
|
|||||||
<h4 class="popover-content-title">
|
<h4 class="popover-content-title">
|
||||||
{{ context.item.text }}
|
{{ context.item.text }}
|
||||||
</h4>
|
</h4>
|
||||||
<questInfo :quest="context.item" :purchased="true" />
|
<questInfo
|
||||||
|
:quest="context.item"
|
||||||
|
:purchased="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h4 class="popover-content-title">
|
<h4 class="popover-content-title">
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="notification-animation-holder">
|
<div class="notification-animation-holder">
|
||||||
<div class="notification-holder"
|
<div
|
||||||
@click="handleOnClick()">
|
class="notification-holder"
|
||||||
<div v-if="notification.type === 'drop'"
|
@click="handleOnClick()"
|
||||||
class="icon-item">
|
>
|
||||||
<div :class="notification.icon" class="icon-negative-margin"></div>
|
<div
|
||||||
|
v-if="notification.type === 'drop'"
|
||||||
|
class="icon-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="notification.icon"
|
||||||
|
class="icon-negative-margin"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -32,7 +39,10 @@
|
|||||||
class="svg-icon"
|
class="svg-icon"
|
||||||
v-html="icons.gold"
|
v-html="icons.gold"
|
||||||
></div>
|
></div>
|
||||||
<div class="icon-text" v-html="notification.text"></div>
|
<div
|
||||||
|
class="icon-text"
|
||||||
|
v-html="notification.text"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -63,7 +73,10 @@
|
|||||||
class="svg-icon"
|
class="svg-icon"
|
||||||
v-html="icons.mana"
|
v-html="icons.mana"
|
||||||
></div>
|
></div>
|
||||||
<div class="icon-text" v-html="notification.text"></div>
|
<div
|
||||||
|
class="icon-text"
|
||||||
|
v-html="notification.text"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -78,7 +91,10 @@
|
|||||||
class="svg-icon"
|
class="svg-icon"
|
||||||
v-html="icons.sword"
|
v-html="icons.sword"
|
||||||
></div>
|
></div>
|
||||||
<div class="icon-text" v-html="notification.text"></div>
|
<div
|
||||||
|
class="icon-text"
|
||||||
|
v-html="notification.text"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -98,7 +114,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -81,12 +81,12 @@ const DELAY_DELETE_AND_NEW = 60;
|
|||||||
const DELAY_FILLING_ENTRIES = 240;
|
const DELAY_FILLING_ENTRIES = 240;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [
|
|
||||||
worldStateMixin,
|
|
||||||
],
|
|
||||||
components: {
|
components: {
|
||||||
notification,
|
notification,
|
||||||
},
|
},
|
||||||
|
mixins: [
|
||||||
|
worldStateMixin,
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
preventQueue: {
|
preventQueue: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@@ -133,6 +133,7 @@
|
|||||||
"unsupportedNetwork": "This network is not currently supported.",
|
"unsupportedNetwork": "This network is not currently supported.",
|
||||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||||
"onlySocialAttachLocal": "Local authentication can be added to only a social account.",
|
"onlySocialAttachLocal": "Local authentication can be added to only a social account.",
|
||||||
|
"socialAlreadyExists": "This social login is already linked to an existing Habitica account.",
|
||||||
"invalidReqParams": "Invalid request parameters.",
|
"invalidReqParams": "Invalid request parameters.",
|
||||||
"memberIdRequired": "\"member\" must be a valid UUID.",
|
"memberIdRequired": "\"member\" must be a valid UUID.",
|
||||||
"heroIdRequired": "\"heroId\" must be a valid UUID.",
|
"heroIdRequired": "\"heroId\" must be a valid UUID.",
|
||||||
|
|||||||
@@ -341,7 +341,14 @@ api.resetPassword = {
|
|||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
const email = req.body.email.toLowerCase();
|
const email = req.body.email.toLowerCase();
|
||||||
const user = await User.findOne({ 'auth.local.email': email }).exec();
|
const user = await User.findOne({
|
||||||
|
$or: [
|
||||||
|
{ 'auth.local.email': email },
|
||||||
|
{ 'auth.apple.emails.value': email },
|
||||||
|
{ 'auth.google.emails.value': email },
|
||||||
|
{ 'auth.facebook.emails.value': email },
|
||||||
|
],
|
||||||
|
}).exec();
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
// create an encrypted link to be used to reset the password
|
// create an encrypted link to be used to reset the password
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import common from '../../../common';
|
import common from '../../../common';
|
||||||
import { BadRequest } from '../errors';
|
import { BadRequest, NotAuthorized } from '../errors';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import {
|
import {
|
||||||
generateUsername,
|
generateUsername,
|
||||||
@@ -24,7 +24,7 @@ function _passportProfile (network, accessToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loginSocial (req, res) { // eslint-disable-line import/prefer-default-export
|
export async function loginSocial (req, res) { // eslint-disable-line import/prefer-default-export
|
||||||
const existingUser = res.locals.user;
|
let existingUser = res.locals.user;
|
||||||
const { network } = req.body;
|
const { network } = req.body;
|
||||||
|
|
||||||
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
|
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
|
||||||
@@ -47,9 +47,28 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
|||||||
|
|
||||||
// User already signed up
|
// User already signed up
|
||||||
if (user) {
|
if (user) {
|
||||||
|
if (existingUser) {
|
||||||
|
throw new NotAuthorized(res.t('socialAlreadyExists'));
|
||||||
|
}
|
||||||
return loginRes(user, req, res);
|
return loginRes(user, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let email;
|
||||||
|
if (profile.emails && profile.emails[0] && profile.emails[0].value) {
|
||||||
|
email = profile.emails[0].value.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existingUser && email) {
|
||||||
|
existingUser = await User.findOne({ 'auth.local.email': email }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
existingUser.auth[network] = {
|
||||||
|
id: profile.id,
|
||||||
|
emails: profile.emails,
|
||||||
|
};
|
||||||
|
user = existingUser;
|
||||||
|
} else {
|
||||||
const generatedUsername = generateUsername();
|
const generatedUsername = generateUsername();
|
||||||
|
|
||||||
user = {
|
user = {
|
||||||
@@ -61,6 +80,7 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
|||||||
local: {
|
local: {
|
||||||
username: generatedUsername,
|
username: generatedUsername,
|
||||||
lowerCaseUsername: generatedUsername,
|
lowerCaseUsername: generatedUsername,
|
||||||
|
email,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
@@ -73,11 +93,6 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
|||||||
verifiedUsername: true,
|
verifiedUsername: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
existingUser.auth[network] = user.auth[network];
|
|
||||||
user = existingUser;
|
|
||||||
} else {
|
|
||||||
user = new User(user);
|
user = new User(user);
|
||||||
user.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
|
user.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
|
||||||
}
|
}
|
||||||
@@ -85,19 +100,15 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
|||||||
const savedUser = await user.save();
|
const savedUser = await user.save();
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
user.newUser = true;
|
savedUser.newUser = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = loginRes(user, req, res);
|
const response = loginRes(savedUser, req, res);
|
||||||
|
|
||||||
// Clean previous email preferences
|
// Clean previous email preferences
|
||||||
if (
|
if (email) {
|
||||||
savedUser.auth[network].emails
|
|
||||||
&& savedUser.auth[network].emails[0]
|
|
||||||
&& savedUser.auth[network].emails[0].value
|
|
||||||
) {
|
|
||||||
EmailUnsubscription
|
EmailUnsubscription
|
||||||
.remove({ email: savedUser.auth[network].emails[0].value.toLowerCase() })
|
.remove({ email })
|
||||||
.exec()
|
.exec()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
|
|||||||
Reference in New Issue
Block a user