Files
habitica/website/server/libs/auth/social.js
T
Phillip Thelen 24841346dc Purge Facebook (#13696)
* 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.

* purge Facebook.

Only keep it in some select places to allow for some compatablilty.

* Fix error

* fix error

* Let settings handle it when you don't have a password set but an email

* fix error

* Fix boolean logic

* fix json conversion

* .

* fix password reset for old social accounts

* 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.

* purge Facebook.

Only keep it in some select places to allow for some compatablilty.

* Fix error

* fix error

* Let settings handle it when you don't have a password set but an email

* fix error

* Fix boolean logic

* fix json conversion

* fix password reset for old social accounts

* Revert "lint fixes"

This reverts commit c244b1651c.

# Conflicts:
#	website/client/src/components/auth/registerLoginReset.vue
#	website/client/src/components/static/contact.vue

* Revert "fix password reset for old social accounts"

This reverts commit 7e0069a80f.

* fix duplicate code

* chore(misc): remove irrelevant changes

* chore(privacy): update policy page with note about FB

Co-authored-by: SabreCat <sabe@habitica.com>
2022-09-15 18:22:52 -05:00

159 lines
4.3 KiB
JavaScript

import passport from 'passport';
import common from '../../../common';
import { BadRequest, NotAuthorized } from '../errors';
import logger from '../logger';
import {
generateUsername,
loginRes,
} from './utils';
import { appleProfile } from './apple';
import { model as User } from '../../models/user';
import { model as EmailUnsubscription } from '../../models/emailUnsubscription';
import { sendTxn as sendTxnEmail } from '../email';
function _passportProfile (network, accessToken) {
return new Promise((resolve, reject) => {
passport._strategies[network].userProfile(accessToken, (err, profile) => {
if (err) {
reject(err);
} else {
resolve(profile);
}
});
});
}
export async function socialEmailToLocal (user) {
const socialEmail = (user.auth.google && user.auth.google.emails
&& user.auth.google.emails[0].value)
|| (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0].value)
|| (user.auth.apple && user.auth.apple.emails && user.auth.apple.emails[0].value);
if (socialEmail) {
const conflictingUser = await User.findOne(
{ 'auth.local.email': socialEmail },
{ _id: 1 },
).exec();
if (!conflictingUser) return socialEmail;
}
return undefined;
}
export async function loginSocial (req, res) { // eslint-disable-line import/prefer-default-export
let existingUser = res.locals.user;
const { network } = req.body;
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
.find(supportedNetwork => supportedNetwork.key === network);
if (!isSupportedNetwork) throw new BadRequest(res.t('unsupportedNetwork'));
let profile = {};
if (network === 'apple') {
profile = await appleProfile(req);
} else {
const accessToken = req.body.authResponse.access_token;
profile = await _passportProfile(network, accessToken);
}
if (!profile.id) throw new BadRequest(res.t('invalidData'));
let user = await User.findOne({
[`auth.${network}.id`]: profile.id,
}, { _id: 1, apiToken: 1, auth: 1 }).exec();
// User already signed up
if (user) {
if (existingUser) {
throw new NotAuthorized(res.t('socialAlreadyExists'));
}
if (!user.auth.local.email) {
user.auth.local.email = await socialEmailToLocal(user);
if (user.auth.local.email) {
await user.save();
}
}
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();
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
}
const savedUser = await user.save();
if (!existingUser) {
savedUser.newUser = true;
}
const response = loginRes(savedUser, req, res);
// Clean previous email preferences
if (email) {
EmailUnsubscription
.remove({ email })
.exec()
.then(() => {
if (!existingUser) {
if (savedUser._ABtests && savedUser._ABtests.welcomeEmailSplit) {
sendTxnEmail(savedUser, savedUser._ABtests.welcomeEmailSplit);
} else {
sendTxnEmail(savedUser, 'welcome');
}
}
})
.catch(err => logger.error(err)); // eslint-disable-line max-nested-callbacks
}
if (!existingUser) {
res.analytics.track('register', {
category: 'acquisition',
type: network,
gaLabel: network,
uuid: savedUser._id,
headers: req.headers,
user: savedUser,
});
}
return response;
}