mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-02 09:09:33 -05:00
Merge pull request #1903 from overleaf/jel-oauth-email-notification
OAuth link/unlink email notification via v2 GitOrigin-RevId: 36b0c6153d3eb8174adc4fd684837d81be95b644
This commit is contained in:
parent
d07a46c51b
commit
c8b6b83848
4 changed files with 157 additions and 55 deletions
|
@ -23,6 +23,7 @@ const BaseWithHeaderEmailLayout = require(`./Layouts/${
|
||||||
}BaseWithHeaderEmailLayout`)
|
}BaseWithHeaderEmailLayout`)
|
||||||
const SpamSafe = require('./SpamSafe')
|
const SpamSafe = require('./SpamSafe')
|
||||||
|
|
||||||
|
// Single CTA Email
|
||||||
const SingleCTAEmailBody = require(`./Bodies/${
|
const SingleCTAEmailBody = require(`./Bodies/${
|
||||||
settings.brandPrefix
|
settings.brandPrefix
|
||||||
}SingleCTAEmailBody`)
|
}SingleCTAEmailBody`)
|
||||||
|
@ -476,6 +477,38 @@ If you have any questions, you can contact our support team by reply.\
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
templates.emailThirdPartyIdentifierLinked = NoCTAEmailTemplate({
|
||||||
|
subject(opts) {
|
||||||
|
return `Your ${settings.appName} account is now linked with ${
|
||||||
|
opts.provider
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
title(opts) {
|
||||||
|
return `Accounts Linked`
|
||||||
|
},
|
||||||
|
message(opts) {
|
||||||
|
let message = `We're contacting you to notify you that your ${opts.provider}
|
||||||
|
account is now linked to your ${settings.appName} account`
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
templates.emailThirdPartyIdentifierUnlinked = NoCTAEmailTemplate({
|
||||||
|
subject(opts) {
|
||||||
|
return `Your ${settings.appName} account is no longer linked with ${
|
||||||
|
opts.provider
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
title(opts) {
|
||||||
|
return `Accounts No Longer Linked`
|
||||||
|
},
|
||||||
|
message(opts) {
|
||||||
|
let message = `We're contacting you to notify you that your ${opts.provider}
|
||||||
|
account is no longer linked with your ${settings.appName} account.`
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
templates,
|
templates,
|
||||||
CTAEmailTemplate,
|
CTAEmailTemplate,
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
const EmailHandler = require('../../../../app/src/Features/Email/EmailHandler')
|
||||||
const Errors = require('../Errors/Errors')
|
const Errors = require('../Errors/Errors')
|
||||||
|
const logger = require('logger-sharelatex')
|
||||||
const { User } = require('../../models/User')
|
const { User } = require('../../models/User')
|
||||||
const UserUpdater = require('./UserUpdater')
|
const settings = require('settings-sharelatex')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
const oauthProviders = settings.oauthProviders || {}
|
||||||
|
|
||||||
const ThirdPartyIdentityManager = (module.exports = {
|
const ThirdPartyIdentityManager = (module.exports = {
|
||||||
getUser(providerId, externalUserId, callback) {
|
getUser(providerId, externalUserId, callback) {
|
||||||
if (providerId == null || externalUserId == null) {
|
if (providerId == null || externalUserId == null) {
|
||||||
|
@ -77,6 +81,9 @@ const ThirdPartyIdentityManager = (module.exports = {
|
||||||
// is complete
|
// is complete
|
||||||
|
|
||||||
link(userId, providerId, externalUserId, externalData, callback, retry) {
|
link(userId, providerId, externalUserId, externalData, callback, retry) {
|
||||||
|
if (!oauthProviders[providerId]) {
|
||||||
|
return callback(new Error('Not a valid provider'))
|
||||||
|
}
|
||||||
const query = {
|
const query = {
|
||||||
_id: userId,
|
_id: userId,
|
||||||
'thirdPartyIdentifiers.providerId': {
|
'thirdPartyIdentifiers.providerId': {
|
||||||
|
@ -93,20 +100,39 @@ const ThirdPartyIdentityManager = (module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add new tpi only if an entry for the provider does not exist
|
// add new tpi only if an entry for the provider does not exist
|
||||||
UserUpdater.updateUser(query, update, function(err, res) {
|
// projection includes thirdPartyIdentifiers for tests
|
||||||
|
User.findOneAndUpdate(
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
{ projection: { email: 1, thirdPartyIdentifiers: 1 }, new: 1 },
|
||||||
|
(err, res) => {
|
||||||
if (err && err.code === 11000) {
|
if (err && err.code === 11000) {
|
||||||
return callback(new Errors.ThirdPartyIdentityExistsError())
|
callback(new Errors.ThirdPartyIdentityExistsError())
|
||||||
|
} else if (err != null) {
|
||||||
|
callback(err)
|
||||||
|
} else if (res) {
|
||||||
|
const emailOptions = {
|
||||||
|
to: res.email,
|
||||||
|
provider: oauthProviders[providerId].name
|
||||||
}
|
}
|
||||||
if (err != null) {
|
if (settings.oauthFallback) {
|
||||||
return callback(err)
|
return callback(null, res)
|
||||||
|
} else {
|
||||||
|
EmailHandler.sendEmail(
|
||||||
|
'emailThirdPartyIdentifierLinked',
|
||||||
|
emailOptions,
|
||||||
|
error => {
|
||||||
|
if (error != null) {
|
||||||
|
logger.warn(error)
|
||||||
}
|
}
|
||||||
if (res.nModified === 1) {
|
|
||||||
return callback(null, res)
|
return callback(null, res)
|
||||||
}
|
}
|
||||||
// if already retried then throw error
|
)
|
||||||
if (retry) {
|
|
||||||
return callback(new Error('update failed'))
|
|
||||||
}
|
}
|
||||||
|
} else if (retry) {
|
||||||
|
// if already retried then throw error
|
||||||
|
callback(new Error('update failed'))
|
||||||
|
} else {
|
||||||
// attempt to clear existing entry then retry
|
// attempt to clear existing entry then retry
|
||||||
ThirdPartyIdentityManager.unlink(userId, providerId, function(err) {
|
ThirdPartyIdentityManager.unlink(userId, providerId, function(err) {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
|
@ -118,13 +144,21 @@ const ThirdPartyIdentityManager = (module.exports = {
|
||||||
externalUserId,
|
externalUserId,
|
||||||
externalData,
|
externalData,
|
||||||
callback,
|
callback,
|
||||||
true
|
retry
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
unlink(userId, providerId, callback) {
|
unlink(userId, providerId, callback) {
|
||||||
|
if (!oauthProviders[providerId]) {
|
||||||
|
return callback(new Error('Not a valid provider'))
|
||||||
|
}
|
||||||
|
const query = {
|
||||||
|
_id: userId
|
||||||
|
}
|
||||||
const update = {
|
const update = {
|
||||||
$pull: {
|
$pull: {
|
||||||
thirdPartyIdentifiers: {
|
thirdPartyIdentifiers: {
|
||||||
|
@ -132,6 +166,37 @@ const ThirdPartyIdentityManager = (module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UserUpdater.updateUser(userId, update, callback)
|
// projection includes thirdPartyIdentifiers for tests
|
||||||
|
User.findOneAndUpdate(
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
{ projection: { email: 1, thirdPartyIdentifiers: 1 }, 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
|
||||||
|
}
|
||||||
|
if (settings.oauthFallback) {
|
||||||
|
return callback(null, res)
|
||||||
|
} else {
|
||||||
|
EmailHandler.sendEmail(
|
||||||
|
'emailThirdPartyIdentifierUnlinked',
|
||||||
|
emailOptions,
|
||||||
|
error => {
|
||||||
|
if (error != null) {
|
||||||
|
logger.warn(error)
|
||||||
|
}
|
||||||
|
return callback(null, res)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -144,3 +144,15 @@ module.exports =
|
||||||
authWithV1: true
|
authWithV1: true
|
||||||
url: '/docs'
|
url: '/docs'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oauthProviders:
|
||||||
|
'provider': {
|
||||||
|
name: 'provider'
|
||||||
|
},
|
||||||
|
'collabratec': {
|
||||||
|
name: 'collabratec'
|
||||||
|
}
|
||||||
|
'google': {
|
||||||
|
name: 'google'
|
||||||
|
},
|
||||||
|
|
||||||
|
|
|
@ -93,13 +93,13 @@ describe('ThirdPartyIdentityManager', function() {
|
||||||
describe('link', function() {
|
describe('link', function() {
|
||||||
describe('when provider not already linked', () =>
|
describe('when provider not already linked', () =>
|
||||||
it('should link provider to user', function(done) {
|
it('should link provider to user', function(done) {
|
||||||
return ThirdPartyIdentityManager.link(
|
ThirdPartyIdentityManager.link(
|
||||||
this.user.id,
|
this.user.id,
|
||||||
this.provider,
|
this.provider,
|
||||||
this.externalUserId,
|
this.externalUserId,
|
||||||
this.externalData,
|
this.externalData,
|
||||||
function(err, res) {
|
function(err, res) {
|
||||||
expect(res.nModified).to.equal(1)
|
expect(res.thirdPartyIdentifiers.length).to.equal(1)
|
||||||
return done()
|
return done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -107,7 +107,7 @@ describe('ThirdPartyIdentityManager', function() {
|
||||||
|
|
||||||
describe('when provider is already linked', function() {
|
describe('when provider is already linked', function() {
|
||||||
beforeEach(function(done) {
|
beforeEach(function(done) {
|
||||||
return ThirdPartyIdentityManager.link(
|
ThirdPartyIdentityManager.link(
|
||||||
this.user.id,
|
this.user.id,
|
||||||
this.provider,
|
this.provider,
|
||||||
this.externalUserId,
|
this.externalUserId,
|
||||||
|
@ -117,29 +117,27 @@ describe('ThirdPartyIdentityManager', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should link provider to user', function(done) {
|
it('should link provider to user', function(done) {
|
||||||
return ThirdPartyIdentityManager.link(
|
ThirdPartyIdentityManager.link(
|
||||||
this.user.id,
|
this.user.id,
|
||||||
this.provider,
|
this.provider,
|
||||||
this.externalUserId,
|
this.externalUserId,
|
||||||
this.externalData,
|
this.externalData,
|
||||||
function(err, res) {
|
function(err, res) {
|
||||||
expect(res.nModified).to.equal(1)
|
expect(res).to.exist
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not create duplicate thirdPartyIdentifiers', function(done) {
|
it('should not create duplicate thirdPartyIdentifiers', function(done) {
|
||||||
return ThirdPartyIdentityManager.link(
|
ThirdPartyIdentityManager.link(
|
||||||
this.user.id,
|
this.user.id,
|
||||||
this.provider,
|
this.provider,
|
||||||
this.externalUserId,
|
this.externalUserId,
|
||||||
this.externalData,
|
this.externalData,
|
||||||
(err, res) => {
|
function(err, user) {
|
||||||
return this.user.get(function(err, user) {
|
|
||||||
expect(user.thirdPartyIdentifiers.length).to.equal(1)
|
expect(user.thirdPartyIdentifiers.length).to.equal(1)
|
||||||
return done()
|
return done()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -151,13 +149,9 @@ describe('ThirdPartyIdentityManager', function() {
|
||||||
this.provider,
|
this.provider,
|
||||||
this.externalUserId,
|
this.externalUserId,
|
||||||
this.externalData,
|
this.externalData,
|
||||||
(err, res) => {
|
(err, user) => {
|
||||||
return this.user.get((err, user) => {
|
expect(user.thirdPartyIdentifiers.length).to.equal(1)
|
||||||
expect(user.thirdPartyIdentifiers[0].externalData).to.deep.equal(
|
|
||||||
this.externalData
|
|
||||||
)
|
|
||||||
return done()
|
return done()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -193,7 +187,7 @@ describe('ThirdPartyIdentityManager', function() {
|
||||||
this.provider,
|
this.provider,
|
||||||
function(err, res) {
|
function(err, res) {
|
||||||
expect(err).to.be.null
|
expect(err).to.be.null
|
||||||
expect(res.nModified).to.equal(0)
|
expect(res.thirdPartyIdentifiers.length).to.equal(0)
|
||||||
return done()
|
return done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -214,11 +208,9 @@ describe('ThirdPartyIdentityManager', function() {
|
||||||
return ThirdPartyIdentityManager.unlink(
|
return ThirdPartyIdentityManager.unlink(
|
||||||
this.user.id,
|
this.user.id,
|
||||||
this.provider,
|
this.provider,
|
||||||
(err, res) => {
|
(err, user) => {
|
||||||
return this.user.get(function(err, user) {
|
|
||||||
expect(user.thirdPartyIdentifiers.length).to.equal(0)
|
expect(user.thirdPartyIdentifiers.length).to.equal(0)
|
||||||
return done()
|
return done()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue