diff --git a/services/web/app/src/Features/User/ThirdPartyIdentityManager.js b/services/web/app/src/Features/User/ThirdPartyIdentityManager.js index 5851935c16..8cf0c043f9 100644 --- a/services/web/app/src/Features/User/ThirdPartyIdentityManager.js +++ b/services/web/app/src/Features/User/ThirdPartyIdentityManager.js @@ -1,184 +1,196 @@ -const EmailHandler = require('../../../../app/src/Features/Email/EmailHandler') +const APP_ROOT = '../../../../app/src' +const EmailHandler = require(`${APP_ROOT}/Features/Email/EmailHandler`) const Errors = require('../Errors/Errors') -const logger = require('logger-sharelatex') -const { User } = require('../../models/User') -const settings = require('settings-sharelatex') const _ = require('lodash') +const logger = require('logger-sharelatex') +const settings = require('settings-sharelatex') +const { User } = require(`${APP_ROOT}/models/User`) +const { promisifyAll } = require(`${APP_ROOT}/util/promises`) const oauthProviders = settings.oauthProviders || {} -const ThirdPartyIdentityManager = (module.exports = { - getUser(providerId, externalUserId, callback) { - if (providerId == null || externalUserId == null) { - return callback(new Error('invalid arguments')) +function getUser(providerId, externalUserId, callback) { + if (providerId == null || externalUserId == null) { + return callback(new Error('invalid arguments')) + } + const query = _getUserQuery(providerId, externalUserId) + User.findOne(query, function(err, user) { + if (err != null) { + return callback(err) } - const query = ThirdPartyIdentityManager._getUserQuery( + if (!user) { + return callback(new Errors.ThirdPartyUserNotFoundError()) + } + callback(null, user) + }) +} + +function login(providerId, externalUserId, externalData, callback) { + ThirdPartyIdentityManager.getUser(providerId, externalUserId, function( + err, + user + ) { + if (err != null) { + return callback(err) + } + if (!externalData) { + return callback(null, user) + } + const query = _getUserQuery(providerId, externalUserId) + const update = _thirdPartyIdentifierUpdate( + user, providerId, - externalUserId + externalUserId, + externalData ) - User.findOne(query, function(err, user) { - if (err != null) { - return callback(err) - } - if (!user) { - return callback(new Errors.ThirdPartyUserNotFoundError()) - } - callback(null, user) - }) - }, + User.findOneAndUpdate(query, update, { new: true }, callback) + }) +} - login(providerId, externalUserId, externalData, callback) { - ThirdPartyIdentityManager.getUser(providerId, externalUserId, function( - err, - user - ) { - if (err != null) { - return callback(err) - } - if (!externalData) { - return callback(null, user) - } - const query = ThirdPartyIdentityManager._getUserQuery( - providerId, - externalUserId - ) - const update = ThirdPartyIdentityManager._thirdPartyIdentifierUpdate( - user, - providerId, +function link( + userId, + providerId, + externalUserId, + externalData, + callback, + retry +) { + if (!oauthProviders[providerId]) { + return callback(new Error('Not a valid provider')) + } + const query = { + _id: userId, + 'thirdPartyIdentifiers.providerId': { + $ne: providerId + } + } + const update = { + $push: { + thirdPartyIdentifiers: { externalUserId, - externalData - ) - User.findOneAndUpdate(query, update, { new: true }, callback) - }) - }, - - _getUserQuery(providerId, externalUserId) { - externalUserId = externalUserId.toString() - providerId = providerId.toString() - const query = { - 'thirdPartyIdentifiers.externalUserId': externalUserId, - 'thirdPartyIdentifiers.providerId': providerId - } - return query - }, - - _thirdPartyIdentifierUpdate(user, providerId, externalUserId, externalData) { - providerId = providerId.toString() - // get third party identifier object from array - const thirdPartyIdentifier = user.thirdPartyIdentifiers.find( - tpi => - tpi.externalUserId === externalUserId && tpi.providerId === providerId - ) - // do recursive merge of new data over existing data - _.merge(thirdPartyIdentifier.externalData, externalData) - const update = { 'thirdPartyIdentifiers.$': thirdPartyIdentifier } - return update - }, - - // register: () -> - // this should be implemented once we move to having v2 as the master - // but for now we need to register with v1 then call link once that - // is complete - - link(userId, providerId, externalUserId, externalData, callback, retry) { - if (!oauthProviders[providerId]) { - return callback(new Error('Not a valid provider')) - } - const query = { - _id: userId, - 'thirdPartyIdentifiers.providerId': { - $ne: providerId + externalData, + providerId } } - const update = { - $push: { - thirdPartyIdentifiers: { + } + // add new tpi only if an entry for the provider does not exist + // projection includes thirdPartyIdentifiers for tests + User.findOneAndUpdate(query, update, { new: 1 }, (err, res) => { + if (err && err.code === 11000) { + callback(new Errors.ThirdPartyIdentityExistsError()) + } else if (err != null) { + callback(err) + } else if (res) { + const emailOptions = { + to: res.email, + provider: oauthProviders[providerId].name + } + EmailHandler.sendEmail( + 'emailThirdPartyIdentifierLinked', + emailOptions, + error => { + if (error != null) { + logger.warn(error) + } + return callback(null, res) + } + ) + } else if (retry) { + // if already retried then throw error + callback(new Error('update failed')) + } else { + // attempt to clear existing entry then retry + ThirdPartyIdentityManager.unlink(userId, providerId, function(err) { + if (err != null) { + return callback(err) + } + ThirdPartyIdentityManager.link( + userId, + providerId, externalUserId, externalData, - providerId - } - } - } - // add new tpi only if an entry for the provider does not exist - // projection includes thirdPartyIdentifiers for tests - User.findOneAndUpdate(query, update, { new: 1 }, (err, res) => { - if (err && err.code === 11000) { - callback(new Errors.ThirdPartyIdentityExistsError()) - } else if (err != null) { - callback(err) - } else if (res) { - const emailOptions = { - to: res.email, - provider: oauthProviders[providerId].name - } - EmailHandler.sendEmail( - 'emailThirdPartyIdentifierLinked', - emailOptions, - error => { - if (error != null) { - logger.warn(error) - } - return callback(null, res) - } + callback, + true ) - } else if (retry) { - // if already retried then throw error - callback(new Error('update failed')) - } else { - // attempt to clear existing entry then retry - ThirdPartyIdentityManager.unlink(userId, providerId, function(err) { - if (err != null) { - return callback(err) - } - ThirdPartyIdentityManager.link( - userId, - providerId, - externalUserId, - externalData, - callback, - true - ) - }) - } - }) - }, + }) + } + }) +} - unlink(userId, providerId, callback) { - if (!oauthProviders[providerId]) { - return callback(new Error('Not a valid provider')) - } - const query = { - _id: userId - } - const update = { - $pull: { - thirdPartyIdentifiers: { - providerId - } - } - } - // projection includes thirdPartyIdentifiers for tests - User.findOneAndUpdate(query, update, { new: 1 }, (err, res) => { - if (err != null) { - callback(err) - } else if (!res) { - callback(new Error('update failed')) - } else { - const emailOptions = { - to: res.email, - provider: oauthProviders[providerId].name - } - EmailHandler.sendEmail( - 'emailThirdPartyIdentifierUnlinked', - emailOptions, - error => { - if (error != null) { - logger.warn(error) - } - return callback(null, res) - } - ) - } - }) +function unlink(userId, providerId, callback) { + if (!oauthProviders[providerId]) { + return callback(new Error('Not a valid provider')) } -}) + const query = { + _id: userId + } + const update = { + $pull: { + thirdPartyIdentifiers: { + providerId + } + } + } + // projection includes thirdPartyIdentifiers for tests + User.findOneAndUpdate(query, update, { new: 1 }, (err, res) => { + if (err != null) { + callback(err) + } else if (!res) { + callback(new Error('update failed')) + } else { + const emailOptions = { + to: res.email, + provider: oauthProviders[providerId].name + } + EmailHandler.sendEmail( + 'emailThirdPartyIdentifierUnlinked', + emailOptions, + error => { + if (error != null) { + logger.warn(error) + } + return callback(null, res) + } + ) + } + }) +} + +function _getUserQuery(providerId, externalUserId) { + externalUserId = externalUserId.toString() + providerId = providerId.toString() + const query = { + 'thirdPartyIdentifiers.externalUserId': externalUserId, + 'thirdPartyIdentifiers.providerId': providerId + } + return query +} + +function _thirdPartyIdentifierUpdate( + user, + providerId, + externalUserId, + externalData +) { + providerId = providerId.toString() + // get third party identifier object from array + const thirdPartyIdentifier = user.thirdPartyIdentifiers.find( + tpi => + tpi.externalUserId === externalUserId && tpi.providerId === providerId + ) + // do recursive merge of new data over existing data + _.merge(thirdPartyIdentifier.externalData, externalData) + const update = { 'thirdPartyIdentifiers.$': thirdPartyIdentifier } + return update +} + +const ThirdPartyIdentityManager = { + getUser, + login, + link, + unlink +} + +ThirdPartyIdentityManager.promises = promisifyAll(ThirdPartyIdentityManager) + +module.exports = ThirdPartyIdentityManager diff --git a/services/web/test/acceptance/src/helpers/UserHelper.js b/services/web/test/acceptance/src/helpers/UserHelper.js index 944f7cbda5..71da9cc2c0 100644 --- a/services/web/test/acceptance/src/helpers/UserHelper.js +++ b/services/web/test/acceptance/src/helpers/UserHelper.js @@ -41,13 +41,15 @@ module.exports = class UserHelper { this.user = null // cookie jar this.jar = request.jar() + // create new request instance + this.request = request.defaults({}) // initialize request instance with default options this.setRequestDefaults() } setRequestDefaults(defaults = {}) { // request-promise instance for making requests - this.request = request.defaults({ + this.request = this.request.defaults({ baseUrl: UserHelper.baseUrl(), followRedirect: false, jar: this.jar,