overleaf/services/web/app/src/Features/User/SAMLIdentityManager.js

160 lines
4.1 KiB
JavaScript
Raw Normal View History

const EmailHandler = require('../Email/EmailHandler')
const Errors = require('../Errors/Errors')
const logger = require('logger-sharelatex')
const OError = require('@overleaf/o-error')
const { User } = require('../../models/User')
const UserGetter = require('../User/UserGetter')
const UserUpdater = require('../User/UserUpdater')
function _addIdentifier(userId, externalUserId, providerId) {
providerId = providerId.toString()
const query = {
_id: userId,
'samlIdentifiers.providerId': {
$ne: providerId
}
}
const update = {
$push: {
samlIdentifiers: {
externalUserId,
providerId
}
}
}
// First update user.samlIdentifiers
let updatedUser = User.findOneAndUpdate(query, update, { new: true }).exec()
try {
updatedUser = User.findOneAndUpdate(query, update, { new: true }).exec()
} catch (err) {
if (err && err.code === 11000) {
throw new Errors.SAMLIdentityExistsError()
} else if (err != null) {
logger.log(err, userId, 'failed to add institution SAML identifier')
throw new OError(err)
}
}
return updatedUser
}
function _getUserQuery(providerId, externalUserId) {
externalUserId = externalUserId.toString()
providerId = providerId.toString()
const query = {
'samlIdentifiers.externalUserId': externalUserId,
'samlIdentifiers.providerId': providerId
}
return query
}
async function _addInstitutionEmail(userId, email, providerId) {
const user = await UserGetter.promises.getUser(userId)
const query = {
_id: userId,
'emails.email': email
}
const update = {
$set: {
'emails.$.samlProviderId': providerId.toString()
}
}
if (user == null) {
logger.log(userId, 'could not find user for institution SAML linking')
throw new Errors.NotFoundError('user not found')
}
const emailAlreadyAssociated = user.emails.find(e => e.email === email)
if (emailAlreadyAssociated && emailAlreadyAssociated.confirmedAt) {
await UserUpdater.promises.updateUser(query, update)
} else if (emailAlreadyAssociated) {
// add and confirm email
await UserUpdater.promises.confirmEmail(user._id, email)
await UserUpdater.promises.updateUser(query, update)
} else {
// add and confirm email
await UserUpdater.promises.addEmailAddress(user._id, email)
await UserUpdater.promises.confirmEmail(user._id, email)
await UserUpdater.promises.updateUser(query, update)
}
}
async function _sendLinkedEmail(userId, providerName) {
const user = await UserGetter.promises.getUser(userId, { email: 1 })
const emailOptions = {
to: user.email,
provider: providerName
}
EmailHandler.sendEmail(
'emailThirdPartyIdentifierLinked',
emailOptions,
error => {
if (error != null) {
logger.warn(error)
}
}
)
}
function _sendUnlinkedEmail(primaryEmail, providerName) {
const emailOptions = {
to: primaryEmail,
provider: providerName
}
EmailHandler.sendEmail(
'emailThirdPartyIdentifierUnlinked',
emailOptions,
error => {
if (error != null) {
logger.warn(error)
}
}
)
}
async function getUser(providerId, externalUserId) {
if (providerId == null || externalUserId == null) {
throw new Error('invalid arguments')
}
const query = _getUserQuery(providerId, externalUserId)
let user = await User.findOne(query).exec()
if (!user) {
throw new Errors.SAMLUserNotFoundError()
}
return user
}
async function linkAccounts(
userId,
externalUserId,
institutionEmail,
providerId,
providerName
) {
await _addIdentifier(userId, externalUserId, providerId)
await _addInstitutionEmail(userId, institutionEmail, providerId)
await _sendLinkedEmail(userId, providerName)
}
async function unlinkAccounts(userId, primaryEmail, providerId, providerName) {
const query = {
_id: userId
}
const update = {
$pull: {
samlIdentifiers: {
providerId
}
}
}
await User.update(query, update).exec()
_sendUnlinkedEmail(primaryEmail, providerName)
}
const SAMLIdentityManager = {
getUser,
linkAccounts,
unlinkAccounts
}
module.exports = SAMLIdentityManager