diff --git a/services/web/app/src/Features/User/SAMLIdentityManager.js b/services/web/app/src/Features/User/SAMLIdentityManager.js index fed1b1bd1d..c4896dd5e5 100644 --- a/services/web/app/src/Features/User/SAMLIdentityManager.js +++ b/services/web/app/src/Features/User/SAMLIdentityManager.js @@ -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( diff --git a/services/web/app/src/Features/User/UserUpdater.js b/services/web/app/src/Features/User/UserUpdater.js index 383572585c..1d6cf99797 100644 --- a/services/web/app/src/Features/User/UserUpdater.js +++ b/services/web/app/src/Features/User/UserUpdater.js @@ -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) { diff --git a/services/web/test/unit/src/User/SAMLIdentityManagerTests.js b/services/web/test/unit/src/User/SAMLIdentityManagerTests.js index 0eeb1f0892..ce8005e3bc 100644 --- a/services/web/test/unit/src/User/SAMLIdentityManagerTests.js +++ b/services/web/test/unit/src/User/SAMLIdentityManagerTests.js @@ -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' diff --git a/services/web/test/unit/src/User/UserUpdaterTests.js b/services/web/test/unit/src/User/UserUpdaterTests.js index 4b71f8d816..5ddf2d6e7a 100644 --- a/services/web/test/unit/src/User/UserUpdaterTests.js +++ b/services/web/test/unit/src/User/UserUpdaterTests.js @@ -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 } + ) + }) + }) }) })