Skip to content

Commit

Permalink
[UID-159] Additional RTR debugging and configuration options (#431)
Browse files Browse the repository at this point in the history
Expose additional RTR configuration and debugging functions

Refs UID-159

---------

Co-authored-by: Zak Burke <[email protected]>
  • Loading branch information
ncovercash and zburke authored Oct 9, 2024
1 parent a9819fa commit fefb060
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 46 deletions.
148 changes: 105 additions & 43 deletions src/settings/RefreshTokenRotation.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,79 @@
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 } from '@folio/stripes/core';
import { Button, LoadingPane, Pane, PaneHeader, TextField } from '@folio/stripes/components';

/**
* manipulate AT/RT expiration dates in storage in order to test RTR.
* @returns
*/
const RefreshTokenRotation = () => {
const RefreshTokenRotation = ({ stripes }) => {
// why WHY copy this string here instead of importing it from stripes-core?
//
// RTR_FORCE_REFRESH_EVENT will be present in stripes-core 10.2.0 (stripes
// 9.2.0). Importing it would force the stripes peer depedency to bump from
// ^9.1.0 to ^9.2.0.If we copy the string instead of importing it, we can
// remain compatible with 9.1.0.
//
// OK, compatibility is nice. But it's still gross, right? Yep, super gross.
// Aren't you nauseated? Yes, yes I am. 🤢🧼🛁
const RTR_FORCE_REFRESH_EVENT = '@folio/stripes/core::RTRForceRefresh';

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) => {
setTokenExpiration(te ?? { atExpires: -1, rtExpires: -1 });
setIsLoading(false);
});
}, []);

/**
* invalidateAT
* return a promise to expire the AT in local storage
* forceRefresh
* dispatch an event to force a token rotation
*/
const invalidateAT = () => {
return getTokenExpiry()
.then(te => {
const expiration = { ...te };
expiration.atExpires = -1;

return setTokenExpiry(expiration);
});
};
const forceRefresh = useCallback(
() => window.dispatchEvent(new Event(RTR_FORCE_REFRESH_EVENT)),
[],
);

/**
* invalidateRT
* return a promise to expire the AT and RT in local storage
* saveRtrConfig
* update stripes.config.rtr from form
*/
const invalidateRT = () => {
const expiration = {
atExpires: -1,
rtExpires: -1,
};
const saveRtrConfig = useCallback(
(values) => {
merge(stripes.config.rtr, {
idleSessionTTL: values.idleSessionTTL,
idleModalTTL: values.idleModalTTL,
fixedLengthSessionWarningTTL: values.fixedLengthSessionWarningTTL,
rotationIntervalFraction: Number(values.rotationIntervalFraction),
activityEvents: values.activityEvents.split(',').map((e) => e.trim()),
});

return setTokenExpiry(expiration);
};
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 +84,53 @@ 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={forceRefresh}>
<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="fixedLengthSessionWarningTTL"
label={<FormattedMessage id="ui-developer.rtr.fixedLengthSessionWarningTTL" />}
/>
<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 +143,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
9 changes: 6 additions & 3 deletions translations/ui-developer/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,12 @@
"userLocale.numberingSystem": "Numbering system (override locale's default; <a>details</a>)",

"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 lasts 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.fixedLengthSessionWarningTTL": "fixedLengthSessionWarningTTL: how long the “your session is going to die!” warning should be shown before the 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 +196,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"

}

0 comments on commit fefb060

Please sign in to comment.