Revert "Prerequisites to removing Facebook authentication (#13683)"

This reverts commit 1177ad8b8c.
This commit is contained in:
SabreCat
2022-01-25 09:30:37 -06:00
parent 9a6b05fe67
commit b3c466ad5a
12 changed files with 72 additions and 288 deletions

View File

@@ -32,8 +32,8 @@
"LOGGLY_SUBDOMAIN": "example-subdomain",
"LOGGLY_TOKEN": "example-token",
"MAINTENANCE_MODE": "false",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test",
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
"MONGODB_POOL_SIZE": "10",
"NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",

View File

@@ -1,4 +1,3 @@
import { v4 as generateUUID } from 'uuid';
import {
generateUser,
requester,
@@ -10,18 +9,15 @@ describe('GET /user/auth/apple', () => {
let api;
let user;
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 () => {
api = requester();
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 () => {
@@ -30,7 +26,7 @@ describe('GET /user/auth/apple', () => {
expect(response.apiToken).to.exist;
expect(response.id).to.exist;
expect(response.newUser).to.be.true;
await expect(getProperty('users', response.id, 'auth.apple.id')).to.eventually.equal(randomAppleId);
await expect(getProperty('users', response.id, 'auth.apple.id')).to.eventually.equal('appleId');
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('an apple user');
});

View File

@@ -1,5 +1,4 @@
import passport from 'passport';
import { v4 as generateUUID } from 'uuid';
import {
generateUser,
requester,
@@ -11,15 +10,14 @@ describe('POST /user/auth/social', () => {
let api;
let user;
const endpoint = '/user/auth/social';
let randomAccessToken = '123456';
let randomFacebookId = 'facebookId';
let randomGoogleId = 'googleId';
const randomAccessToken = '123456';
const facebookId = 'facebookId';
const googleId = 'googleId';
let network = 'NoNetwork';
beforeEach(async () => {
api = requester();
user = await generateUser();
randomAccessToken = generateUUID();
});
it('fails if network is not supported', async () => {
@@ -34,23 +32,12 @@ describe('POST /user/auth/social', () => {
});
describe('facebook', () => {
beforeEach(async () => {
randomFacebookId = generateUUID();
const expectedResult = {
id: randomFacebookId,
displayName: 'a facebook user',
emails: [
{ value: `${user.auth.local.username}+facebook@example.com` },
],
};
before(async () => {
const expectedResult = { id: facebookId, displayName: 'a facebook user' };
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
network = 'facebook';
});
afterEach(async () => {
passport._strategies.facebook.userProfile.restore();
});
it('registers a new user', async () => {
const response = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
@@ -64,8 +51,7 @@ 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, 'auth.local.lowerCaseUsername')).to.exist;
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);
await expect(getProperty('users', response.id, 'auth.facebook.id')).to.eventually.equal(facebookId);
});
it('logs an existing user in', async () => {
@@ -82,57 +68,6 @@ describe('POST /user/auth/social', () => {
expect(response.apiToken).to.eql(registerResponse.apiToken);
expect(response.id).to.eql(registerResponse.id);
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 () => {
@@ -141,28 +76,11 @@ describe('POST /user/auth/social', () => {
network,
});
expect(response.apiToken).to.eql(user.apiToken);
expect(response.id).to.eql(user._id);
expect(response.apiToken).to.exist;
expect(response.id).to.exist;
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 () => {
await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
@@ -174,23 +92,12 @@ describe('POST /user/auth/social', () => {
});
describe('google', () => {
beforeEach(async () => {
randomGoogleId = generateUUID();
const expectedResult = {
id: randomGoogleId,
displayName: 'a google user',
emails: [
{ value: `${user.auth.local.username}+google@example.com` },
],
};
before(async () => {
const expectedResult = { id: googleId, displayName: 'a google user' };
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
network = 'google';
});
afterEach(async () => {
passport._strategies.google.userProfile.restore();
});
it('registers a new user', async () => {
const response = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
@@ -200,8 +107,7 @@ describe('POST /user/auth/social', () => {
expect(response.apiToken).to.exist;
expect(response.id).to.exist;
expect(response.newUser).to.be.true;
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, 'auth.google.id')).to.eventually.equal(googleId);
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
});
@@ -219,57 +125,6 @@ describe('POST /user/auth/social', () => {
expect(response.apiToken).to.eql(registerResponse.apiToken);
expect(response.id).to.eql(registerResponse.id);
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 () => {
@@ -278,28 +133,11 @@ describe('POST /user/auth/social', () => {
network,
});
expect(response.apiToken).to.eql(user.apiToken);
expect(response.id).to.eql(user._id);
expect(response.apiToken).to.exist;
expect(response.id).to.exist;
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 () => {
await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase

View File

@@ -25,19 +25,6 @@ describe('POST /user/reset-password', async () => {
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 () => {
const response = await user.post(endpoint, {
email: 'nonExistent@email.com',

View File

@@ -20,8 +20,8 @@
</div>
</div>
<div
v-if="!registering"
class="form-group row text-center"
v-if="!registering"
>
<div class="col-12 col-md-12">
<div

View File

@@ -178,12 +178,12 @@ import sword from '@/assets/svg/sword.svg';
import { worldStateMixin } from '@/mixins/worldState';
export default {
components: {
BaseNotification,
},
mixins: [
worldStateMixin,
],
components: {
BaseNotification,
},
data () {
const questData = quests.quests.dysheartener;

View File

@@ -194,10 +194,7 @@
<h4 class="popover-content-title">
{{ context.item.text }}
</h4>
<questInfo
:quest="context.item"
:purchased="true"
/>
<questInfo :quest="context.item" :purchased="true" />
</div>
<div v-else>
<h4 class="popover-content-title">

View File

@@ -1,17 +1,10 @@
<template>
<div class="notification-animation-holder">
<div
class="notification-holder"
@click="handleOnClick()"
>
<div
v-if="notification.type === 'drop'"
class="icon-item"
>
<div
:class="notification.icon"
class="icon-negative-margin"
></div>
<div class="notification-holder"
@click="handleOnClick()">
<div v-if="notification.type === 'drop'"
class="icon-item">
<div :class="notification.icon" class="icon-negative-margin"></div>
</div>
<div
@@ -39,10 +32,7 @@
class="svg-icon"
v-html="icons.gold"
></div>
<div
class="icon-text"
v-html="notification.text"
></div>
<div class="icon-text" v-html="notification.text"></div>
</div>
</div>
<div
@@ -73,10 +63,7 @@
class="svg-icon"
v-html="icons.mana"
></div>
<div
class="icon-text"
v-html="notification.text"
></div>
<div class="icon-text" v-html="notification.text"></div>
</div>
</div>
<div
@@ -91,10 +78,7 @@
class="svg-icon"
v-html="icons.sword"
></div>
<div
class="icon-text"
v-html="notification.text"
></div>
<div class="icon-text" v-html="notification.text"></div>
</div>
</div>
<div
@@ -114,6 +98,7 @@
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -81,12 +81,12 @@ const DELAY_DELETE_AND_NEW = 60;
const DELAY_FILLING_ENTRIES = 240;
export default {
components: {
notification,
},
mixins: [
worldStateMixin,
],
components: {
notification,
},
props: {
preventQueue: {
type: Boolean,

View File

@@ -133,7 +133,6 @@
"unsupportedNetwork": "This network is not currently supported.",
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
"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.",
"memberIdRequired": "\"member\" must be a valid UUID.",
"heroIdRequired": "\"heroId\" must be a valid UUID.",

View File

@@ -341,14 +341,7 @@ api.resetPassword = {
if (validationErrors) throw validationErrors;
const email = req.body.email.toLowerCase();
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();
const user = await User.findOne({ 'auth.local.email': email }).exec();
if (user) {
// create an encrypted link to be used to reset the password

View File

@@ -1,6 +1,6 @@
import passport from 'passport';
import common from '../../../common';
import { BadRequest, NotAuthorized } from '../errors';
import { BadRequest } from '../errors';
import logger from '../logger';
import {
generateUsername,
@@ -24,7 +24,7 @@ function _passportProfile (network, accessToken) {
}
export async function loginSocial (req, res) { // eslint-disable-line import/prefer-default-export
let existingUser = res.locals.user;
const existingUser = res.locals.user;
const { network } = req.body;
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
@@ -47,52 +47,37 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
// User already signed up
if (user) {
if (existingUser) {
throw new NotAuthorized(res.t('socialAlreadyExists'));
}
return loginRes(user, req, res);
}
let email;
if (profile.emails && profile.emails[0] && profile.emails[0].value) {
email = profile.emails[0].value.toLowerCase();
}
const generatedUsername = generateUsername();
if (!existingUser && email) {
existingUser = await User.findOne({ 'auth.local.email': email }).exec();
}
user = {
auth: {
[network]: {
id: profile.id,
emails: profile.emails,
},
local: {
username: generatedUsername,
lowerCaseUsername: generatedUsername,
},
},
profile: {
name: profile.displayName || profile.name || profile.username,
},
preferences: {
language: req.language,
},
flags: {
verifiedUsername: true,
},
};
if (existingUser) {
existingUser.auth[network] = {
id: profile.id,
emails: profile.emails,
};
existingUser.auth[network] = user.auth[network];
user = existingUser;
} else {
const generatedUsername = generateUsername();
user = {
auth: {
[network]: {
id: profile.id,
emails: profile.emails,
},
local: {
username: generatedUsername,
lowerCaseUsername: generatedUsername,
email,
},
},
profile: {
name: profile.displayName || profile.name || profile.username,
},
preferences: {
language: req.language,
},
flags: {
verifiedUsername: true,
},
};
user = new User(user);
user.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
}
@@ -100,15 +85,19 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
const savedUser = await user.save();
if (!existingUser) {
savedUser.newUser = true;
user.newUser = true;
}
const response = loginRes(savedUser, req, res);
const response = loginRes(user, req, res);
// Clean previous email preferences
if (email) {
if (
savedUser.auth[network].emails
&& savedUser.auth[network].emails[0]
&& savedUser.auth[network].emails[0].value
) {
EmailUnsubscription
.remove({ email })
.remove({ email: savedUser.auth[network].emails[0].value.toLowerCase() })
.exec()
.then(() => {
if (!existingUser) {