Merge pull request #15539 from overleaf/ab-schedule-sso-reminder

[web] Schedule Group SSO account linking reminder after joining the group

GitOrigin-RevId: 5586787fbd268446e441762fd7b4846821f849f6
This commit is contained in:
Alexandre Bourdin 2023-11-06 15:26:22 +01:00 committed by Copybot
parent c3afce73c1
commit 2783e89bc3
6 changed files with 122 additions and 34 deletions

View file

@ -475,7 +475,7 @@ templates.inviteNewUserToJoinManagedUsers = ctaTemplate({
},
})
templates.managedUsersEnabledSSO = ctaTemplate({
templates.groupSSOLinkingInvite = ctaTemplate({
subject(opts) {
const subjectPrefix = opts.reminder ? 'Reminder: ' : 'Action required: '
return `${subjectPrefix}Authenticate your Overleaf account`
@ -516,7 +516,7 @@ templates.managedUsersEnabledSSO = ctaTemplate({
},
})
templates.managedUsersDisabledSSO = ctaTemplate({
templates.groupSSODisabled = ctaTemplate({
subject(opts) {
return `Action required: Set your Overleaf password`
},

View file

@ -5,6 +5,7 @@ const settings = require('@overleaf/settings')
const { ObjectId } = require('mongodb')
const { Subscription } = require('../../models/Subscription')
const { SSOConfig } = require('../../models/SSOConfig')
const UserGetter = require('../User/UserGetter')
const SubscriptionLocator = require('./SubscriptionLocator')
@ -21,6 +22,7 @@ const {
callbackifyMultiResult,
} = require('@overleaf/promise-utils')
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
const Modules = require('../../infrastructure/Modules')
async function getInvite(token) {
const subscription = await Subscription.findOne({
@ -77,6 +79,16 @@ async function acceptInvite(token, userId) {
subscription
)
}
if (subscription.ssoConfig) {
const ssoConfig = await SSOConfig.findById(subscription.ssoConfig)
if (ssoConfig?.enabled) {
await Modules.promises.hooks.fire(
'scheduleGroupSSOReminder',
userId,
subscription._id
)
}
}
await _removeInviteFromTeam(subscription.id, invite.email)

View file

@ -11,6 +11,7 @@ const {
const EmailHandler = require('../Features/Email/EmailHandler')
const logger = require('@overleaf/logger')
const OError = require('@overleaf/o-error')
const Modules = require('./Modules')
function start() {
if (!Features.hasFeature('saas')) {
@ -79,6 +80,26 @@ function start() {
}
})
registerCleanup(confirmInstitutionDomainQueue)
const groupSSOReminderQueue = Queues.getQueue('group-sso-reminder')
groupSSOReminderQueue.process(async job => {
const { userId, subscriptionId } = job.data
try {
await Modules.promises.hooks.fire(
'sendGroupSSOReminder',
userId,
subscriptionId
)
} catch (e) {
const error = OError.tag(
e,
'failed to send scheduled Group SSO account linking reminder'
)
logger.warn(error)
throw error
}
})
registerCleanup(groupSSOReminderQueue)
}
function registerCleanup(queue) {

View file

@ -37,6 +37,10 @@ const QUEUES_JOB_OPTIONS = {
removeOnFail: MAX_FAILED_JOBS_RETAINED,
attempts: 3,
},
'group-sso-reminder': {
removeOnFail: MAX_FAILED_JOBS_RETAINED,
attempts: 3,
},
}
const QUEUE_OPTIONS = {

View file

@ -51,11 +51,11 @@ export default function DropdownButton({
isLoading: isResendingGroupInvite,
} = useAsync<resendInviteResponse>()
const userNotManaged =
!user.isEntityAdmin && !user.invite && !user.enrollment?.managedBy
const userPending = user.invite
const userNotManaged =
!user.isEntityAdmin && !userPending && !user.enrollment?.managedBy
const handleResendManagedUserInvite = useCallback(
async user => {
try {
@ -219,7 +219,7 @@ export default function DropdownButton({
) : null}
</MenuItemButton>
) : null}
{ssoEnabledButNotAccepted && (
{!userPending && ssoEnabledButNotAccepted && (
<MenuItemButton
onClick={onResendSSOLinkInviteClick}
data-testid="resend-sso-link-invite-action"

View file

@ -66,6 +66,10 @@ describe('TeamInvitesHandler', function () {
updateOne: sinon.stub().resolves(),
}
this.SSOConfig = {
findById: sinon.stub().resolves(),
}
this.EmailHandler = {
promises: {
sendEmail: sinon.stub().resolves(null),
@ -118,6 +122,7 @@ describe('TeamInvitesHandler', function () {
'@overleaf/settings': { siteUrl: 'http://example.com' },
'../../models/TeamInvite': { TeamInvite: (this.TeamInvite = {}) },
'../../models/Subscription': { Subscription: this.Subscription },
'../../models/SSOConfig': { SSOConfig: this.SSOConfig },
'../User/UserGetter': this.UserGetter,
'./SubscriptionLocator': this.SubscriptionLocator,
'./SubscriptionUpdater': this.SubscriptionUpdater,
@ -125,6 +130,9 @@ describe('TeamInvitesHandler', function () {
'../Email/EmailHandler': this.EmailHandler,
'./ManagedUsersHandler': this.ManagedUsersHandler,
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
'../../infrastructure/Modules': (this.Modules = {
promises: { hooks: { fire: sinon.stub().resolves() } },
}),
},
})
})
@ -334,6 +342,7 @@ describe('TeamInvitesHandler', function () {
})
})
describe('with standard group', function () {
it('adds the user to the team', function (done) {
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
this.SubscriptionUpdater.promises.addUserToGroup
@ -369,6 +378,48 @@ describe('TeamInvitesHandler', function () {
done()
})
})
it('should not schedule an SSO invite reminder', function (done) {
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
done()
})
})
})
describe('with managed group', function () {
it('should enroll the group member', function (done) {
this.subscription.managedUsersEnabled = true
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.calledWith(
this.ManagedUsersHandler.promises.enrollInSubscription,
this.user.id,
this.subscription
)
done()
})
})
})
describe('with group SSO enabled', function () {
it('should schedule an SSO invite reminder', function (done) {
this.subscription.ssoConfig = 'ssoconfig1'
this.SSOConfig.findById
.withArgs('ssoconfig1')
.resolves({ enabled: true })
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'scheduleGroupSSOReminder',
this.user.id,
this.subscription._id
)
done()
})
})
})
})
describe('revokeInvite', function () {