Merge pull request #32401 from overleaf/jel-sso-unable-to-reconfirm

[web] Return error when user is reconfirming but no user found

GitOrigin-RevId: 4c7b626b0c6ea6a7e883f90d16c5fb1330ffbbc6
This commit is contained in:
Jessica Lawshe
2026-04-13 08:29:24 -05:00
committed by Copybot
parent 3f75f35a8e
commit 2758952781
9 changed files with 98 additions and 25 deletions

View File

@@ -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,

View File

@@ -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": "",

View File

@@ -183,23 +183,37 @@ function Institution() {
}
/>
)}
{templateKey === 'notification_institution_sso_error' && (
<Notification
type="error"
onDismiss={() => id && handleDismiss(id)}
content={
<>
{t('generic_something_went_wrong')}.
<div>
{error?.translatedMessage
? error?.translatedMessage
: error?.message}
</div>
{error?.tryAgain ? `${t('try_again')}.` : null}
</>
}
/>
)}
{templateKey === 'notification_institution_sso_error' &&
(error?.name ===
'SAMLCommonsReconfirmationUnableToFindUserError' ? (
<Notification
type="error"
onDismiss={() => id && handleDismiss(id)}
content={
<Trans
i18nKey="saml_commons_reconfirmation_unable_to_find_user"
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
components={[<a href="/contact" target="_blank" />]}
/>
}
/>
) : (
<Notification
type="error"
onDismiss={() => id && handleDismiss(id)}
content={
<>
{t('generic_something_went_wrong')}.
<div>
{error?.translatedMessage
? error?.translatedMessage
: error?.message}
</div>
{error?.tryAgain ? `${t('try_again')}.` : null}
</>
}
/>
))}
</Fragment>
)
)}

View File

@@ -21,17 +21,26 @@ export function SSOAlert() {
const handleErrorClosed = () => setErrorClosed(true)
if (samlError) {
const content =
samlError.name === 'SAMLCommonsReconfirmationUnableToFindUserError' ? (
<Trans
i18nKey="saml_commons_reconfirmation_unable_to_find_user"
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
components={[<a href="/contact" target="_blank" />]}
/>
) : (
<>
{samlError.translatedMessage
? samlError.translatedMessage
: samlError.message}
{samlError.tryAgain && <p>{t('try_again')}</p>}
</>
)
return !errorClosed ? (
<OLNotification
type="error"
content={
<>
{samlError.translatedMessage
? samlError.translatedMessage
: samlError.message}
{samlError.tryAgain && <p>{t('try_again')}</p>}
</>
}
content={content}
isDismissible
onDismiss={handleErrorClosed}
/>

View File

@@ -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</0> for help with relinking your Overleaf account to your institutions SSO.",
"saml_commons_unavailable": "Institution SSO is currently unavailable. For more details, please see <0>__linkText__</0>.",
"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__</0> was not recognized. Your organizations identity provider returned <1>__samlEmail__</1>. You will need to log out of <0>__userEmail__</0> and use <1>__samlEmail__</1> to create an account via SSO. You can <2>transfer your existing projects</2> to the new account.",

View File

@@ -627,6 +627,31 @@ describe('<UserNotifications />', function () {
expect(screen.queryByRole('alert')).to.be.null
})
it('shows reconfirmation unable-to-find-user error content', function () {
const institution: DeepPartial<InstitutionType> = {
templateKey: 'notification_institution_sso_error',
error: {
name: 'SAMLCommonsReconfirmationUnableToFindUserError',
},
}
window.metaAttributesCache.set('ol-notificationsInstitution', [
{ ...notificationsInstitution, ...institution },
])
render(<Institution />)
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 () {

View File

@@ -84,6 +84,19 @@ describe('<SSOAlert/>', 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(<SSOAlert />)
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(<SSOAlert />)
const closeButton = screen.getByRole('button', {

View File

@@ -93,6 +93,7 @@ export type Institution = {
translatedMessage?: string
message?: string
tryAgain?: boolean
name?: string
}
}

View File

@@ -10,6 +10,7 @@ export type SAMLError = {
translatedMessage?: string
message?: string
tryAgain?: boolean
name?: string
}
export type InstitutionLink = {