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

[UID-159] Additional RTR debugging and configuration options #431

Merged
merged 12 commits into from
Oct 9, 2024
145 changes: 112 additions & 33 deletions src/settings/RefreshTokenRotation.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,94 @@
import { useEffect, useState } from 'react';
import { merge } from 'lodash';
import PropTypes from 'prop-types';
import { useCallback, useEffect, useState } from 'react';
import { Field, Form } from 'react-final-form';
import { FormattedMessage } from 'react-intl';

import {
getTokenExpiry,
setTokenExpiry,
} from '@folio/stripes/core';

import {
Button,
LoadingPane,
Pane,
PaneHeader,
} from '@folio/stripes/components';
import { getTokenExpiry, setTokenExpiry } from '@folio/stripes/core';
import { Button, LoadingPane, Pane, PaneHeader, TextField } from '@folio/stripes/components';
import { RTR_FORCE_REFRESH_EVENT } from '../../../stripes-core/src/components/Root/constants';

ncovercash marked this conversation as resolved.
Show resolved Hide resolved
/**
* manipulate AT/RT expiration dates in storage in order to test RTR.
* @returns
*/
const RefreshTokenRotation = () => {
const RefreshTokenRotation = ({ stripes }) => {
const [isLoading, setIsLoading] = useState(true);
const [tokenExpiration, setTokenExpiration] = useState({});

useEffect(() => {
getTokenExpiry()
.then(te => {
setTokenExpiration(te ?? { atExpires: -1, rtExpires: -1 });
setIsLoading(false);
});
setIsLoading(true);
getTokenExpiry().then((te) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is goofy, but eslint got mad and insisted it was always false

setTokenExpiration(te ?? { atExpires: -1, rtExpires: -1 });
setIsLoading(false);
});
}, []);

/**
* invalidateAT
* return a promise to expire the AT in local storage
*/
const invalidateAT = () => {
return getTokenExpiry()
.then(te => {
const expiration = { ...te };
expiration.atExpires = -1;
const invalidateAT = useCallback(() => {
return getTokenExpiry().then((te) => {
const expiration = { ...te };
expiration.atExpires = -1;

return setTokenExpiry(expiration);
});
};
return setTokenExpiry(expiration);
});
}, []);

/**
* invalidateRT
* return a promise to expire the AT and RT in local storage
*/
const invalidateRT = () => {
const invalidateRT = useCallback(() => {
const expiration = {
atExpires: -1,
rtExpires: -1,
};

return setTokenExpiry(expiration);
};
}, []);

/**
* forceRefresh
* dispatch an event to force a token rotation
*/
const forceRefresh = useCallback(
() => window.dispatchEvent(new Event(RTR_FORCE_REFRESH_EVENT)),
[],
);

/**
* saveRtrConfig
* update stripes.config.rtr from form
*/
const saveRtrConfig = useCallback(
(values) => {
merge(stripes.config.rtr, {
idleSessionTTL: values.idleSessionTTL,
idleModalTTL: values.idleModalTTL,
rotationIntervalFraction: Number(values.rotationIntervalFraction),
activityEvents: values.activityEvents.split(',').map((e) => e.trim()),
});

forceRefresh();
},
[stripes, forceRefresh],
);

if (!isLoading) {
return (
<Pane
defaultWidth="fill"
renderHeader={renderProps => <PaneHeader {...renderProps} paneTitle={<FormattedMessage id="ui-developer.rtr" />} />}
renderHeader={(renderProps) => (
<PaneHeader {...renderProps} paneTitle={<FormattedMessage id="ui-developer.rtr" />} />
)}
>
<ul>
<li>stripes logs RTR events in the category <code>rtr</code></li>
<li>
stripes logs RTR events in the category <code>rtr</code>
</li>
</ul>
{!isLoading && (
<dl>
Expand All @@ -75,8 +99,55 @@ const RefreshTokenRotation = () => {
</dl>
)}
<div>
<Button onClick={invalidateAT}><FormattedMessage id="ui-developer.rtr.invalidateAT" /></Button>
<Button onClick={invalidateRT}><FormattedMessage id="ui-developer.rtr.invalidateRT" /></Button>
<Button onClick={invalidateAT}>
<FormattedMessage id="ui-developer.rtr.invalidateAT" />
</Button>
<Button onClick={invalidateRT}>
<FormattedMessage id="ui-developer.rtr.invalidateRT" />
</Button>

<Button onClick={forceRefresh}>
ncovercash marked this conversation as resolved.
Show resolved Hide resolved
<FormattedMessage id="ui-developer.rtr.forceRefresh" />
</Button>

<Form
onSubmit={saveRtrConfig}
initialValues={{
...stripes.config.rtr,
activityEvents: stripes.config.rtr.activityEvents.join(','),
}}
>
{({ handleSubmit, pristine, submitting }) => (
<form onSubmit={handleSubmit}>
<Field
component={TextField}
name="idleSessionTTL"
label={<FormattedMessage id="ui-developer.rtr.idleSessionTTL" />}
/>
<Field
component={TextField}
name="idleModalTTL"
label={<FormattedMessage id="ui-developer.rtr.idleModalTTL" />}
/>
<Field
component={TextField}
name="rotationIntervalFraction"
label={<FormattedMessage id="ui-developer.rtr.rotationIntervalFraction" />}
type="number"
step={0.01}
min={0}
/>
<Field
component={TextField}
name="activityEvents"
label={<FormattedMessage id="ui-developer.rtr.activityEvents" />}
/>
<Button buttonStyle="primary" type="submit" disabled={pristine || submitting}>
<FormattedMessage id="stripes-core.button.save" />
</Button>
</form>
)}
</Form>
</div>
</Pane>
);
Expand All @@ -89,7 +160,15 @@ RefreshTokenRotation.propTypes = {
stripes: PropTypes.shape({
okapi: PropTypes.shape({
tenant: PropTypes.string,
})
}),
config: PropTypes.shape({
rtr: PropTypes.shape({
idleSessionTTL: PropTypes.string,
idleModalTTL: PropTypes.string,
rotationIntervalFraction: PropTypes.number,
activityEvents: PropTypes.arrayOf(PropTypes.string),
}),
}),
}).isRequired,
};

Expand Down
6 changes: 5 additions & 1 deletion translations/ui-developer/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@
"rtr": "Refresh token rotation",
"rtr.invalidateAT": "Invalidate access token",
"rtr.invalidateRT": "Invalidate refresh token",
"rtr.forceRefresh": "Force refresh",
"rtr.idleSessionTTL": "idleSessionTTL: duration an idle session last before being killed (e.g. 1h, 1m, 5s, 10ms)",
"rtr.idleModalTTL": "idleModalTTL: duration the idle modal should be shown before session is killed (e.g. 1h, 1m, 5s, 10ms)",
"rtr.rotationIntervalFraction": "rotationIntervalFraction: decimal fraction of how early to refresh the access token (e.g. 0.6 is 60% into the lifetime of the token)",
"rtr.activityEvents": "activityEvents: which DOM events constitute user activity (comma-separated, e.g. 'mousemove,keydown')",
"rtr.registerServiceWorker": "Register the service worker",
"rtr.unregisterServiceWorker": "Unregister the service worker",

Expand All @@ -192,5 +197,4 @@
"permission.settings.okapiConsole.modules": "Settings (developer): Can use the Okapi console's Modules tab",
"permission.settings.userLocale": "Settings (developer): Can edit locale entries for any user",
"permission.settings.okapiTimers": "Settings (developer): Can view okapi timers"

}
Loading