mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #17946 from overleaf/rh-promisify-third-party-identity-
[web] Promisify ThirdPartyIdentityManager and ThirdPartyIdentityManagerTests GitOrigin-RevId: f7d24f73213fb0a43eb453aa21749b21ba60b83d
This commit is contained in:
parent
a496e479b2
commit
9601fd097a
2 changed files with 211 additions and 229 deletions
|
@ -4,148 +4,128 @@ const EmailOptionsHelper = require('../../../../app/src/Features/Email/EmailOpti
|
||||||
const Errors = require('../Errors/Errors')
|
const Errors = require('../Errors/Errors')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const logger = require('@overleaf/logger')
|
const logger = require('@overleaf/logger')
|
||||||
const OError = require('@overleaf/o-error')
|
|
||||||
const settings = require('@overleaf/settings')
|
const settings = require('@overleaf/settings')
|
||||||
const { User } = require('../../../../app/src/models/User')
|
const { User } = require('../../../../app/src/models/User')
|
||||||
const { promisifyAll } = require('@overleaf/promise-utils')
|
const { callbackify } = require('@overleaf/promise-utils')
|
||||||
|
const OError = require('@overleaf/o-error')
|
||||||
|
|
||||||
const oauthProviders = settings.oauthProviders || {}
|
const oauthProviders = settings.oauthProviders || {}
|
||||||
|
|
||||||
function getUser(providerId, externalUserId, callback) {
|
async function getUser(providerId, externalUserId) {
|
||||||
if (providerId == null || externalUserId == null) {
|
if (providerId == null || externalUserId == null) {
|
||||||
return callback(
|
throw new OError('invalid SSO arguments', {
|
||||||
new OError('invalid SSO arguments', {
|
externalUserId,
|
||||||
externalUserId,
|
providerId,
|
||||||
providerId,
|
})
|
||||||
})
|
}
|
||||||
)
|
|
||||||
|
const query = _getUserQuery(providerId, externalUserId)
|
||||||
|
const user = await User.findOne(query).exec()
|
||||||
|
if (!user) {
|
||||||
|
throw new Errors.ThirdPartyUserNotFoundError()
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login(providerId, externalUserId, externalData) {
|
||||||
|
const user = await ThirdPartyIdentityManager.promises.getUser(
|
||||||
|
providerId,
|
||||||
|
externalUserId
|
||||||
|
)
|
||||||
|
if (!externalData) {
|
||||||
|
return user
|
||||||
}
|
}
|
||||||
const query = _getUserQuery(providerId, externalUserId)
|
const query = _getUserQuery(providerId, externalUserId)
|
||||||
User.findOne(query, function (err, user) {
|
const update = _thirdPartyIdentifierUpdate(
|
||||||
if (err != null) {
|
user,
|
||||||
return callback(err)
|
|
||||||
}
|
|
||||||
if (!user) {
|
|
||||||
return callback(new Errors.ThirdPartyUserNotFoundError())
|
|
||||||
}
|
|
||||||
callback(null, user)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function login(providerId, externalUserId, externalData, callback) {
|
|
||||||
ThirdPartyIdentityManager.getUser(
|
|
||||||
providerId,
|
providerId,
|
||||||
externalUserId,
|
externalUserId,
|
||||||
function (err, user) {
|
externalData
|
||||||
if (err != null) {
|
|
||||||
return callback(err)
|
|
||||||
}
|
|
||||||
if (!externalData) {
|
|
||||||
return callback(null, user)
|
|
||||||
}
|
|
||||||
const query = _getUserQuery(providerId, externalUserId)
|
|
||||||
const update = _thirdPartyIdentifierUpdate(
|
|
||||||
user,
|
|
||||||
providerId,
|
|
||||||
externalUserId,
|
|
||||||
externalData
|
|
||||||
)
|
|
||||||
User.findOneAndUpdate(query, update, { new: true }, callback)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
return await User.findOneAndUpdate(query, update, { new: true }).exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
function link(
|
async function link(
|
||||||
userId,
|
userId,
|
||||||
providerId,
|
providerId,
|
||||||
externalUserId,
|
externalUserId,
|
||||||
externalData,
|
externalData,
|
||||||
auditLog,
|
auditLog,
|
||||||
callback,
|
|
||||||
retry
|
retry
|
||||||
) {
|
) {
|
||||||
const accountLinked = true
|
const accountLinked = true
|
||||||
if (!oauthProviders[providerId]) {
|
if (!oauthProviders[providerId]) {
|
||||||
return callback(new Error('Not a valid provider'))
|
throw new Error('Not a valid provider')
|
||||||
}
|
}
|
||||||
|
|
||||||
UserAuditLogHandler.addEntry(
|
await UserAuditLogHandler.promises.addEntry(
|
||||||
userId,
|
userId,
|
||||||
'link-sso',
|
'link-sso',
|
||||||
auditLog.initiatorId,
|
auditLog.initiatorId,
|
||||||
auditLog.ipAddress,
|
auditLog.ipAddress,
|
||||||
{
|
{
|
||||||
providerId,
|
providerId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
_id: userId,
|
||||||
|
'thirdPartyIdentifiers.providerId': {
|
||||||
|
$ne: providerId,
|
||||||
},
|
},
|
||||||
error => {
|
}
|
||||||
if (error) {
|
const update = {
|
||||||
return callback(error)
|
$push: {
|
||||||
}
|
thirdPartyIdentifiers: {
|
||||||
const query = {
|
externalUserId,
|
||||||
_id: userId,
|
externalData,
|
||||||
'thirdPartyIdentifiers.providerId': {
|
providerId,
|
||||||
$ne: providerId,
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const update = {
|
// add new tpi only if an entry for the provider does not exist
|
||||||
$push: {
|
// projection includes thirdPartyIdentifiers for tests
|
||||||
thirdPartyIdentifiers: {
|
let res
|
||||||
externalUserId,
|
try {
|
||||||
externalData,
|
res = await User.findOneAndUpdate(query, update, { new: 1 }).exec()
|
||||||
providerId,
|
} catch (err) {
|
||||||
},
|
if (err.code === 11000) {
|
||||||
},
|
throw new Errors.ThirdPartyIdentityExistsError({
|
||||||
}
|
info: { externalUserId },
|
||||||
// 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({
|
|
||||||
info: { externalUserId },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else if (err != null) {
|
|
||||||
callback(err)
|
|
||||||
} else if (res) {
|
|
||||||
_sendSecurityAlert(accountLinked, providerId, res, userId)
|
|
||||||
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,
|
|
||||||
auditLog,
|
|
||||||
function (err) {
|
|
||||||
if (err != null) {
|
|
||||||
return callback(err)
|
|
||||||
}
|
|
||||||
ThirdPartyIdentityManager.link(
|
|
||||||
userId,
|
|
||||||
providerId,
|
|
||||||
externalUserId,
|
|
||||||
externalData,
|
|
||||||
auditLog,
|
|
||||||
callback,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
_sendSecurityAlert(accountLinked, providerId, res, userId)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retry) {
|
||||||
|
// if already retried then throw error
|
||||||
|
throw new Error('update failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to clear existing entry then retry
|
||||||
|
await ThirdPartyIdentityManager.promises.unlink(userId, providerId, auditLog)
|
||||||
|
return await ThirdPartyIdentityManager.promises.link(
|
||||||
|
userId,
|
||||||
|
providerId,
|
||||||
|
externalUserId,
|
||||||
|
externalData,
|
||||||
|
auditLog,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlink(userId, providerId, auditLog, callback) {
|
async function unlink(userId, providerId, auditLog) {
|
||||||
const accountLinked = false
|
const accountLinked = false
|
||||||
if (!oauthProviders[providerId]) {
|
if (!oauthProviders[providerId]) {
|
||||||
return callback(new Error('Not a valid provider'))
|
throw new Error('Not a valid provider')
|
||||||
}
|
}
|
||||||
UserAuditLogHandler.addEntry(
|
|
||||||
|
await UserAuditLogHandler.promises.addEntry(
|
||||||
userId,
|
userId,
|
||||||
'unlink-sso',
|
'unlink-sso',
|
||||||
auditLog.initiatorId,
|
auditLog.initiatorId,
|
||||||
|
@ -153,35 +133,26 @@ function unlink(userId, providerId, auditLog, callback) {
|
||||||
{
|
{
|
||||||
...(auditLog.extraInfo || {}),
|
...(auditLog.extraInfo || {}),
|
||||||
providerId,
|
providerId,
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if (error) {
|
|
||||||
return callback(error)
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
// no need to wait, errors are logged and not passed back
|
|
||||||
_sendSecurityAlert(accountLinked, providerId, res, userId)
|
|
||||||
callback(null, res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
_id: userId,
|
||||||
|
}
|
||||||
|
const update = {
|
||||||
|
$pull: {
|
||||||
|
thirdPartyIdentifiers: {
|
||||||
|
providerId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// projection includes thirdPartyIdentifiers for tests
|
||||||
|
const res = await User.findOneAndUpdate(query, update, { new: 1 })
|
||||||
|
if (!res) {
|
||||||
|
throw new Error('update failed')
|
||||||
|
}
|
||||||
|
_sendSecurityAlert(accountLinked, providerId, res, userId)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getUserQuery(providerId, externalUserId) {
|
function _getUserQuery(providerId, externalUserId) {
|
||||||
|
@ -194,21 +165,21 @@ function _getUserQuery(providerId, externalUserId) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
function _sendSecurityAlert(accountLinked, providerId, user, userId) {
|
async function _sendSecurityAlert(accountLinked, providerId, user, userId) {
|
||||||
const providerName = oauthProviders[providerId].name
|
const providerName = oauthProviders[providerId].name
|
||||||
const emailOptions = EmailOptionsHelper.linkOrUnlink(
|
const emailOptions = EmailOptionsHelper.linkOrUnlink(
|
||||||
accountLinked,
|
accountLinked,
|
||||||
providerName,
|
providerName,
|
||||||
user.email
|
user.email
|
||||||
)
|
)
|
||||||
EmailHandler.sendEmail('securityAlert', emailOptions, error => {
|
try {
|
||||||
if (error) {
|
await EmailHandler.promises.sendEmail('securityAlert', emailOptions)
|
||||||
logger.error(
|
} catch (error) {
|
||||||
{ err: error, userId },
|
logger.error(
|
||||||
`could not send security alert email when ${emailOptions.action.toLowerCase()}`
|
{ err: error, userId },
|
||||||
)
|
`could not send security alert email when ${emailOptions.action.toLowerCase()}`
|
||||||
}
|
)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _thirdPartyIdentifierUpdate(
|
function _thirdPartyIdentifierUpdate(
|
||||||
|
@ -230,12 +201,17 @@ function _thirdPartyIdentifierUpdate(
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThirdPartyIdentityManager = {
|
const ThirdPartyIdentityManager = {
|
||||||
|
getUser: callbackify(getUser),
|
||||||
|
login: callbackify(login),
|
||||||
|
link: callbackify(link),
|
||||||
|
unlink: callbackify(unlink),
|
||||||
|
}
|
||||||
|
|
||||||
|
ThirdPartyIdentityManager.promises = {
|
||||||
getUser,
|
getUser,
|
||||||
login,
|
login,
|
||||||
link,
|
link,
|
||||||
unlink,
|
unlink,
|
||||||
}
|
}
|
||||||
|
|
||||||
ThirdPartyIdentityManager.promises = promisifyAll(ThirdPartyIdentityManager)
|
|
||||||
|
|
||||||
module.exports = ThirdPartyIdentityManager
|
module.exports = ThirdPartyIdentityManager
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const { expect } = require('chai')
|
const { expect } = require('chai')
|
||||||
const SandboxedModule = require('sandboxed-module')
|
const SandboxedModule = require('sandboxed-module')
|
||||||
|
const OError = require('@overleaf/o-error')
|
||||||
const modulePath =
|
const modulePath =
|
||||||
'../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
|
'../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
|
||||||
|
|
||||||
|
@ -18,16 +19,24 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
requires: {
|
requires: {
|
||||||
'../../../../app/src/Features/User/UserAuditLogHandler':
|
'../../../../app/src/Features/User/UserAuditLogHandler':
|
||||||
(this.UserAuditLogHandler = {
|
(this.UserAuditLogHandler = {
|
||||||
addEntry: sinon.stub().yields(),
|
promises: {
|
||||||
|
addEntry: sinon.stub().resolves(),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
'../../../../app/src/Features/Email/EmailHandler': (this.EmailHandler =
|
'../../../../app/src/Features/Email/EmailHandler': (this.EmailHandler =
|
||||||
{
|
{
|
||||||
sendEmail: sinon.stub().yields(),
|
promises: {
|
||||||
|
sendEmail: sinon.stub().resolves(),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
'../../../../app/src/models/User': {
|
'../../../../app/src/models/User': {
|
||||||
User: (this.User = {
|
User: (this.User = {
|
||||||
findOneAndUpdate: sinon.stub().yields(undefined, this.user),
|
findOneAndUpdate: sinon
|
||||||
findOne: sinon.stub().yields(undefined, undefined),
|
.stub()
|
||||||
|
.returns({ exec: sinon.stub().resolves(this.user) }),
|
||||||
|
findOne: sinon.stub().returns({
|
||||||
|
exec: sinon.stub().resolves(undefined),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
'@overleaf/settings': {
|
'@overleaf/settings': {
|
||||||
|
@ -44,28 +53,23 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('getUser', function () {
|
describe('getUser', function () {
|
||||||
it('should an error when missing providerId or externalUserId', function (done) {
|
it('should throw an error when missing providerId or externalUserId', async function () {
|
||||||
this.ThirdPartyIdentityManager.getUser(
|
await expect(
|
||||||
undefined,
|
this.ThirdPartyIdentityManager.promises.getUser(undefined, undefined)
|
||||||
undefined,
|
).to.be.rejectedWith(OError, `invalid SSO arguments`)
|
||||||
(error, user) => {
|
|
||||||
expect(error).to.exist
|
|
||||||
expect(error.message).to.equal('invalid SSO arguments')
|
|
||||||
expect(error.info).to.deep.equal({
|
|
||||||
externalUserId: undefined,
|
|
||||||
providerId: undefined,
|
|
||||||
})
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when user linked', function () {
|
describe('when user linked', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.User.findOne.yields(undefined, this.user)
|
this.User.findOne.returns({
|
||||||
|
exec: sinon.stub().resolves(this.user),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the user', async function () {
|
it('should return the user', async function () {
|
||||||
this.User.findOne.returns(undefined, this.user)
|
this.User.findOne.returns({
|
||||||
|
exec: sinon.stub().resolves(this.user),
|
||||||
|
})
|
||||||
const user = await this.ThirdPartyIdentityManager.promises.getUser(
|
const user = await this.ThirdPartyIdentityManager.promises.getUser(
|
||||||
'google',
|
'google',
|
||||||
'an-id-linked'
|
'an-id-linked'
|
||||||
|
@ -94,7 +98,7 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
this.externalData,
|
this.externalData,
|
||||||
this.auditLog
|
this.auditLog
|
||||||
)
|
)
|
||||||
const emailCall = this.EmailHandler.sendEmail.getCall(0)
|
const emailCall = this.EmailHandler.promises.sendEmail.getCall(0)
|
||||||
expect(emailCall.args[0]).to.equal('securityAlert')
|
expect(emailCall.args[0]).to.equal('securityAlert')
|
||||||
expect(emailCall.args[1].actionDescribed).to.contain(
|
expect(emailCall.args[1].actionDescribed).to.contain(
|
||||||
'a Google account was linked'
|
'a Google account was linked'
|
||||||
|
@ -109,7 +113,9 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
this.externalData,
|
this.externalData,
|
||||||
this.auditLog
|
this.auditLog
|
||||||
)
|
)
|
||||||
expect(this.UserAuditLogHandler.addEntry).to.have.been.calledOnceWith(
|
expect(
|
||||||
|
this.UserAuditLogHandler.promises.addEntry
|
||||||
|
).to.have.been.calledOnceWith(
|
||||||
this.userId,
|
this.userId,
|
||||||
'link-sso',
|
'link-sso',
|
||||||
this.auditLog.initiatorId,
|
this.auditLog.initiatorId,
|
||||||
|
@ -121,49 +127,47 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
})
|
})
|
||||||
describe('errors', function () {
|
describe('errors', function () {
|
||||||
const anError = new Error('oops')
|
const anError = new Error('oops')
|
||||||
it('should not unlink if the UserAuditLogHandler throws an error', function (done) {
|
|
||||||
this.UserAuditLogHandler.addEntry.yields(anError)
|
it('should not unlink if the UserAuditLogHandler throws an error', async function () {
|
||||||
this.ThirdPartyIdentityManager.link(
|
this.UserAuditLogHandler.promises.addEntry.throws(anError)
|
||||||
this.userId,
|
await expect(
|
||||||
'google',
|
this.ThirdPartyIdentityManager.promises.link(
|
||||||
this.externalUserId,
|
|
||||||
this.externalData,
|
|
||||||
this.auditLog,
|
|
||||||
error => {
|
|
||||||
expect(error).to.exist
|
|
||||||
expect(error).to.equal(anError)
|
|
||||||
expect(this.User.findOneAndUpdate).to.not.have.been.called
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
describe('EmailHandler', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.EmailHandler.sendEmail.yields(anError)
|
|
||||||
})
|
|
||||||
it('should log but not return the error', function (done) {
|
|
||||||
this.ThirdPartyIdentityManager.link(
|
|
||||||
this.userId,
|
this.userId,
|
||||||
'google',
|
'google',
|
||||||
this.externalUserId,
|
this.externalUserId,
|
||||||
this.externalData,
|
this.externalData,
|
||||||
this.auditLog,
|
this.auditLog
|
||||||
error => {
|
)
|
||||||
expect(error).to.not.exist
|
).to.be.rejectedWith(anError)
|
||||||
expect(this.logger.error.lastCall).to.be.calledWithExactly(
|
expect(this.User.findOneAndUpdate).to.not.have.been.called
|
||||||
{
|
})
|
||||||
err: anError,
|
|
||||||
userId: this.userId,
|
describe('EmailHandler', function () {
|
||||||
},
|
beforeEach(function () {
|
||||||
'could not send security alert email when new account linked'
|
this.EmailHandler.promises.sendEmail.throws(anError)
|
||||||
)
|
})
|
||||||
done()
|
it('should log but not return the error', async function () {
|
||||||
}
|
await expect(
|
||||||
|
this.ThirdPartyIdentityManager.promises.link(
|
||||||
|
this.userId,
|
||||||
|
'google',
|
||||||
|
this.externalUserId,
|
||||||
|
this.externalData,
|
||||||
|
this.auditLog
|
||||||
|
)
|
||||||
|
).to.be.fulfilled
|
||||||
|
expect(this.logger.error.lastCall).to.be.calledWithExactly(
|
||||||
|
{
|
||||||
|
err: anError,
|
||||||
|
userId: this.userId,
|
||||||
|
},
|
||||||
|
'could not send security alert email when new account linked'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unlink', function () {
|
describe('unlink', function () {
|
||||||
it('should send email alert', async function () {
|
it('should send email alert', async function () {
|
||||||
await this.ThirdPartyIdentityManager.promises.unlink(
|
await this.ThirdPartyIdentityManager.promises.unlink(
|
||||||
|
@ -171,7 +175,7 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
'orcid',
|
'orcid',
|
||||||
this.auditLog
|
this.auditLog
|
||||||
)
|
)
|
||||||
const emailCall = this.EmailHandler.sendEmail.getCall(0)
|
const emailCall = this.EmailHandler.promises.sendEmail.getCall(0)
|
||||||
expect(emailCall.args[0]).to.equal('securityAlert')
|
expect(emailCall.args[0]).to.equal('securityAlert')
|
||||||
expect(emailCall.args[1].actionDescribed).to.contain(
|
expect(emailCall.args[1].actionDescribed).to.contain(
|
||||||
'an ORCID account was unlinked from'
|
'an ORCID account was unlinked from'
|
||||||
|
@ -183,7 +187,9 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
'orcid',
|
'orcid',
|
||||||
this.auditLog
|
this.auditLog
|
||||||
)
|
)
|
||||||
expect(this.UserAuditLogHandler.addEntry).to.have.been.calledOnceWith(
|
expect(
|
||||||
|
this.UserAuditLogHandler.promises.addEntry
|
||||||
|
).to.have.been.calledOnceWith(
|
||||||
this.userId,
|
this.userId,
|
||||||
'unlink-sso',
|
'unlink-sso',
|
||||||
this.auditLog.initiatorId,
|
this.auditLog.initiatorId,
|
||||||
|
@ -193,43 +199,43 @@ describe('ThirdPartyIdentityManager', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('errors', function () {
|
describe('errors', function () {
|
||||||
const anError = new Error('oops')
|
const anError = new Error('oops')
|
||||||
it('should not unlink if the UserAuditLogHandler throws an error', function (done) {
|
|
||||||
this.UserAuditLogHandler.addEntry.yields(anError)
|
it('should not unlink if the UserAuditLogHandler throws an error', async function () {
|
||||||
this.ThirdPartyIdentityManager.unlink(
|
this.UserAuditLogHandler.promises.addEntry.throws(anError)
|
||||||
this.userId,
|
|
||||||
'orcid',
|
await expect(
|
||||||
this.auditLog,
|
this.ThirdPartyIdentityManager.promises.unlink(
|
||||||
error => {
|
this.userId,
|
||||||
expect(error).to.exist
|
'orcid',
|
||||||
expect(error).to.equal(anError)
|
this.auditLog
|
||||||
expect(this.User.findOneAndUpdate).to.not.have.been.called
|
)
|
||||||
done()
|
).to.be.rejectedWith(anError)
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(this.User.findOneAndUpdate).to.not.have.been.called
|
expect(this.User.findOneAndUpdate).to.not.have.been.called
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('EmailHandler', function () {
|
describe('EmailHandler', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.EmailHandler.sendEmail.yields(anError)
|
this.EmailHandler.promises.sendEmail.throws(anError)
|
||||||
})
|
})
|
||||||
it('should log but not return the error', function (done) {
|
it('should log but not return the error', async function () {
|
||||||
this.ThirdPartyIdentityManager.unlink(
|
await expect(
|
||||||
this.userId,
|
this.ThirdPartyIdentityManager.promises.unlink(
|
||||||
'google',
|
this.userId,
|
||||||
this.auditLog,
|
'google',
|
||||||
error => {
|
this.auditLog
|
||||||
expect(error).to.not.exist
|
)
|
||||||
expect(this.logger.error.lastCall).to.be.calledWithExactly(
|
).to.be.fulfilled
|
||||||
{
|
|
||||||
err: anError,
|
expect(this.logger.error.lastCall).to.be.calledWithExactly(
|
||||||
userId: this.userId,
|
{
|
||||||
},
|
err: anError,
|
||||||
'could not send security alert email when account no longer linked'
|
userId: this.userId,
|
||||||
)
|
},
|
||||||
done()
|
'could not send security alert email when account no longer linked'
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue