diff --git a/services/web/app/src/Features/Email/EmailBuilder.js b/services/web/app/src/Features/Email/EmailBuilder.js
index 6070f03cfa..41f0ba3847 100644
--- a/services/web/app/src/Features/Email/EmailBuilder.js
+++ b/services/web/app/src/Features/Email/EmailBuilder.js
@@ -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({
What does this mean for you?
-
+
- 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.
`,
@@ -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({
What does this mean for you?
-
+
You now need an email address and password to sign in to your Overleaf account.
diff --git a/services/web/app/src/Features/Subscription/TeamInvitesHandler.js b/services/web/app/src/Features/Subscription/TeamInvitesHandler.js
index ab6c620ea2..fde4842fa6 100644
--- a/services/web/app/src/Features/Subscription/TeamInvitesHandler.js
+++ b/services/web/app/src/Features/Subscription/TeamInvitesHandler.js
@@ -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)
diff --git a/services/web/app/src/infrastructure/QueueWorkers.js b/services/web/app/src/infrastructure/QueueWorkers.js
index 31921447a6..0c9ed4f1cb 100644
--- a/services/web/app/src/infrastructure/QueueWorkers.js
+++ b/services/web/app/src/infrastructure/QueueWorkers.js
@@ -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) {
diff --git a/services/web/app/src/infrastructure/Queues.js b/services/web/app/src/infrastructure/Queues.js
index b6a0a19c44..6610528097 100644
--- a/services/web/app/src/infrastructure/Queues.js
+++ b/services/web/app/src/infrastructure/Queues.js
@@ -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 = {
diff --git a/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx b/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx
index db914040c5..900ff39ff9 100644
--- a/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx
+++ b/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx
@@ -51,11 +51,11 @@ export default function DropdownButton({
isLoading: isResendingGroupInvite,
} = useAsync()
- 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}
) : null}
- {ssoEnabledButNotAccepted && (
+ {!userPending && ssoEnabledButNotAccepted && (
{
- 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()
+ })
})
})
})