mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
c3afce73c1
commit
2783e89bc3
6 changed files with 122 additions and 34 deletions
|
@ -475,7 +475,7 @@ templates.inviteNewUserToJoinManagedUsers = ctaTemplate({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
templates.managedUsersEnabledSSO = ctaTemplate({
|
templates.groupSSOLinkingInvite = ctaTemplate({
|
||||||
subject(opts) {
|
subject(opts) {
|
||||||
const subjectPrefix = opts.reminder ? 'Reminder: ' : 'Action required: '
|
const subjectPrefix = opts.reminder ? 'Reminder: ' : 'Action required: '
|
||||||
return `${subjectPrefix}Authenticate your Overleaf account`
|
return `${subjectPrefix}Authenticate your Overleaf account`
|
||||||
|
@ -516,7 +516,7 @@ templates.managedUsersEnabledSSO = ctaTemplate({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
templates.managedUsersDisabledSSO = ctaTemplate({
|
templates.groupSSODisabled = ctaTemplate({
|
||||||
subject(opts) {
|
subject(opts) {
|
||||||
return `Action required: Set your Overleaf password`
|
return `Action required: Set your Overleaf password`
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@ const settings = require('@overleaf/settings')
|
||||||
const { ObjectId } = require('mongodb')
|
const { ObjectId } = require('mongodb')
|
||||||
|
|
||||||
const { Subscription } = require('../../models/Subscription')
|
const { Subscription } = require('../../models/Subscription')
|
||||||
|
const { SSOConfig } = require('../../models/SSOConfig')
|
||||||
|
|
||||||
const UserGetter = require('../User/UserGetter')
|
const UserGetter = require('../User/UserGetter')
|
||||||
const SubscriptionLocator = require('./SubscriptionLocator')
|
const SubscriptionLocator = require('./SubscriptionLocator')
|
||||||
|
@ -21,6 +22,7 @@ const {
|
||||||
callbackifyMultiResult,
|
callbackifyMultiResult,
|
||||||
} = require('@overleaf/promise-utils')
|
} = require('@overleaf/promise-utils')
|
||||||
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
||||||
|
const Modules = require('../../infrastructure/Modules')
|
||||||
|
|
||||||
async function getInvite(token) {
|
async function getInvite(token) {
|
||||||
const subscription = await Subscription.findOne({
|
const subscription = await Subscription.findOne({
|
||||||
|
@ -77,6 +79,16 @@ async function acceptInvite(token, userId) {
|
||||||
subscription
|
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)
|
await _removeInviteFromTeam(subscription.id, invite.email)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ const {
|
||||||
const EmailHandler = require('../Features/Email/EmailHandler')
|
const EmailHandler = require('../Features/Email/EmailHandler')
|
||||||
const logger = require('@overleaf/logger')
|
const logger = require('@overleaf/logger')
|
||||||
const OError = require('@overleaf/o-error')
|
const OError = require('@overleaf/o-error')
|
||||||
|
const Modules = require('./Modules')
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
if (!Features.hasFeature('saas')) {
|
if (!Features.hasFeature('saas')) {
|
||||||
|
@ -79,6 +80,26 @@ function start() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
registerCleanup(confirmInstitutionDomainQueue)
|
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) {
|
function registerCleanup(queue) {
|
||||||
|
|
|
@ -37,6 +37,10 @@ const QUEUES_JOB_OPTIONS = {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
},
|
},
|
||||||
|
'group-sso-reminder': {
|
||||||
|
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
||||||
|
attempts: 3,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const QUEUE_OPTIONS = {
|
const QUEUE_OPTIONS = {
|
||||||
|
|
|
@ -51,11 +51,11 @@ export default function DropdownButton({
|
||||||
isLoading: isResendingGroupInvite,
|
isLoading: isResendingGroupInvite,
|
||||||
} = useAsync<resendInviteResponse>()
|
} = useAsync<resendInviteResponse>()
|
||||||
|
|
||||||
const userNotManaged =
|
|
||||||
!user.isEntityAdmin && !user.invite && !user.enrollment?.managedBy
|
|
||||||
|
|
||||||
const userPending = user.invite
|
const userPending = user.invite
|
||||||
|
|
||||||
|
const userNotManaged =
|
||||||
|
!user.isEntityAdmin && !userPending && !user.enrollment?.managedBy
|
||||||
|
|
||||||
const handleResendManagedUserInvite = useCallback(
|
const handleResendManagedUserInvite = useCallback(
|
||||||
async user => {
|
async user => {
|
||||||
try {
|
try {
|
||||||
|
@ -219,7 +219,7 @@ export default function DropdownButton({
|
||||||
) : null}
|
) : null}
|
||||||
</MenuItemButton>
|
</MenuItemButton>
|
||||||
) : null}
|
) : null}
|
||||||
{ssoEnabledButNotAccepted && (
|
{!userPending && ssoEnabledButNotAccepted && (
|
||||||
<MenuItemButton
|
<MenuItemButton
|
||||||
onClick={onResendSSOLinkInviteClick}
|
onClick={onResendSSOLinkInviteClick}
|
||||||
data-testid="resend-sso-link-invite-action"
|
data-testid="resend-sso-link-invite-action"
|
||||||
|
|
|
@ -66,6 +66,10 @@ describe('TeamInvitesHandler', function () {
|
||||||
updateOne: sinon.stub().resolves(),
|
updateOne: sinon.stub().resolves(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.SSOConfig = {
|
||||||
|
findById: sinon.stub().resolves(),
|
||||||
|
}
|
||||||
|
|
||||||
this.EmailHandler = {
|
this.EmailHandler = {
|
||||||
promises: {
|
promises: {
|
||||||
sendEmail: sinon.stub().resolves(null),
|
sendEmail: sinon.stub().resolves(null),
|
||||||
|
@ -118,6 +122,7 @@ describe('TeamInvitesHandler', function () {
|
||||||
'@overleaf/settings': { siteUrl: 'http://example.com' },
|
'@overleaf/settings': { siteUrl: 'http://example.com' },
|
||||||
'../../models/TeamInvite': { TeamInvite: (this.TeamInvite = {}) },
|
'../../models/TeamInvite': { TeamInvite: (this.TeamInvite = {}) },
|
||||||
'../../models/Subscription': { Subscription: this.Subscription },
|
'../../models/Subscription': { Subscription: this.Subscription },
|
||||||
|
'../../models/SSOConfig': { SSOConfig: this.SSOConfig },
|
||||||
'../User/UserGetter': this.UserGetter,
|
'../User/UserGetter': this.UserGetter,
|
||||||
'./SubscriptionLocator': this.SubscriptionLocator,
|
'./SubscriptionLocator': this.SubscriptionLocator,
|
||||||
'./SubscriptionUpdater': this.SubscriptionUpdater,
|
'./SubscriptionUpdater': this.SubscriptionUpdater,
|
||||||
|
@ -125,6 +130,9 @@ describe('TeamInvitesHandler', function () {
|
||||||
'../Email/EmailHandler': this.EmailHandler,
|
'../Email/EmailHandler': this.EmailHandler,
|
||||||
'./ManagedUsersHandler': this.ManagedUsersHandler,
|
'./ManagedUsersHandler': this.ManagedUsersHandler,
|
||||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
'../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) {
|
it('adds the user to the team', function (done) {
|
||||||
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
||||||
this.SubscriptionUpdater.promises.addUserToGroup
|
this.SubscriptionUpdater.promises.addUserToGroup
|
||||||
|
@ -369,6 +378,48 @@ describe('TeamInvitesHandler', function () {
|
||||||
done()
|
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 () {
|
describe('revokeInvite', function () {
|
||||||
|
|
Loading…
Reference in a new issue