Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STCOR-902 show error message on OIDC fetch failure #1557

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 55 additions & 33 deletions src/components/OIDCLanding.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import React, { useEffect, useState } from 'react';
import { useLocation, Redirect } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import queryString from 'query-string';
import { useStore } from 'react-redux';
import { FormattedMessage } from 'react-intl';

import { Loading } from '@folio/stripes-components';
import {
Button,
Col,
Headline,
Loading,
Row,
} from '@folio/stripes-components';

import OrganizationLogo from './OrganizationLogo';
import { requestUserWithPerms, setTokenExpiry } from '../loginServices';

import css from './Front.css';
import { useStripes } from '../StripesContext';

/**
* OIDCLanding: un-authenticated route handler for /sso-landing.
* OIDCLanding: un-authenticated route handler for /oidc-landing.
*
* Reads one-time-code from URL params, exchanging it for an access_token
* and then leveraging that to retrieve a user via requestUserWithPerms,
* eventually dispatching session and Okapi-ready, resulting in a
* re-render of RoothWithIntl with prop isAuthenticated: true.
* * Read one-time-code from URL params
* * make an API call to /authn/token to exchange the OTP for cookies
* * call requestUserWithPerms to make an API call to .../_self,
* eventually dispatching session and Okapi-ready, resulting in a
* re-render of RoothWithIntl with prop isAuthenticated: true
*
* @see RootWithIntl
*/
const OIDCLanding = () => {
const location = useLocation();
const store = useStore();
// const samlError = useRef();
const { okapi } = useStripes();
const [potp, setPotp] = useState();
const [samlError, setSamlError] = useState();

const [oidcError, setOIDCError] = useState();

/**
* Exchange the otp for AT/RT cookies, then retrieve the user.
Expand Down Expand Up @@ -56,7 +61,6 @@ const OIDCLanding = () => {
const otp = getOtp();

if (otp) {
setPotp(otp);
fetch(`${okapi.url}/authn/token?code=${otp}&redirect-uri=${window.location.protocol}//${window.location.host}/oidc-landing`, {
credentials: 'include',
headers: { 'X-Okapi-tenant': okapi.tenant, 'Content-Type': 'application/json' },
Expand All @@ -83,7 +87,7 @@ const OIDCLanding = () => {
.catch(e => {
// eslint-disable-next-line no-console
console.error('@@ Oh, snap, OTP exchange failed!', e);
setSamlError(e);
setOIDCError(e);
});
}
// we only want to run this effect once, on load.
Expand All @@ -93,22 +97,45 @@ const OIDCLanding = () => {
// store: the redux store
}, []); // eslint-disable-line react-hooks/exhaustive-deps

if (samlError) {
/**
* formatOIDCError
* Return formatted OIDC error message, or null
* @returns
*/
const formatOIDCError = () => {
if (Array.isArray(oidcError?.errors)) {
return (
<Row center="xs">
<Col xs={12}>
<Headline>{oidcError.errors[0]?.message}</Headline>
</Col>
</Row>
);
}

return null;
};

if (oidcError) {
return (
<div data-test-saml-error>
<div>
<FormattedMessage id="errors.saml.missingToken" />
</div>
<div>
<h3>code</h3>
{potp}
<h3>error</h3>
<code>
{JSON.stringify(samlError, null, 2)}
</code>
</div>
<Redirect to="/" />
</div>
<main data-test-saml-error>
<Row center="xs">
<Col xs={12}>
<OrganizationLogo />
</Col>
</Row>
<Row center="xs">
<Col xs={12}>
<Headline size="large"><FormattedMessage id="stripes-core.errors.oidc" /></Headline>
</Col>
</Row>
{formatOIDCError()}
<Row center="xs">
<Col xs={12}>
<Button to="/"><FormattedMessage id="stripes-core.rtr.idleSession.logInAgain" /></Button>
</Col>
</Row>
</main>
);
}

Expand All @@ -117,11 +144,6 @@ const OIDCLanding = () => {
<div className={css.frontWrap}>
<Loading size="xlarge" />
</div>
<div>
<pre>
{JSON.stringify(samlError, null, 2)}
</pre>
</div>
</div>
);
};
Expand Down
5 changes: 2 additions & 3 deletions src/components/OIDCLanding.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ jest.mock('../StripesContext', () => ({
}),
}));

// jest.mock('../loginServices');

jest.mock('./OrganizationLogo', () => (() => <div>OrganizationLogo</div>));

const mockSetTokenExpiry = jest.fn();
const mockRequestUserWithPerms = jest.fn();
Expand Down Expand Up @@ -79,7 +78,7 @@ describe('OIDCLanding', () => {
mockFetchError('barf');

await render(<OIDCLanding />);
await screen.findByText('errors.saml.missingToken');
await screen.findByText('stripes-core.errors.oidc');
mockFetchCleanUp();
});
});
1 change: 1 addition & 0 deletions translations/stripes-core/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
"errors.password.consecutiveWhitespaces.invalid": "The password must not contain consecutive white space characters.",
"errors.password.compromised.invalid": "The password must not be commonly-used, expected or compromised",
"errors.saml.missingToken": "No <code>code</code> query parameter.",
"errors.oidc": "Error: server is forbidden, unreachable, or unavailable.",

"createResetPassword.header": "Choose a password",
"createResetPassword.newPassword": "New Password",
Expand Down
4 changes: 3 additions & 1 deletion translations/stripes-core/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,7 @@
"tenantLibrary": "Tenant/Library",
"errors.saml.missingToken": "No <code>code</code> query parameter.",
"rtr.fixedLengthSession.timeRemaining": "Your session will end soon! Time remaining:",
"logoutComplete": "You have logged out."
"logoutComplete": "You have logged out.",
"errors.oidc": "Error: server is forbidden, unreachable, or unavailable."

}
Loading