diff --git a/services/web/app/src/Features/User/UserGetter.js b/services/web/app/src/Features/User/UserGetter.js index d35fb418b9..1910c9fc3c 100644 --- a/services/web/app/src/Features/User/UserGetter.js +++ b/services/web/app/src/Features/User/UserGetter.js @@ -2,6 +2,8 @@ const { callbackify } = require('util') const { db } = require('../../infrastructure/mongodb') const metrics = require('@overleaf/metrics') const logger = require('logger-sharelatex') +const moment = require('moment') +const settings = require('settings-sharelatex') const { promisifyAll } = require('../../util/promises') const { promises: InstitutionsAPIPromises @@ -11,6 +13,35 @@ const Errors = require('../Errors/Errors') const Features = require('../../infrastructure/Features') const { normalizeQuery, normalizeMultiQuery } = require('../Helpers/Mongo') +function _emailInReconfirmNotificationPeriod(emailData, institutionData) { + const globalReconfirmPeriod = settings.reconfirmNotificationDays + if (!globalReconfirmPeriod) return false + + // only show notification for institutions with reconfirmation enabled + if (!institutionData || !institutionData.maxConfirmationMonths) return false + + if (!emailData.confirmedAt) return false + + if (institutionData.ssoEnabled && !emailData.samlProviderId) { + // For SSO, only show notification for linked email + return false + } + + // reconfirmedAt will not always be set, use confirmedAt as fallback + const lastConfirmed = emailData.reconfirmedAt || emailData.confirmedAt + + const lastDayToReconfirm = moment(lastConfirmed).add( + institutionData.maxConfirmationMonths, + 'months' + ) + const notificationStarts = moment(lastDayToReconfirm).subtract( + globalReconfirmPeriod, + 'days' + ) + + return moment().isAfter(notificationStarts) +} + async function getUserFullEmails(userId) { const user = await UserGetter.promises.getUser(userId, { email: 1, @@ -155,8 +186,8 @@ var decorateFullEmails = ( emailsData, affiliationsData, samlIdentifiers -) => - emailsData.map(function(emailData) { +) => { + emailsData.forEach(function(emailData) { emailData.default = emailData.email === defaultEmail const affiliation = affiliationsData.find( @@ -164,31 +195,33 @@ var decorateFullEmails = ( ) if (affiliation) { const { institution, inferred, role, department, licence } = affiliation + const inReconfirmNotificationPeriod = _emailInReconfirmNotificationPeriod( + emailData, + institution + ) emailData.affiliation = { institution, inferred, + inReconfirmNotificationPeriod, role, department, licence } - } else { - emailsData.affiliation = null } if (emailData.samlProviderId) { emailData.samlIdentifier = samlIdentifiers.find( samlIdentifier => samlIdentifier.providerId === emailData.samlProviderId ) - } else { - emailsData.samlIdentifier = null } emailData.emailHasInstitutionLicence = InstitutionsHelper.emailHasLicence( emailData ) - - return emailData }) + + return emailsData +} ;[ 'getUser', 'getUserEmail', diff --git a/services/web/test/acceptance/config/settings.test.coffee b/services/web/test/acceptance/config/settings.test.coffee index 76fc57912a..165aad3b41 100644 --- a/services/web/test/acceptance/config/settings.test.coffee +++ b/services/web/test/acceptance/config/settings.test.coffee @@ -186,3 +186,5 @@ module.exports = # setting to true since many features are enabled/disabled after availability of this # property (check Features.js) overleaf: true + + reconfirmNotificationDays: 14 diff --git a/services/web/test/acceptance/src/SubscriptionDashboardTests.js b/services/web/test/acceptance/src/SubscriptionDashboardTests.js index 8c0185d807..cc8795b906 100644 --- a/services/web/test/acceptance/src/SubscriptionDashboardTests.js +++ b/services/web/test/acceptance/src/SubscriptionDashboardTests.js @@ -516,6 +516,7 @@ describe('Subscriptions', function() { department: 'Math', role: 'Prof', inferred: false, + inReconfirmNotificationPeriod: false, institution: { name: 'Stanford', confirmed: true diff --git a/services/web/test/acceptance/src/UserEmailsTests.js b/services/web/test/acceptance/src/UserEmailsTests.js index 52f1a6b656..6b203e21bd 100644 --- a/services/web/test/acceptance/src/UserEmailsTests.js +++ b/services/web/test/acceptance/src/UserEmailsTests.js @@ -1,39 +1,14 @@ const { expect } = require('chai') const async = require('async') +const moment = require('moment') +const Features = require('../../../app/src/infrastructure/Features') const User = require('./helpers/User') const UserHelper = require('./helpers/UserHelper') +const UserUpdater = require('../../../app/src/Features/User/UserUpdater') const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb') const MockV1Api = require('./helpers/MockV1Api') const expectErrorResponse = require('./helpers/expectErrorResponse') -async function confirmEmail(userHelper, email) { - let response - // UserHelper.createUser does not create a confirmation token - response = await userHelper.request.post({ - form: { - email - }, - simple: false, - uri: '/user/emails/resend_confirmation' - }) - expect(response.statusCode).to.equal(200) - const tokenData = await db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': userHelper.user._id.toString(), - usedAt: { $exists: false } - }) - .next() - response = await userHelper.request.post({ - form: { - token: tokenData.token - }, - simple: false, - uri: '/user/emails/confirm' - }) - expect(response.statusCode).to.equal(200) -} - describe('UserEmails', function() { beforeEach(function(done) { this.timeout(20000) @@ -283,14 +258,14 @@ describe('UserEmails', function() { password: userHelper.getDefaultPassword() }) // original confirmation - await confirmEmail(userHelper, email) + await userHelper.confirmEmail(userHelper.user._id, email) const user = (await UserHelper.getUser({ email })).user confirmedAtDate = user.emails[0].confirmedAt expect(user.emails[0].confirmedAt).to.exist expect(user.emails[0].reconfirmedAt).to.exist }) it('should set reconfirmedAt and not reset confirmedAt', async function() { - await confirmEmail(userHelper, email) + await userHelper.confirmEmail(userHelper.user._id, email) const user = (await UserHelper.getUser({ email })).user expect(user.emails[0].confirmedAt).to.exist expect(user.emails[0].reconfirmedAt).to.exist @@ -1019,4 +994,159 @@ describe('UserEmails', function() { expect(user.auditLog[0].ip).to.equal(this.user.request.ip) }) }) + + describe('notification period', function() { + let defaultEmail, userHelper, email1, email2, email3 + const maxConfirmationMonths = 12 + + beforeEach(async function() { + if (!Features.hasFeature('affiliations')) { + this.skip() + } + userHelper = new UserHelper() + defaultEmail = userHelper.getDefaultEmail() + userHelper = await UserHelper.createUser({ email: defaultEmail }) + userHelper = await UserHelper.loginUser({ + email: defaultEmail, + password: userHelper.getDefaultPassword() + }) + const institutionId = MockV1Api.createInstitution({ + ssoEnabled: false, + maxConfirmationMonths + }) + const domain = 'example-affiliation.com' + MockV1Api.addInstitutionDomain(institutionId, domain) + + email1 = `leonard@${domain}` + email2 = `mccoy@${domain}` + email3 = `bones@${domain}` + }) + + describe('non SSO affiliations', function() { + beforeEach(async function() { + // create a user with 3 affiliations at the institution. + // all are within in the notification period + const backdatedDays = maxConfirmationMonths * 2 * 30 + const userId = userHelper.user._id + await userHelper.addEmailAndConfirm(userId, email1) + await userHelper.addEmailAndConfirm(userId, email2) + await userHelper.addEmailAndConfirm(userId, email3) + await userHelper.backdateConfirmation(userId, email1, backdatedDays) + await userHelper.backdateConfirmation(userId, email2, backdatedDays) + await userHelper.backdateConfirmation(userId, email3, backdatedDays) + }) + + it('should flag inReconfirmNotificationPeriod for all affiliations in period', async function() { + const response = await userHelper.request.get('/user/emails') + expect(response.statusCode).to.equal(200) + const fullEmails = JSON.parse(response.body) + expect(fullEmails.length).to.equal(4) + expect(fullEmails[0].affiliation).to.not.exist + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[3].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + }) + + describe('should flag emails before their confirmation expires, but within the notification period', function() { + beforeEach(async function() { + const dateInPeriodButNotExpired = moment() + .subtract(maxConfirmationMonths, 'months') + .add(14, 'days') + .toDate() + const backdatedDays = moment().diff(dateInPeriodButNotExpired, 'days') + await userHelper.backdateConfirmation( + userHelper.user._id, + email1, + backdatedDays + ) + await userHelper.backdateConfirmation( + userHelper.user._id, + email2, + backdatedDays + ) + await userHelper.backdateConfirmation( + userHelper.user._id, + email3, + backdatedDays + ) + }) + + it('should flag the emails', async function() { + const response = await userHelper.request.get('/user/emails') + expect(response.statusCode).to.equal(200) + const fullEmails = JSON.parse(response.body) + expect(fullEmails.length).to.equal(4) + expect(fullEmails[0].affiliation).to.not.exist + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[3].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + // ensure dates are not past reconfirmation period + function _getLastDayToReconfirm(date) { + return moment(date).add(maxConfirmationMonths, 'months') + } + + expect( + moment(fullEmails[1].reconfirmedAt).isAfter( + _getLastDayToReconfirm(fullEmails[1].reconfirmedAt) + ) + ).to.equal(false) + + expect( + moment(fullEmails[2].reconfirmedAt).isAfter( + _getLastDayToReconfirm(fullEmails[2].reconfirmedAt) + ) + ).to.equal(false) + expect( + moment(fullEmails[3].reconfirmedAt).isAfter( + _getLastDayToReconfirm(fullEmails[3].reconfirmedAt) + ) + ).to.equal(false) + }) + }) + + describe('missing reconfirmedAt', function() { + beforeEach(async function() { + const userId = userHelper.user._id + const query = { + _id: userId, + 'emails.email': email2 + } + const update = { + $unset: { 'emails.$.reconfirmedAt': true } + } + await UserUpdater.promises.updateUser(query, update) + }) + + it('should fallback to confirmedAt for date check', async function() { + const response = await userHelper.request.get('/user/emails') + expect(response.statusCode).to.equal(200) + const fullEmails = JSON.parse(response.body) + expect(fullEmails.length).to.equal(4) + expect(fullEmails[0].affiliation).to.not.exist + expect(fullEmails[2].reconfirmedAt).to.not.exist + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[3].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + }) + }) + }) + }) }) diff --git a/services/web/test/acceptance/src/helpers/UserHelper.js b/services/web/test/acceptance/src/helpers/UserHelper.js index 86d8606dd4..d48aed649d 100644 --- a/services/web/test/acceptance/src/helpers/UserHelper.js +++ b/services/web/test/acceptance/src/helpers/UserHelper.js @@ -4,7 +4,9 @@ const Settings = require('settings-sharelatex') const UserCreator = require('../../../../app/src/Features/User/UserCreator') const UserGetter = require('../../../../app/src/Features/User/UserGetter') const UserUpdater = require('../../../../app/src/Features/User/UserUpdater') +const moment = require('moment') const request = require('request-promise-native') +const { db } = require('../../../../app/src/infrastructure/mongodb') const { ObjectId } = require('mongodb') let globalUserNum = 1 @@ -289,6 +291,76 @@ class UserHelper { return userHelper } + + async addEmailAndConfirm(userId, email) { + let response = await this.request.post({ + form: { + email + }, + simple: false, + uri: '/user/emails' + }) + expect(response.statusCode).to.equal(204) + const token = ( + await db.tokens.findOne({ + 'data.user_id': userId.toString(), + 'data.email': email + }) + ).token + response = await this.request.post({ + form: { + token + }, + simple: false, + uri: '/user/emails/confirm' + }) + expect(response.statusCode).to.equal(200) + } + + async backdateConfirmation(userId, email, days) { + const confirmedDate = moment() + .subtract(days, 'days') + .toDate() + const query = { + _id: userId, + 'emails.email': email + } + const update = { + $set: { + 'emails.$.confirmedAt': confirmedDate, + 'emails.$.reconfirmedAt': confirmedDate + } + } + await UserUpdater.promises.updateUser(query, update) + } + + async confirmEmail(userId, email) { + let response + // UserHelper.createUser does not create a confirmation token + response = await this.request.post({ + form: { + email + }, + simple: false, + uri: '/user/emails/resend_confirmation' + }) + expect(response.statusCode).to.equal(200) + const tokenData = await db.tokens + .find({ + use: 'email_confirmation', + 'data.user_id': userId.toString(), + usedAt: { $exists: false } + }) + .next() + response = await this.request.post({ + form: { + token: tokenData.token + }, + simple: false, + uri: '/user/emails/confirm' + }) + expect(response.statusCode).to.equal(200) + } } module.exports = UserHelper diff --git a/services/web/test/unit/src/User/UserGetterTests.js b/services/web/test/unit/src/User/UserGetterTests.js index 8373b8dc67..888bab7448 100644 --- a/services/web/test/unit/src/User/UserGetterTests.js +++ b/services/web/test/unit/src/User/UserGetterTests.js @@ -2,6 +2,7 @@ const { ObjectId } = require('mongodb') const should = require('chai').should() const SandboxedModule = require('sandboxed-module') const assert = require('assert') +const moment = require('moment') const path = require('path') const sinon = require('sinon') const modulePath = path.join( @@ -41,7 +42,6 @@ describe('UserGetter', function() { }, ObjectId } - const settings = { apis: { v1: { url: 'v1.url', user: '', pass: '' } } } this.getUserAffiliations = sinon.stub().resolves([]) this.UserGetter = SandboxedModule.require(modulePath, { @@ -57,7 +57,9 @@ describe('UserGetter', function() { '@overleaf/metrics': { timeAsyncMethod: sinon.stub() }, - 'settings-sharelatex': settings, + 'settings-sharelatex': (this.settings = { + reconfirmNotificationDays: 14 + }), '../Institutions/InstitutionsAPI': { promises: { getUserAffiliations: this.getUserAffiliations @@ -192,7 +194,8 @@ describe('UserGetter', function() { inferred: affiliationsData[0].inferred, department: affiliationsData[0].department, role: affiliationsData[0].role, - licence: affiliationsData[0].licence + licence: affiliationsData[0].licence, + inReconfirmNotificationPeriod: false } }, { @@ -262,6 +265,528 @@ describe('UserGetter', function() { } ) }) + + describe('affiliation reconfirmation', function() { + const institutionNonSSO = { + id: 1, + name: 'University Name', + commonsAccount: true, + isUniversity: true, + confirmed: true, + ssoBeta: false, + ssoEnabled: false, + maxConfirmationMonths: 12 + } + const institutionSSO = { + id: 2, + name: 'SSO University Name', + isUniversity: true, + confirmed: true, + ssoBeta: false, + ssoEnabled: true, + maxConfirmationMonths: 12 + } + describe('non-SSO institutions', function() { + const email1 = 'leonard@example-affiliation.com' + const email2 = 'mccoy@example-affiliation.com' + const affiliationsData = [ + { + email: email1, + role: 'Prof', + department: 'Medicine', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + }, + { + email: email2, + role: 'Prof', + department: 'Medicine', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + } + ] + it('should flag inReconfirmNotificationPeriod for all affiliations in period', function(done) { + const user = { + _id: '12390i', + email: email1, + emails: [ + { + email: email1, + reversedHostname: 'moc.noitailiffa-elpmaxe', + confirmedAt: moment() + .subtract( + institutionNonSSO.maxConfirmationMonths + 2, + 'months' + ) + .toDate(), + default: true + }, + { + email: email2, + reversedHostname: 'moc.noitailiffa-elpmaxe', + confirmedAt: moment() + .subtract( + institutionNonSSO.maxConfirmationMonths + 1, + 'months' + ) + .toDate() + } + ] + } + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + done() + } + ) + }) + it('should not flag affiliations outside of notification period', function(done) { + const aboutToBeWithinPeriod = moment() + .subtract(institutionNonSSO.maxConfirmationMonths, 'months') + .add(15, 'days') + .toDate() // expires in 15 days + const user = { + _id: '12390i', + email: email1, + emails: [ + { + email: email1, + reversedHostname: 'moc.noitailiffa-elpmaxe', + confirmedAt: new Date(), + default: true + }, + { + email: email2, + reversedHostname: 'moc.noitailiffa-elpmaxe', + confirmedAt: aboutToBeWithinPeriod + } + ] + } + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.equal(false) + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(false) + done() + } + ) + }) + }) + + describe('SSO institutions', function() { + it('should flag only linked email, if in notification period', function(done) { + const email1 = 'email1@sso.bar' + const email2 = 'email2@sso.bar' + const email3 = 'email3@sso.bar' + + const affiliationsData = [ + { + email: email1, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionSSO + }, + { + email: email2, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionSSO + }, + { + email: email3, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionSSO + } + ] + + const user = { + _id: '12390i', + email: email1, + emails: [ + { + email: email1, + reversedHostname: 'rab.oss', + confirmedAt: new Date('2019-09-24'), + reconfirmedAt: new Date('2019-09-24'), + default: true + }, + { + email: email2, + reversedHostname: 'rab.oss', + confirmedAt: new Date('2019-09-24'), + reconfirmedAt: new Date('2019-09-24'), + samlProviderId: institutionSSO.id + }, + { + email: email3, + reversedHostname: 'rab.oss', + confirmedAt: new Date('2019-09-24'), + reconfirmedAt: new Date('2019-09-24') + } + ], + samlIdentifiers: [ + { + providerId: institutionSSO.id, + externalUserId: 'abc123' + } + ] + } + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.equal(false) + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(false) + done() + } + ) + }) + }) + + describe('multiple institution affiliations', function() { + it('should flag each institution', function(done) { + const email1 = 'email1@sso.bar' + const email2 = 'email2@sso.bar' + const email3 = 'email3@foo.bar' + const email4 = 'email4@foo.bar' + + const affiliationsData = [ + { + email: email1, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionSSO + }, + { + email: email2, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionSSO + }, + { + email: email3, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + }, + { + email: email4, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + } + ] + const user = { + _id: '12390i', + email: email1, + emails: [ + { + email: email1, + reversedHostname: 'rab.oss', + confirmedAt: '2019-09-24T20:25:08.503Z', + default: true + }, + { + email: email2, + reversedHostname: 'rab.oss', + confirmedAt: new Date('2019-09-24T20:25:08.503Z'), + samlProviderId: institutionSSO.id + }, + { + email: email3, + reversedHostname: 'rab.oof', + confirmedAt: new Date('2019-10-24T20:25:08.503Z') + }, + { + email: email4, + reversedHostname: 'rab.oof', + confirmedAt: new Date('2019-09-24T20:25:08.503Z') + } + ], + samlIdentifiers: [ + { + providerId: institutionSSO.id, + externalUserId: 'abc123' + } + ] + } + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.to.equal(false) + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[3].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + done() + } + ) + }) + }) + + describe('reconfirmedAt', function() { + it('only use confirmedAt when no reconfirmedAt', function(done) { + const email1 = 'email1@foo.bar' + const email2 = 'email2@foo.bar' + const email3 = 'email3@foo.bar' + + const affiliationsData = [ + { + email: email1, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + }, + { + email: email2, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + }, + { + email: email3, + role: 'Prof', + department: 'Maths', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + } + ] + const user = { + _id: '12390i', + email: email1, + emails: [ + { + email: email1, + reversedHostname: 'rab.oof', + confirmedAt: moment().subtract( + institutionSSO.maxConfirmationMonths * 2, + 'months' + ), + reconfirmedAt: moment().subtract( + institutionSSO.maxConfirmationMonths * 3, + 'months' + ), + default: true + }, + { + email: email2, + reversedHostname: 'rab.oof', + confirmedAt: moment().subtract( + institutionSSO.maxConfirmationMonths * 3, + 'months' + ), + reconfirmedAt: moment().subtract( + institutionSSO.maxConfirmationMonths * 2, + 'months' + ) + }, + { + email: email3, + reversedHostname: 'rab.oof', + confirmedAt: moment().subtract( + institutionSSO.maxConfirmationMonths * 4, + 'months' + ), + reconfirmedAt: moment().subtract( + institutionSSO.maxConfirmationMonths * 4, + 'months' + ) + } + ] + } + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + done() + } + ) + }) + }) + + describe('before reconfirmation period expires and within reconfirmation notification period', function() { + const email = 'leonard@example-affiliation.com' + it('should flag the email', function(done) { + const confirmedAt = moment() + .subtract(institutionNonSSO.maxConfirmationMonths, 'months') + .subtract(14, 'days') + .toDate() // expires in 14 days + const affiliationsData = [ + { + email, + role: 'Prof', + department: 'Medicine', + inferred: false, + licence: 'pro_plus', + institution: institutionNonSSO + } + ] + const user = { + _id: '12390i', + email, + emails: [ + { + email, + confirmedAt, + default: true + } + ] + } + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.equal(true) + done() + } + ) + }) + }) + + describe('when no Settings.reconfirmNotificationDays', function() { + it('should always return inReconfirmNotificationPeriod:false', function(done) { + const email1 = 'email1@sso.bar' + const email2 = 'email2@foo.bar' + const email3 = 'email3@foo.bar' + const confirmedAtAboutToExpire = moment() + .subtract(institutionNonSSO.maxConfirmationMonths, 'months') + .subtract(14, 'days') + .toDate() // expires in 14 days + + const affiliationsData = [ + { + email: email1, + institution: institutionSSO + }, + { + email: email2, + institution: institutionNonSSO + }, + { + email: email3, + institution: institutionNonSSO + } + ] + const user = { + _id: '12390i', + email: email1, + emails: [ + { + email: email1, + confirmedAt: confirmedAtAboutToExpire, + default: true, + samlProviderId: institutionSSO.id + }, + { + email: email2, + confirmedAt: new Date('2019-09-24T20:25:08.503Z') + }, + { + email: email3, + confirmedAt: new Date('2019-10-24T20:25:08.503Z') + } + ], + samlIdentifiers: [ + { + providerId: institutionSSO.id, + externalUserId: 'abc123' + } + ] + } + this.settings.reconfirmNotificationDays = undefined + this.getUserAffiliations.resolves(affiliationsData) + this.UserGetter.promises.getUser = sinon.stub().resolves(user) + this.UserGetter.getUserFullEmails( + this.fakeUser._id, + (error, fullEmails) => { + expect(error).to.not.exist + expect( + fullEmails[0].affiliation.inReconfirmNotificationPeriod + ).to.to.equal(false) + expect( + fullEmails[1].affiliation.inReconfirmNotificationPeriod + ).to.equal(false) + expect( + fullEmails[2].affiliation.inReconfirmNotificationPeriod + ).to.equal(false) + done() + } + ) + }) + }) + }) }) describe('getUserbyMainEmail', function() {