diff --git a/services/web/app/src/Features/Errors/Errors.js b/services/web/app/src/Features/Errors/Errors.js index c66780610a..0d3a763900 100644 --- a/services/web/app/src/Features/Errors/Errors.js +++ b/services/web/app/src/Features/Errors/Errors.js @@ -120,6 +120,13 @@ class SAMLAuthenticationError extends OError { return 'saml_auth_error' } } + +class SAMLCommonsReconfirmationUnableToFindUserError extends SAMLAuthenticationError { + get i18nKey() { + return 'saml_commons_reconfirmation_unable_to_find_user' + } +} + class SAMLAssertionAudienceMismatch extends SAMLAuthenticationError {} class SAMLAuthenticationRequiredError extends SAMLAuthenticationError { @@ -394,6 +401,7 @@ module.exports = { OutputFileFetchFailedError, SAMLAssertionAudienceMismatch, SAMLAuthenticationRequiredError, + SAMLCommonsReconfirmationUnableToFindUserError, SAMLCommonsUnavailable, SAMLDomainCaptureEmailExistsError, SAMLDomainCaptureEmailDomainMismatchError, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index cbf35d790f..066d4c11fb 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1601,6 +1601,7 @@ "run_current_script_to_see_output": "", "run_python_code": "", "saml_auth_error": "", + "saml_commons_reconfirmation_unable_to_find_user": "", "saml_email_not_in_account_error_managed_users_2": "", "saml_identity_exists_error": "", "saml_invalid_signature_error": "", diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/institution.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/institution.tsx index 4e8bb01bd9..87b0433270 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/groups/institution.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/groups/institution.tsx @@ -183,23 +183,37 @@ function Institution() { } /> )} - {templateKey === 'notification_institution_sso_error' && ( - id && handleDismiss(id)} - content={ - <> - {t('generic_something_went_wrong')}. -
- {error?.translatedMessage - ? error?.translatedMessage - : error?.message} -
- {error?.tryAgain ? `${t('try_again')}.` : null} - - } - /> - )} + {templateKey === 'notification_institution_sso_error' && + (error?.name === + 'SAMLCommonsReconfirmationUnableToFindUserError' ? ( + id && handleDismiss(id)} + content={ + ]} + /> + } + /> + ) : ( + id && handleDismiss(id)} + content={ + <> + {t('generic_something_went_wrong')}. +
+ {error?.translatedMessage + ? error?.translatedMessage + : error?.message} +
+ {error?.tryAgain ? `${t('try_again')}.` : null} + + } + /> + ))} ) )} diff --git a/services/web/frontend/js/features/settings/components/emails/sso-alert.tsx b/services/web/frontend/js/features/settings/components/emails/sso-alert.tsx index 4f90feed6a..c4d7f23506 100644 --- a/services/web/frontend/js/features/settings/components/emails/sso-alert.tsx +++ b/services/web/frontend/js/features/settings/components/emails/sso-alert.tsx @@ -21,17 +21,26 @@ export function SSOAlert() { const handleErrorClosed = () => setErrorClosed(true) if (samlError) { + const content = + samlError.name === 'SAMLCommonsReconfirmationUnableToFindUserError' ? ( + ]} + /> + ) : ( + <> + {samlError.translatedMessage + ? samlError.translatedMessage + : samlError.message} + {samlError.tryAgain &&

{t('try_again')}

} + + ) + return !errorClosed ? ( - {samlError.translatedMessage - ? samlError.translatedMessage - : samlError.message} - {samlError.tryAgain &&

{t('try_again')}

} - - } + content={content} isDismissible onDismiss={handleErrorClosed} /> diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 2b453358ee..c43417be6a 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -2089,6 +2089,7 @@ "saml": "SAML", "saml_auth_error": "Sorry, your identity provider responded with an error. Please contact your administrator for more information.", "saml_authentication_required_error": "Other login methods have been disabled by your group administrator. Please use your group SSO login.", + "saml_commons_reconfirmation_unable_to_find_user": "Sorry, we were unable to confirm your affiliation. This may have been caused by a change in the SSO data that is being sent to us from your institution. Please <0>contact us for help with relinking your Overleaf account to your institution’s SSO.", "saml_commons_unavailable": "Institution SSO is currently unavailable. For more details, please see <0>__linkText__.", "saml_create_admin_instructions": "Choose an email address for the first __appName__ admin account. This should correspond to an account in the SAML system. You will then be asked to log in with this account.", "saml_email_not_in_account_error_managed_users_2": "Your email address <0>__userEmail__ was not recognized. Your organization’s identity provider returned <1>__samlEmail__. You will need to log out of <0>__userEmail__ and use <1>__samlEmail__ to create an account via SSO. You can <2>transfer your existing projects to the new account.", diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index 644135f0b1..a29ed9133b 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -627,6 +627,31 @@ describe('', function () { expect(screen.queryByRole('alert')).to.be.null }) + + it('shows reconfirmation unable-to-find-user error content', function () { + const institution: DeepPartial = { + templateKey: 'notification_institution_sso_error', + error: { + name: 'SAMLCommonsReconfirmationUnableToFindUserError', + }, + } + window.metaAttributesCache.set('ol-notificationsInstitution', [ + { ...notificationsInstitution, ...institution }, + ]) + render() + + screen.getByRole('alert') + screen.getByText(/unable to confirm your affiliation/i) + + const contactLink = screen.getByRole('link', { name: /contact us/i }) + expect(contactLink.getAttribute('href')).to.equal('/contact') + expect(contactLink.getAttribute('target')).to.equal('_blank') + + const closeBtn = screen.getByRole('button', { name: /close/i }) + fireEvent.click(closeBtn) + + expect(screen.queryByRole('alert')).to.be.null + }) }) describe('getEmailDeletionDate', function () { diff --git a/services/web/test/frontend/features/settings/components/emails/sso-alert.test.tsx b/services/web/test/frontend/features/settings/components/emails/sso-alert.test.tsx index 1bce35d8e4..0ad5a1f11a 100644 --- a/services/web/test/frontend/features/settings/components/emails/sso-alert.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/sso-alert.test.tsx @@ -84,6 +84,19 @@ describe('', function () { screen.getByText('Please try again') }) + it('should render reconfirmation unable-to-find-user message for matching SAML error name', function () { + window.metaAttributesCache.set('ol-samlError', { + name: 'SAMLCommonsReconfirmationUnableToFindUserError', + }) + + render() + + screen.getByText(/unable to confirm your affiliation/i) + const contactLink = screen.getByRole('link', { name: /contact us/i }) + expect(contactLink.getAttribute('href')).to.equal('/contact') + expect(contactLink.getAttribute('target')).to.equal('_blank') + }) + it('the alert should be closeable', function () { render() const closeButton = screen.getByRole('button', { diff --git a/services/web/types/project/dashboard/notification.ts b/services/web/types/project/dashboard/notification.ts index 2db8e12432..72f836474f 100644 --- a/services/web/types/project/dashboard/notification.ts +++ b/services/web/types/project/dashboard/notification.ts @@ -93,6 +93,7 @@ export type Institution = { translatedMessage?: string message?: string tryAgain?: boolean + name?: string } } diff --git a/services/web/types/settings-page.ts b/services/web/types/settings-page.ts index 8c0a487e3a..1f0fa6dc83 100644 --- a/services/web/types/settings-page.ts +++ b/services/web/types/settings-page.ts @@ -10,6 +10,7 @@ export type SAMLError = { translatedMessage?: string message?: string tryAgain?: boolean + name?: string } export type InstitutionLink = {