mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 23:55:33 -05:00
Merge pull request #8289 from overleaf/ta-redundant-subscription-email
Create Redundant Subscription Notification on Email Confirmation GitOrigin-RevId: 77baab93ebaae85d09681051641e663bb680c17e
This commit is contained in:
parent
d94478b4a8
commit
e6c7025813
4 changed files with 110 additions and 87 deletions
|
@ -262,19 +262,10 @@ async function linkAccounts(userId, samlData, auditLog) {
|
|||
await _removeIdentifier(userId, providerId)
|
||||
throw error
|
||||
}
|
||||
await UserUpdater.promises.confirmEmail(userId, institutionEmail) // will set confirmedAt if not set, and will always update reconfirmedAt
|
||||
await UserUpdater.promises.confirmEmail(userId, institutionEmail, {
|
||||
entitlement: hasEntitlement,
|
||||
}) // will set confirmedAt if not set, and will always update reconfirmedAt
|
||||
await _sendLinkedEmail(userId, providerName, institutionEmail)
|
||||
// update v1 affiliations record
|
||||
if (hasEntitlement) {
|
||||
await InstitutionsAPI.promises.addEntitlement(userId, institutionEmail)
|
||||
try {
|
||||
await redundantSubscription(userId, providerId, providerName)
|
||||
} catch (error) {
|
||||
logger.err({ err: error }, 'error checking redundant subscription')
|
||||
}
|
||||
} else {
|
||||
await InstitutionsAPI.promises.removeEntitlement(userId, institutionEmail)
|
||||
}
|
||||
}
|
||||
|
||||
async function unlinkAccounts(
|
||||
|
|
|
@ -14,6 +14,8 @@ const NewsletterManager = require('../Newsletter/NewsletterManager')
|
|||
const RecurlyWrapper = require('../Subscription/RecurlyWrapper')
|
||||
const UserAuditLogHandler = require('./UserAuditLogHandler')
|
||||
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
||||
const _ = require('lodash')
|
||||
|
||||
async function _sendSecurityAlertPrimaryEmailChanged(userId, oldEmail, email) {
|
||||
|
@ -223,7 +225,7 @@ async function setDefaultEmailAddress(
|
|||
}
|
||||
}
|
||||
|
||||
async function confirmEmail(userId, email) {
|
||||
async function confirmEmail(userId, email, affiliationOptions) {
|
||||
// used for initial email confirmation (non-SSO and SSO)
|
||||
// also used for reconfirmation of non-SSO emails
|
||||
const confirmedAt = new Date()
|
||||
|
@ -234,9 +236,13 @@ async function confirmEmail(userId, email) {
|
|||
logger.debug({ userId, email }, 'confirming user email')
|
||||
|
||||
try {
|
||||
await InstitutionsAPI.promises.addAffiliation(userId, email, {
|
||||
confirmedAt,
|
||||
})
|
||||
affiliationOptions = affiliationOptions || {}
|
||||
affiliationOptions.confirmedAt = confirmedAt
|
||||
await InstitutionsAPI.promises.addAffiliation(
|
||||
userId,
|
||||
email,
|
||||
affiliationOptions
|
||||
)
|
||||
} catch (error) {
|
||||
throw OError.tag(error, 'problem adding affiliation while confirming email')
|
||||
}
|
||||
|
@ -268,6 +274,40 @@ async function confirmEmail(userId, email) {
|
|||
throw new Errors.NotFoundError('user id and email do no match')
|
||||
}
|
||||
await FeaturesUpdater.promises.refreshFeatures(userId, 'confirm-email')
|
||||
try {
|
||||
await maybeCreateRedundantSubscriptionNotification(userId, email)
|
||||
} catch (error) {
|
||||
logger.err(
|
||||
{ err: error },
|
||||
'error checking redundant subscription on email confirmation'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function maybeCreateRedundantSubscriptionNotification(userId, email) {
|
||||
const subscription =
|
||||
await SubscriptionLocator.promises.getUserIndividualSubscription(userId)
|
||||
if (!subscription || subscription.groupPlan) {
|
||||
return
|
||||
}
|
||||
|
||||
const affiliations = await InstitutionsAPI.promises.getUserAffiliations(
|
||||
userId
|
||||
)
|
||||
const confirmedAffiliation = affiliations.find(a => a.email === email)
|
||||
if (confirmedAffiliation.licence === 'free') {
|
||||
return
|
||||
}
|
||||
|
||||
await NotificationsBuilder.promises
|
||||
.redundantPersonalSubscription(
|
||||
{
|
||||
institutionId: confirmedAffiliation.institution.id,
|
||||
institutionName: confirmedAffiliation.institution.name,
|
||||
},
|
||||
{ _id: userId }
|
||||
)
|
||||
.create()
|
||||
}
|
||||
|
||||
async function removeEmailAddress(userId, email, skipParseEmail = false) {
|
||||
|
|
|
@ -545,77 +545,6 @@ describe('SAMLIdentityManager', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('redundantSubscription', function () {
|
||||
const userId = '1bv'
|
||||
const providerId = 123
|
||||
const providerName = 'University Name'
|
||||
|
||||
describe('with a personal subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
|
||||
{
|
||||
planCode: 'professional',
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should create redundant personal subscription notification ', async function () {
|
||||
try {
|
||||
await this.SAMLIdentityManager.redundantSubscription(
|
||||
userId,
|
||||
providerId,
|
||||
providerName
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error).to.not.exist
|
||||
}
|
||||
expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
|
||||
.to.have.been.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a group subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
|
||||
{
|
||||
planCode: 'professional',
|
||||
groupPlan: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should create redundant personal subscription notification ', async function () {
|
||||
try {
|
||||
await this.SAMLIdentityManager.redundantSubscription(
|
||||
userId,
|
||||
providerId,
|
||||
providerName
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error).to.not.exist
|
||||
}
|
||||
expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
|
||||
.to.not.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
describe('without a personal subscription', function () {
|
||||
it('should not create redundant personal subscription notification ', async function () {
|
||||
try {
|
||||
await this.SAMLIdentityManager.redundantSubscription(
|
||||
userId,
|
||||
providerId,
|
||||
providerName
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error).to.not.exist
|
||||
}
|
||||
expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
|
||||
.to.not.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('migrateIdentifier', function () {
|
||||
const userId = '5efb8b6e9b647b0027e4c0b0'
|
||||
const externalUserId = '987zyx'
|
||||
|
|
|
@ -73,6 +73,7 @@ describe('UserUpdater', function () {
|
|||
promises: {
|
||||
addAffiliation: sinon.stub().resolves(),
|
||||
removeAffiliation: sinon.stub().resolves(),
|
||||
getUserAffiliations: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.EmailHandler = {
|
||||
|
@ -94,6 +95,20 @@ describe('UserUpdater', function () {
|
|||
},
|
||||
}
|
||||
|
||||
this.SubscriptionLocator = {
|
||||
promises: {
|
||||
getUserIndividualSubscription: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.NotificationsBuilder = {
|
||||
promises: {
|
||||
redundantPersonalSubscription: sinon
|
||||
.stub()
|
||||
.returns({ create: () => {} }),
|
||||
},
|
||||
}
|
||||
|
||||
this.UserUpdater = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../Helpers/Mongo': { normalizeQuery },
|
||||
|
@ -109,6 +124,8 @@ describe('UserUpdater', function () {
|
|||
'./UserAuditLogHandler': this.UserAuditLogHandler,
|
||||
'../Analytics/AnalyticsManager': this.AnalyticsManager,
|
||||
'../../Errors/Errors': Errors,
|
||||
'../Subscription/SubscriptionLocator': this.SubscriptionLocator,
|
||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -910,5 +927,51 @@ describe('UserUpdater', function () {
|
|||
this.user._id
|
||||
)
|
||||
})
|
||||
|
||||
describe('with institution licence and subscription', function () {
|
||||
beforeEach(async function () {
|
||||
this.affiliation = {
|
||||
email: this.newEmail,
|
||||
licence: 'pro_plus',
|
||||
institution: {
|
||||
id: 123,
|
||||
name: 'Institution',
|
||||
},
|
||||
}
|
||||
this.InstitutionsAPI.promises.getUserAffiliations.resolves([
|
||||
this.affiliation,
|
||||
{ email: 'other@email.edu' },
|
||||
])
|
||||
this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
|
||||
{
|
||||
planCode: 'personal',
|
||||
groupPlan: false,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('creates redundant subscription notification', async function () {
|
||||
await this.UserUpdater.promises.confirmEmail(
|
||||
this.user._id,
|
||||
this.newEmail
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.InstitutionsAPI.promises.getUserAffiliations,
|
||||
this.user._id
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.SubscriptionLocator.promises.getUserIndividualSubscription,
|
||||
this.user._id
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.NotificationsBuilder.promises.redundantPersonalSubscription,
|
||||
{
|
||||
institutionId: 123,
|
||||
institutionName: 'Institution',
|
||||
},
|
||||
{ _id: this.user._id }
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue