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) {
|
||||
const subjectPrefix = opts.reminder ? 'Reminder: ' : 'Action required: '
|
||||
return `${subjectPrefix}Authenticate your Overleaf account`
|
||||
|
@ -493,10 +493,10 @@ templates.managedUsersEnabledSSO = ctaTemplate({
|
|||
</br>
|
||||
<div>
|
||||
<strong>What does this mean for you?</strong>
|
||||
</div>
|
||||
</div>
|
||||
</br>
|
||||
<div>
|
||||
You won't need to remember a separate email address and password to sign in to Overleaf.
|
||||
You won't need to remember a separate email address and password to sign in to Overleaf.
|
||||
All you need to do is authenticate your existing Overleaf account with your SSO provider.
|
||||
</div>
|
||||
`,
|
||||
|
@ -516,7 +516,7 @@ templates.managedUsersEnabledSSO = ctaTemplate({
|
|||
},
|
||||
})
|
||||
|
||||
templates.managedUsersDisabledSSO = ctaTemplate({
|
||||
templates.groupSSODisabled = ctaTemplate({
|
||||
subject(opts) {
|
||||
return `Action required: Set your Overleaf password`
|
||||
},
|
||||
|
@ -532,7 +532,7 @@ templates.managedUsersDisabledSSO = ctaTemplate({
|
|||
</br>
|
||||
<div>
|
||||
<strong>What does this mean for you?</strong>
|
||||
</div>
|
||||
</div>
|
||||
</br>
|
||||
<div>
|
||||
You now need an email address and password to sign in to your Overleaf account.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,39 +342,82 @@ describe('TeamInvitesHandler', function () {
|
|||
})
|
||||
})
|
||||
|
||||
it('adds the user to the team', function (done) {
|
||||
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
||||
this.SubscriptionUpdater.promises.addUserToGroup
|
||||
.calledWith(this.subscription._id, this.user.id)
|
||||
.should.eq(true)
|
||||
done()
|
||||
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
|
||||
.calledWith(this.subscription._id, this.user.id)
|
||||
.should.eq(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('removes the invite from the subscription', function (done) {
|
||||
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
||||
this.Subscription.updateOne
|
||||
.calledWith(
|
||||
{ _id: new ObjectId('55153a8014829a865bbf700d') },
|
||||
{ $pull: { teamInvites: { email: 'john.snow@example.com' } } }
|
||||
it('removes the invite from the subscription', function (done) {
|
||||
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
||||
this.Subscription.updateOne
|
||||
.calledWith(
|
||||
{ _id: new ObjectId('55153a8014829a865bbf700d') },
|
||||
{ $pull: { teamInvites: { email: 'john.snow@example.com' } } }
|
||||
)
|
||||
.should.eq(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('removes dashboard notification after they accepted group invitation', function (done) {
|
||||
const managedUsersEnabled = false
|
||||
|
||||
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
||||
sinon.assert.called(
|
||||
this.NotificationsBuilder.promises.groupInvitation(
|
||||
this.user.id,
|
||||
this.subscription._id,
|
||||
managedUsersEnabled
|
||||
).read
|
||||
)
|
||||
.should.eq(true)
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('removes dashboard notification after they accepted group invitation', function (done) {
|
||||
const managedUsersEnabled = false
|
||||
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.called(
|
||||
this.NotificationsBuilder.promises.groupInvitation(
|
||||
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
|
||||
sinon.assert.calledWith(
|
||||
this.ManagedUsersHandler.promises.enrollInSubscription,
|
||||
this.user.id,
|
||||
this.subscription._id,
|
||||
managedUsersEnabled
|
||||
).read
|
||||
)
|
||||
done()
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue