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-918: *BREAKING* remove token-based authentication code #1592

Merged
merged 11 commits into from
Feb 14, 2025
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* *BREAKING* Upgrade `@folio/stripes-*` dependencies.
* *BREAKING* Upgrade `react-intl` to `^v7`. Refs STCOR-945.
* Change Help icon aria label to just Help in MainNav component. Refs STCOR-931.
* *BREAKING* remove token-based authentication code. Refs STCOR-918.
* *BREAKING* replace useSecureTokens conditionalsRefs STCOR-922.

## [10.2.0](https://github.com/folio-org/stripes-core/tree/v10.2.0) (2024-10-11)
[Full Changelog](https://github.com/folio-org/stripes-core/compare/v10.1.1...v10.2.0)
Expand Down
2 changes: 1 addition & 1 deletion src/RootWithIntl.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const RootWithIntl = ({ stripes, token = '', isAuthenticated = false, disableAut
{ (typeof connectedStripes.okapi !== 'object' || connectedStripes.discovery.isFinished) && (
<ModuleContainer id="content">
<OverlayContainer />
{connectedStripes.config.useSecureTokens && <SessionEventContainer history={history} queryClient={queryClient} />}
<SessionEventContainer history={history} queryClient={queryClient} />
<Switch>
<TitledRoute
name="home"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Login/LoginCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class LoginCtrl extends Component {
}

handleSubmit = (data) => {
return requestLogin(this.props.okapiUrl, this.context.store, this.tenant, data)
return requestLogin(this.props.okapiUrl, this.context.store, this.props.tenant, data)
.then(this.handleSuccessfulLogin)
.catch(e => {
console.error(e); // eslint-disable-line no-console
Expand Down
20 changes: 9 additions & 11 deletions src/components/Root/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,16 @@ class Root extends Component {
// enhanced security mode:
// * configure fetch and xhr interceptors to conduct RTR
// * see SessionEventContainer for RTR handling
if (this.props.config.useSecureTokens) {
const rtrConfig = configureRtr(this.props.config.rtr);
const rtrConfig = configureRtr(this.props.config.rtr);

this.ffetch = new FFetch({
logger: this.props.logger,
store,
rtrConfig,
okapi
});
this.ffetch.replaceFetch();
this.ffetch.replaceXMLHttpRequest();
}
this.ffetch = new FFetch({
logger: this.props.logger,
store,
rtrConfig,
okapi
});
this.ffetch.replaceFetch();
this.ffetch.replaceXMLHttpRequest();
}

componentDidMount() {
Expand Down
7 changes: 1 addition & 6 deletions src/components/SSOLanding/useSSOSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ import { useLocation } from 'react-router-dom';
import { useCookies } from 'react-cookie';
import queryString from 'query-string';

import { config } from 'stripes-config';

import { defaultErrors } from '../../constants';
import { setAuthError } from '../../okapiActions';
import { requestUserWithPerms } from '../../loginServices';
import { parseJWT } from '../../helpers';

const getParams = (location) => {
const search = location.search;
Expand All @@ -24,9 +21,7 @@ const getToken = (cookies, params) => {
};

const getTenant = (params, token, store) => {
const tenant = config.useSecureTokens
? params?.tenantId
: parseJWT(token)?.tenant;
const tenant = params?.tenantId;

return tenant || store.getState()?.okapi?.tenant;
};
Expand Down
3 changes: 2 additions & 1 deletion src/components/SSOLanding/useSSOSession.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ describe('SSOLanding', () => {

it('should request user session when RTR is disabled and right tenant from ssoToken', () => {
const tokenTenant = 'tokenTenant';
const okapiTenant = 'okapiTenant';
const store = useStore();

useLocation.mockReturnValue({ search: `ssoToken=${ssoTokenValue}` });
Expand All @@ -97,7 +98,7 @@ describe('SSOLanding', () => {

renderHook(() => useSSOSession());

expect(requestUserWithPerms).toHaveBeenCalledWith(store.getState().okapi.url, store, tokenTenant, ssoTokenValue);
expect(requestUserWithPerms).toHaveBeenCalledWith(store.getState().okapi.url, store, okapiTenant, ssoTokenValue);
});

it('should request user session when RTR is enabled and right tenant from query params', () => {
Expand Down
84 changes: 41 additions & 43 deletions src/components/SessionEventContainer/SessionEventContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,62 +188,60 @@ const SessionEventContainer = ({ history }) => {
// i.e. same keys as channels, but the value is the identically named object
const channelListeners = { window, bc };

if (stripes.config.useSecureTokens) {
const { idleModalTTL, idleSessionTTL, activityEvents } = stripes.config.rtr;

// inactive timer: show the "keep working?" modal
const showModalIT = createInactivityTimer(ms(idleSessionTTL) - ms(idleModalTTL), () => {
stripes.logger.log('rtr', 'session idle; showing modal');
stripes.store.dispatch(toggleRtrModal(true));
setIsVisible(true);
});
showModalIT.signal();
const { idleModalTTL, idleSessionTTL, activityEvents } = stripes.config.rtr;

// inactive timer: logout
const logoutIT = createInactivityTimer(idleSessionTTL, () => {
stripes.logger.log('rtr', 'session idle; dispatching RTR_TIMEOUT_EVENT');
// set a localstorage key so other windows know it was a timeout
localStorage.setItem(RTR_TIMEOUT_EVENT, 'true');
// inactive timer: show the "keep working?" modal
const showModalIT = createInactivityTimer(ms(idleSessionTTL) - ms(idleModalTTL), () => {
stripes.logger.log('rtr', 'session idle; showing modal');
stripes.store.dispatch(toggleRtrModal(true));
setIsVisible(true);
});
showModalIT.signal();

// dispatch a timeout event for handling in this window
window.dispatchEvent(new Event(RTR_TIMEOUT_EVENT));
});
logoutIT.signal();
// inactive timer: logout
const logoutIT = createInactivityTimer(idleSessionTTL, () => {
stripes.logger.log('rtr', 'session idle; dispatching RTR_TIMEOUT_EVENT');
// set a localstorage key so other windows know it was a timeout
localStorage.setItem(RTR_TIMEOUT_EVENT, 'true');

timers.current = { showModalIT, logoutIT };
// dispatch a timeout event for handling in this window
window.dispatchEvent(new Event(RTR_TIMEOUT_EVENT));
});
logoutIT.signal();

// RTR error in this window: logout
channels.window[RTR_ERROR_EVENT] = (e) => thisWindowRtrError(e, stripes, history);
timers.current = { showModalIT, logoutIT };

// idle session timeout in this window: logout
channels.window[RTR_TIMEOUT_EVENT] = (e) => thisWindowRtrIstTimeout(e, stripes, history);
// RTR error in this window: logout
channels.window[RTR_ERROR_EVENT] = (e) => thisWindowRtrError(e, stripes, history);

// localstorage change in another window: logout?
channels.window.storage = (e) => otherWindowStorage(e, stripes, history);
// idle session timeout in this window: logout
channels.window[RTR_TIMEOUT_EVENT] = (e) => thisWindowRtrIstTimeout(e, stripes, history);

// activity in another window: send keep-alive to idle-timers.
channels.bc.message = (message) => otherWindowActivity(message, stripes, timers, setIsVisible);
// localstorage change in another window: logout?
channels.window.storage = (e) => otherWindowStorage(e, stripes, history);

// activity in this window: ping idle-timers and BroadcastChannel
activityEvents.forEach(eventName => {
channels.window[eventName] = (e) => thisWindowActivity(e, stripes, timers, bc);
});
// activity in another window: send keep-alive to idle-timers.
channels.bc.message = (message) => otherWindowActivity(message, stripes, timers, setIsVisible);

// activity in this window: ping idle-timers and BroadcastChannel
activityEvents.forEach(eventName => {
channels.window[eventName] = (e) => thisWindowActivity(e, stripes, timers, bc);
});

// fixed-length session: show session-is-ending warning
channels.window[RTR_FLS_WARNING_EVENT] = (e) => thisWindowRtrFlsWarning(e, stripes, setIsFlsVisible);
// fixed-length session: show session-is-ending warning
channels.window[RTR_FLS_WARNING_EVENT] = (e) => thisWindowRtrFlsWarning(e, stripes, setIsFlsVisible);

// fixed-length session: terminate session
channels.window[RTR_FLS_TIMEOUT_EVENT] = (e) => thisWindowRtrFlsTimeout(e, stripes, history);
// fixed-length session: terminate session
channels.window[RTR_FLS_TIMEOUT_EVENT] = (e) => thisWindowRtrFlsTimeout(e, stripes, history);


// add listeners
Object.entries(channels).forEach(([k, channel]) => {
Object.entries(channel).forEach(([e, h]) => {
stripes.logger.log('rtrv', `adding listener ${k}.${e}`);
channelListeners[k].addEventListener(e, h);
});
// add listeners
Object.entries(channels).forEach(([k, channel]) => {
Object.entries(channel).forEach(([e, h]) => {
stripes.logger.log('rtrv', `adding listener ${k}.${e}`);
channelListeners[k].addEventListener(e, h);
});
}
});

// cleanup: clear timers and event listeners
return () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,6 @@ describe('SessionEventContainer', () => {
eventsPortalElement.id = eventsPortal;
document.body.appendChild(eventsPortalElement);
});
it('Renders nothing if useSecureTokens is false', async () => {
const insecureStripes = {
config: {
useSecureTokens: false,
},
};
render(<Harness stripes={insecureStripes}><SessionEventContainer /></Harness>);

expect(screen.queryByText('KeepWorkingModal')).toBe(null);
});

it('Shows a modal when idle timer expires', async () => {
render(<Harness stripes={stripes}><SessionEventContainer /></Harness>);
Expand Down
2 changes: 1 addition & 1 deletion src/loginServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ export function checkOkapiSession(okapiUrl, store, tenant) {
* @returns {Promise}
*/
export function requestLogin(okapiUrl, store, tenant, data) {
const loginPath = config.useSecureTokens ? 'login-with-expiry' : 'login';
const loginPath = 'login-with-expiry';
return fetch(`${okapiUrl}/bl-users/${loginPath}?expandPermissions=true&fullPermissions=true`, {
body: JSON.stringify(data),
credentials: 'include',
Expand Down
2 changes: 1 addition & 1 deletion src/loginServices.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ describe('unauthorizedPath functions', () => {
);

expect(global.fetch).toHaveBeenCalledWith(
'http://okapi-url/bl-users/login?expandPermissions=true&fullPermissions=true',
'http://okapi-url/bl-users/login-with-expiry?expandPermissions=true&fullPermissions=true',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
Expand Down
17 changes: 0 additions & 17 deletions test/bigtest/network/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,6 @@ export default function configure() {

this.post('/authn/logout', {}, 204);

this.post('/bl-users/login', () => {
return new Response(201, {}, {
user: {
id: 'test',
username: 'testuser',
personal: {
lastName: 'User',
firstName: 'Test',
email: '[email protected]',
}
},
permissions: {
permissions: []
}
});
});

this.post('/bl-users/login-with-expiry', () => {
return new Response(201, {}, {
user: {
Expand Down