From ccb194eed7167853c22e480570ebd42859fbfeda Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Wed, 18 Sep 2024 17:42:37 -0400 Subject: [PATCH 1/8] [UID-159] Additional RTR debugging and configuration options --- src/settings/RefreshTokenRotation.js | 145 +++++++++++++++++++++------ translations/ui-developer/en.json | 6 +- 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/src/settings/RefreshTokenRotation.js b/src/settings/RefreshTokenRotation.js index 626c7f0..9529776 100644 --- a/src/settings/RefreshTokenRotation.js +++ b/src/settings/RefreshTokenRotation.js @@ -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'; /** * 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) => { + 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 ( } />} + renderHeader={(renderProps) => ( + } /> + )} >
    -
  • stripes logs RTR events in the category rtr
  • +
  • + stripes logs RTR events in the category rtr +
{!isLoading && (
@@ -75,8 +99,55 @@ const RefreshTokenRotation = () => {
)}
- - + + + + + +
+ {({ handleSubmit, pristine, submitting }) => ( + + } + /> + } + /> + } + type="number" + step={0.01} + min={0} + /> + } + /> + + + )} +
); @@ -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, }; diff --git a/translations/ui-developer/en.json b/translations/ui-developer/en.json index 6323ba0..682362e 100644 --- a/translations/ui-developer/en.json +++ b/translations/ui-developer/en.json @@ -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", @@ -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" - } From 72fb50a5886c859093d7f824288ba73eb97c8711 Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Thu, 19 Sep 2024 10:06:54 -0400 Subject: [PATCH 2/8] typo --- translations/ui-developer/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/ui-developer/en.json b/translations/ui-developer/en.json index 682362e..bd00c6a 100644 --- a/translations/ui-developer/en.json +++ b/translations/ui-developer/en.json @@ -171,7 +171,7 @@ "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.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.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')", From f9759134a2bc63de16a57914f144be1682770897 Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Fri, 20 Sep 2024 10:22:05 -0400 Subject: [PATCH 3/8] cleanup --- src/settings/RefreshTokenRotation.js | 38 ++-------------------------- translations/ui-developer/en.json | 2 -- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/src/settings/RefreshTokenRotation.js b/src/settings/RefreshTokenRotation.js index 9529776..40a886b 100644 --- a/src/settings/RefreshTokenRotation.js +++ b/src/settings/RefreshTokenRotation.js @@ -4,9 +4,8 @@ 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 { getTokenExpiry, RTR_CONSTANTS } 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'; /** * manipulate AT/RT expiration dates in storage in order to test RTR. @@ -24,38 +23,12 @@ const RefreshTokenRotation = ({ stripes }) => { }); }, []); - /** - * invalidateAT - * return a promise to expire the AT in local storage - */ - const invalidateAT = useCallback(() => { - return getTokenExpiry().then((te) => { - const expiration = { ...te }; - expiration.atExpires = -1; - - return setTokenExpiry(expiration); - }); - }, []); - - /** - * invalidateRT - * return a promise to expire the AT and RT in local storage - */ - 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)), + () => window.dispatchEvent(new Event(RTR_CONSTANTS.RTR_FORCE_REFRESH_EVENT)), [], ); @@ -99,13 +72,6 @@ const RefreshTokenRotation = ({ stripes }) => { )}
- - - diff --git a/translations/ui-developer/en.json b/translations/ui-developer/en.json index bd00c6a..5991f93 100644 --- a/translations/ui-developer/en.json +++ b/translations/ui-developer/en.json @@ -168,8 +168,6 @@ "userLocale.numberingSystem": "Numbering system (override locale's default; details)", "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)", From 8dcd6046ce701471573952fc71a9c2fecdbc5e4f Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Tue, 24 Sep 2024 20:34:26 -0400 Subject: [PATCH 4/8] add fixedLengthSessionWarningTTL --- src/settings/RefreshTokenRotation.js | 6 ++++++ translations/ui-developer/en.json | 1 + 2 files changed, 7 insertions(+) diff --git a/src/settings/RefreshTokenRotation.js b/src/settings/RefreshTokenRotation.js index 40a886b..0cef742 100644 --- a/src/settings/RefreshTokenRotation.js +++ b/src/settings/RefreshTokenRotation.js @@ -41,6 +41,7 @@ const RefreshTokenRotation = ({ stripes }) => { 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()), }); @@ -95,6 +96,11 @@ const RefreshTokenRotation = ({ stripes }) => { name="idleModalTTL" label={} /> + } + /> Date: Thu, 10 Oct 2024 16:20:20 -0400 Subject: [PATCH 5/8] [UID-159] Better handle RTR callback; handle new token expiration format --- src/settings/RefreshTokenRotation.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/settings/RefreshTokenRotation.js b/src/settings/RefreshTokenRotation.js index 0cef742..f40b6e1 100644 --- a/src/settings/RefreshTokenRotation.js +++ b/src/settings/RefreshTokenRotation.js @@ -4,23 +4,33 @@ import { useCallback, useEffect, useState } from 'react'; import { Field, Form } from 'react-final-form'; import { FormattedMessage } from 'react-intl'; -import { getTokenExpiry, RTR_CONSTANTS } from '@folio/stripes/core'; import { Button, LoadingPane, Pane, PaneHeader, TextField } from '@folio/stripes/components'; +import { getTokenExpiry, RTR_CONSTANTS } from '@folio/stripes/core'; /** * manipulate AT/RT expiration dates in storage in order to test RTR. - * @returns */ const RefreshTokenRotation = ({ stripes }) => { const [isLoading, setIsLoading] = useState(true); - const [tokenExpiration, setTokenExpiration] = useState({}); + const [tokenExpiration, setTokenExpiration] = useState({ atExpires: -1, rtExpires: -1 }); useEffect(() => { - setIsLoading(true); - getTokenExpiry().then((te) => { - setTokenExpiration(te ?? { atExpires: -1, rtExpires: -1 }); - setIsLoading(false); - }); + const callback = () => { + setIsLoading(true); + getTokenExpiry().then((te) => { + setTokenExpiration({ + atExpires: te.atExpires ?? te.accessTokenExpiration ?? -1, + rtExpires: te.rtExpires ?? te.refreshTokenExpiration ?? -1, + }); + setIsLoading(false); + }); + }; + + callback(); + + window.addEventListener(RTR_CONSTANTS.RTR_SUCCESS_EVENT, callback); + + return () => window.removeEventListener(RTR_CONSTANTS.RTR_SUCCESS_EVENT, callback); }, []); /** From ff5cfc02c3c4a10393699042386a1663942db2b1 Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Thu, 10 Oct 2024 16:22:27 -0400 Subject: [PATCH 6/8] upgrade stripes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0840a9..a68514e 100644 --- a/package.json +++ b/package.json @@ -223,7 +223,7 @@ "@babel/core": "^7.17.10", "@babel/eslint-parser": "^7.17.0", "@folio/eslint-config-stripes": "^7.0.0", - "@folio/stripes": "^9.1.0", + "@folio/stripes": "^9.2.0", "@folio/stripes-cli": "^3.0.0", "@formatjs/cli": "^6.1.3", "eslint": "^7.32.0", From 616a4ce3ce449ff19a91ac1cf65759328cff129a Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Thu, 10 Oct 2024 16:50:43 -0400 Subject: [PATCH 7/8] [UID-159] pull back scope --- src/settings/RefreshTokenRotation.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/settings/RefreshTokenRotation.js b/src/settings/RefreshTokenRotation.js index f40b6e1..b0ed9f3 100644 --- a/src/settings/RefreshTokenRotation.js +++ b/src/settings/RefreshTokenRotation.js @@ -1,16 +1,14 @@ -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 { Button, LoadingPane, Pane, PaneHeader, TextField } from '@folio/stripes/components'; +import { Button, LoadingPane, Pane, PaneHeader } from '@folio/stripes/components'; import { getTokenExpiry, RTR_CONSTANTS } from '@folio/stripes/core'; /** * manipulate AT/RT expiration dates in storage in order to test RTR. */ -const RefreshTokenRotation = ({ stripes }) => { +const RefreshTokenRotation = () => { const [isLoading, setIsLoading] = useState(true); const [tokenExpiration, setTokenExpiration] = useState({ atExpires: -1, rtExpires: -1 }); @@ -46,7 +44,7 @@ const RefreshTokenRotation = ({ stripes }) => { * saveRtrConfig * update stripes.config.rtr from form */ - const saveRtrConfig = useCallback( + /* const saveRtrConfig = useCallback( (values) => { merge(stripes.config.rtr, { idleSessionTTL: values.idleSessionTTL, @@ -59,7 +57,7 @@ const RefreshTokenRotation = ({ stripes }) => { forceRefresh(); }, [stripes, forceRefresh], - ); + ); */ if (!isLoading) { return ( @@ -87,7 +85,7 @@ const RefreshTokenRotation = ({ stripes }) => { -
{
)} - + */}
); From d6601d6e44c2a3e07638ff161233109f1d95c707 Mon Sep 17 00:00:00 2001 From: Noah Overcash Date: Thu, 10 Oct 2024 17:10:32 -0400 Subject: [PATCH 8/8] [UID-163] Expose configuration for RTR properties --- src/settings/RefreshTokenRotation.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/settings/RefreshTokenRotation.js b/src/settings/RefreshTokenRotation.js index 555af52..f40b6e1 100644 --- a/src/settings/RefreshTokenRotation.js +++ b/src/settings/RefreshTokenRotation.js @@ -1,14 +1,16 @@ +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 { Button, LoadingPane, Pane, PaneHeader, TextField } from '@folio/stripes/components'; import { getTokenExpiry, RTR_CONSTANTS } from '@folio/stripes/core'; -import { Button, LoadingPane, Pane, PaneHeader } from '@folio/stripes/components'; /** * manipulate AT/RT expiration dates in storage in order to test RTR. */ -const RefreshTokenRotation = () => { +const RefreshTokenRotation = ({ stripes }) => { const [isLoading, setIsLoading] = useState(true); const [tokenExpiration, setTokenExpiration] = useState({ atExpires: -1, rtExpires: -1 }); @@ -44,7 +46,7 @@ const RefreshTokenRotation = () => { * saveRtrConfig * update stripes.config.rtr from form */ - /* const saveRtrConfig = useCallback( + const saveRtrConfig = useCallback( (values) => { merge(stripes.config.rtr, { idleSessionTTL: values.idleSessionTTL, @@ -57,7 +59,7 @@ const RefreshTokenRotation = () => { forceRefresh(); }, [stripes, forceRefresh], - ); */ + ); if (!isLoading) { return ( @@ -85,7 +87,7 @@ const RefreshTokenRotation = () => { - {/*
{
)} - */} + );