overleaf/services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js
Alf Eaton 1be43911b4 Merge pull request #3942 from overleaf/prettier-trailing-comma
Set Prettier's "trailingComma" setting to "es5"

GitOrigin-RevId: 9f14150511929a855b27467ad17be6ab262fe5d5
2021-04-28 02:10:01 +00:00

236 lines
7.3 KiB
JavaScript

const sinon = require('sinon')
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const modulePath =
'../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
describe('ThirdPartyIdentityManager', function () {
beforeEach(function () {
this.userId = 'a1b2c3'
this.user = {
_id: this.userId,
email: 'example@overleaf.com',
}
this.externalUserId = 'id789'
this.externalData = {}
this.auditLog = { initiatorId: this.userId, ipAddress: '0:0:0:0' }
this.ThirdPartyIdentityManager = SandboxedModule.require(modulePath, {
requires: {
'../../../../app/src/Features/User/UserAuditLogHandler': (this.UserAuditLogHandler = {
addEntry: sinon.stub().yields(),
}),
'../../../../app/src/Features/Email/EmailHandler': (this.EmailHandler = {
sendEmail: sinon.stub().yields(),
}),
'../../../../app/src/models/User': {
User: (this.User = {
findOneAndUpdate: sinon.stub().yields(undefined, this.user),
findOne: sinon.stub().yields(undefined, undefined),
}),
},
'settings-sharelatex': {
oauthProviders: {
google: {
name: 'Google',
},
orcid: {
name: 'Orcid',
},
},
},
},
})
})
describe('getUser', function () {
it('should an error when missing providerId or externalUserId', function (done) {
this.ThirdPartyIdentityManager.getUser(
undefined,
undefined,
(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 () {
beforeEach(function () {
this.User.findOne.yields(undefined, this.user)
})
it('should return the user', async function () {
this.User.findOne.returns(undefined, this.user)
const user = await this.ThirdPartyIdentityManager.promises.getUser(
'google',
'an-id-linked'
)
expect(user).to.deep.equal(this.user)
})
})
it('should return ThirdPartyUserNotFoundError when no user linked', function (done) {
this.ThirdPartyIdentityManager.getUser(
'google',
'an-id-not-linked',
(error, user) => {
expect(error).to.exist
expect(error.name).to.equal('ThirdPartyUserNotFoundError')
done()
}
)
})
})
describe('link', function () {
it('should send email alert', async function () {
await this.ThirdPartyIdentityManager.promises.link(
this.userId,
'google',
this.externalUserId,
this.externalData,
this.auditLog
)
const emailCall = this.EmailHandler.sendEmail.getCall(0)
expect(emailCall.args[0]).to.equal('securityAlert')
expect(emailCall.args[1].actionDescribed).to.contain(
'a Google account was linked'
)
})
it('should update user audit log', async function () {
await this.ThirdPartyIdentityManager.promises.link(
this.userId,
'google',
this.externalUserId,
this.externalData,
this.auditLog
)
expect(this.UserAuditLogHandler.addEntry).to.have.been.calledOnceWith(
this.userId,
'link-sso',
this.auditLog.initiatorId,
this.auditLog.ipAddress,
{
providerId: 'google',
}
)
})
describe('errors', function () {
const anError = new Error('oops')
it('should not unlink if the UserAuditLogHandler throws an error', function (done) {
this.UserAuditLogHandler.addEntry.yields(anError)
this.ThirdPartyIdentityManager.link(
this.userId,
'google',
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,
'google',
this.externalUserId,
this.externalData,
this.auditLog,
error => {
expect(error).to.not.exist
expect(this.logger.error.lastCall).to.be.calledWithExactly(
{
err: anError,
userId: this.userId,
},
'could not send security alert email when Google account linked'
)
done()
}
)
})
})
})
})
describe('unlink', function () {
it('should send email alert', async function () {
await this.ThirdPartyIdentityManager.promises.unlink(
this.userId,
'orcid',
this.auditLog
)
const emailCall = this.EmailHandler.sendEmail.getCall(0)
expect(emailCall.args[0]).to.equal('securityAlert')
expect(emailCall.args[1].actionDescribed).to.contain(
'an Orcid account was unlinked from'
)
})
it('should update user audit log', async function () {
await this.ThirdPartyIdentityManager.promises.unlink(
this.userId,
'orcid',
this.auditLog
)
expect(this.UserAuditLogHandler.addEntry).to.have.been.calledOnceWith(
this.userId,
'unlink-sso',
this.auditLog.initiatorId,
this.auditLog.ipAddress,
{
providerId: 'orcid',
}
)
})
describe('errors', function () {
const anError = new Error('oops')
it('should not unlink if the UserAuditLogHandler throws an error', function (done) {
this.UserAuditLogHandler.addEntry.yields(anError)
this.ThirdPartyIdentityManager.unlink(
this.userId,
'orcid',
this.auditLog,
error => {
expect(error).to.exist
expect(error).to.equal(anError)
expect(this.User.findOneAndUpdate).to.not.have.been.called
done()
}
)
expect(this.User.findOneAndUpdate).to.not.have.been.called
})
describe('EmailHandler', function () {
beforeEach(function () {
this.EmailHandler.sendEmail.yields(anError)
})
it('should log but not return the error', function (done) {
this.ThirdPartyIdentityManager.unlink(
this.userId,
'google',
this.auditLog,
error => {
expect(error).to.not.exist
expect(this.logger.error.lastCall).to.be.calledWithExactly(
{
err: anError,
userId: this.userId,
},
'could not send security alert email when Google account no longer linked'
)
done()
}
)
})
})
})
})
})