-
Notifications
You must be signed in to change notification settings - Fork 26
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-869 do not store /logout as a "return-to" URL #1509
Closed
Closed
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There's a lot going on here, but fundamentally the changes are split into two main categories: * route authentication requests to/from keycloak * handle discovery dynamically via an API request AFTER authentication instead of reading a static module list from `stripes.config.js`
* Update URL redirect to allow back button support * Clean up unused and duplicate code * lint * Add URL param to indicate Consortium --------- Co-authored-by: Zak Burke <zburke@ebsco.com>
* handle legacy discovery via Okapi APIs * handle legacy logout via internal redirect to `/` * handle legacy version display on `/settings/about` There is not really as much work here as it appears. All the new components were split out of `About` in order to allow sub-sections to be reused with both application-based and module-based discovery information. Likewise, `loginServices.js` and `discoveryServices.js` were modestly refactored to handled both APIs. And there are Jest/RTL tests to replace the BTOG test that could not be easily updated to handle the new APIs since its `stripes-config` stub is part of `@folio/stripes-cli` instead of being declared locally. Refs STCOR-773
…1399) When the `users-keycloak` interface is available, use the endpoints it provides in place of the legacy endpoints. Refs STCOR-795, UIU-3031
Move auth tokens into HTTP-only cookies and implement refresh token rotation (STCOR-671) by overriding global.fetch and global.XMLHttpRequest, disabling login when cookies are disabled (STCOR-762). This functionality is implemented behind an opt-in feature-flag (STCOR-763). Okapi and Keycloak do not handle the same situations in the same ways. Changes from the original implementation in PR #1376: * When a token is missing: * Okapi sends a 400 `text/plain` response * Keycloak sends a 401 `application/json` response * Keycloak authentication includes the extra step of exchanging the OTP for the AT/RT and that request needs the `credentials` and `mode` options * Some `loginServices` functions now retrieve the host the access from the `stripes-config` import instead of a function argument * always permit `/authn/token` requests to go through Refs STCOR-796, STCOR-671 (cherry picked from commit 0361353)
* STCOR-803 Add config option for logout mode * Lint fix
…component. (#1411) (#1422) * move async localforage.clear to afterEach * remove BTOG sso login tests, add sso login jest tests * Update CHANGELOG.md * move describe block comments to it blocks.. remove describe blocks (cherry picked from commit 79c76c4) Co-authored-by: John Coburn <jcoburn@ebsco.com>
If the response from `/auth/token?code=...` is OK, parse it to immediately store the AT/RT expiration values (or use a near-future date if values are not provided). Stripes must expect the RT to be valid for any future API call to succeed; otherwise, it will assume the RT has expired resulting in a race condition with the RTR handler dispatching an RTR_ERROR_EVENT but discovery succeeding and re-rendering. This would result in the API call to `.../_self` issued by `requestUserWithPerms` being swallowed and `stripes.user` being populated with an empty object, causing all kinds of problems down the line for any code that leveraged it. Refs STCOR-811
) Include the `X-Okapi-Tenant` header in `/authn/logout` requests, and clear `localStorage` settings as well. `X-Okapi-Tenant` is required for requests to be properly routed; if this request failed, the browser session would be destroyed by the keycloak session would remain active, a security risk. Clearing `localStorage.tenant` is necessary to prevent an incorrect value from being cached and inadvertently reused on subsequent login requests. Refs STCOR-812
The shape of the permissions object differs between responses from calls to `login` and calls to `_self`. This is not awesome. We didn't notice this glitch prior to implementing keycloak because when resuming an existing session (i.e. when calling `_self`), permissions are set as the union of permissions in storage (i.e. stored by a call to `login`) and those from the call to `_self`. We just never noticed that the latter was always empty. With keycloak handling authentication, however, the _only_ permissions we ever receive are in the response from `_self`, so we noticed this immediately. Refs STCOR-813
Remove references to the `stripes.config.js::config` values `tenantManagerUrl` and `applicationManagerUrl`. These were present in early drafts of this work but have since been deprecated and therefore must be removed from code as well. Refs STCOR-810
…page (#1426) * STCOR-803 Add config option for logout mode * Lint fix * Revert "STCOR-803 Add config option for logout mode" This reverts commit b9d2604. * STCOR-803 Simplify logout workflow to bypass keycloak confirmation page. * STCOR-803 PR comments * Revert "STCOR-803 PR comments" This reverts commit 037b6a2. * STCOR-803 Restore console log
Remove references to the `stripes.config.js::config` values `tenantManagerUrl` and `applicationManagerUrl`. These were present in early drafts of the new discovery work but have since been deprecated and therefore must be removed from code as well. Replaces #1418, which did this work incorrectly (referring to `okapi.[...]` instead of `config.[...]`). Refs STCOR-810
* STCOR-816 only fetch /saml/check when login-saml is present When restoring an existing session, after discovery, do not fetch from `/saml/check` unless the `login-saml` interface (indicating SSO/SAML is available). The 404 clutters the log. Refs STCOR-816 * Missing semicolon --------- Co-authored-by: Ryan Berger <rberger@ebsco.com>
* STCOR-776 show "Keep working?" prompt when session ages The main feature here is to track the RT's TTL and use it to show a "Your session is about to expire; keep working?" prompt so the user can fire off RTR in order to keep the session alive. Knock-on effects include tracking such events across multiple windows, so logging out in one window immediately logs you out in others, and so a successful RTR event in one window closes any open "Still working?" prompts in others. It sounds big, and it looks big, but once you wrap your head around it isn't so bad. A couple things to note: The `loginServices::eventManager()` function provides two event-related function, `listen` and `emit` that handle single-window events (i.e. `window.dispatchEvent()`/`window.addEventListener()`) and multi-window events (i.e. BroadcastChannel.post() and BroadcastChannel.addEventListener()`). This simplifies the API for sending and receiving events. [Documentation for BroadcastChannel](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API) is pretty good and worth a look. The thing to keep in mind with single-window events is that they are sent and received in the same window, whereas BroadcastChannel events are sent in one window but received in all others. `<SessionEventContainer>` is instantiated near the top of `<RootWithIntl>`, just like `<OverlayContainer>`. It sets itself up as the listener for all session-related events, including RTR-success (which will close an open "Keep working?" prompt), RTR-failure (which will cause logout), logout (i.e. a logout event from another window), and idle session (i.e. the RT is about to expire, which will show the "Keep working?" modal). Other session-related event handlers have been removed in favor of consolidating them all in this component. The `<KeepWorkingModal>` calls RTR if the user closes it, causing the session to be extended. By default it displays 60 seconds before the session expires but this can be changed by adjusting `stripes.config.js::config.idleSessionWarningSeconds`. If the timer counts down to 0, it emits a session-expired event, causing logout. Refs STCOR-776 * test repair * tests are nice * test infrastructure cleanup * import from @folio/jest-config-stripes/testing-library to get current versions * rename `dismissible` to `_dismissible` on destructure to prevent complaints about it not being used. lint and jest are both happy! * fix prop-types in test props, which is probably a losing battle * tests for SessionEventContainer, KeepWorkingModal Externalize SessionEventContainer event handlers and call them with 1,000 arguments, but at least DI makes them testable. I don't love this, but jest really couldn't grok having an event-handler trigger a state-change. If the render and event were both triggered within a single `act()` the re-render got swallowed. If the original render was outside an `act()` then jest complained state was changing outside the `act()` function. Whaddayawant? * codesmell cleanup recommended by sonar * tyop * Lint fixes * Remove commented-out code --------- Co-authored-by: Ryan Berger <rberger@ebsco.com>
…LFetch` (#1438) * STCOR-821 Add `idName` and `limit` as passable parameters to `useChunkedCQLFetch` * Update CHANGELOG
Refs STCOR-789.
…in the path or query arguments (#1445) * STCOR-820 Add support for optionaly passing token by URL param * Remove console.log * Update CHANGELOG.md
…ssible API (#1452) * STCOR-789: add /authn/token to always-permissible list. Refs STCOR-789 Include `/authn/token` on the list of always-permissible API in order to allow OTP-for-cookie exchange on return from authentication. Without this allowance in place, stripes will get stuck in a loop bouncing between the authn-server (which believes, correctly, that the user has authenticated) and stripes (which believes, wrongly, that the user has not authenticated because its "valid AT?" check fails). The AT won't be valid until after we get to exchange the OTP for an AT by visiting `/authn/token`. --------- Co-authored-by: Ryan Berger <rberger@ebsco.com> Co-authored-by: Zak Burke <zburke@ebsco.com> (cherry picked from commit f9d82f6)
…inactivity (#1463) Track activity (e.g. clicks, keypresses, etc), and after a period of inactivity show a "Keep working?" modal with a countdown timer; when the countdown reaches zero, end the session. * Separate the RTR cycle from regular API requests. Previously, we would inspect the AT on each request to make sure it was valid, and fire RTR if the AT was getting old. This meant we had to inspect requests (to see if the AT was valid) and responses (to see if a 4xx failure was due to RTR or the API itself). Since RTR and regular API requests are now separate, we can assume the AT in an API request is valid and likewise that any error response is related to that API. Much simpler. * Show a "Keep working?" modal after a period of inactivity (default: 1 hour) with a countdown (default: 1 minute), and terminate the session if the countdown reaches 0 without the user clicking the modal's CTA. "Activity" defaults to `keydown` and `mousedown` events. Previously, an abandoned session with an expired RT showed no indication that the RT had expired until the user performed an API request, which would cause an immediate logout. * Provide a `/logout` route, making it possible to logout by directly accessing that URL. * When a session is terminated due to inactivity, redirect to `/logout-timeout`, a static route with a message explaining what happened. Activity across tabs/windows happens via BroadcastChannel and storage requests, both of which emit events _only_ to the channels where they were not fired, i.e. you won't receive BroadcastChannel event on the object where it was posted, nor a storage event in the window/tab where the value changed. * Confirming the "Keep working?" modal in any window keeps all windows alive. * Activity in any window keeps all windows alive. * Logging out in any window logs out in all windows. * The session timeout, keep-working timeout, and the events that constitute "activity" have default values but may be overridden via `stripes.config.js`: ``` config: { //... useSecureTokens: true, rtr: { // how long before an idle session is killed? default: 60m. // this value must be shorter than the RT's TTL. // must be a string parseable by ms, e.g. 60s, 10m, 1h idleSessionTTL: '10m', // how long to show the "warning, session is idle" modal? default: 1m. // this value must be shorter than the idleSessionTTL. // must be a string parseable by ms, e.g. 60s, 10m, 1h idleModalTTL: '30s', // which events constitute "activity" that prolongs a session? // default: keydown, mousedown activityEvents: ['keydown', 'mousedown', 'wheel', 'touchstart', 'scroll'], } } ``` * Turn on the logging channels `rtr` and `rtrv` (verbose) See https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API Refs STCOR-776. Replaces PR #1431, which implemented something similar but with RTR still attached to regular API requests. It ... sort of worked, but not really, and was reverted in PR #1433. (cherry picked from commit 39d1fc9)
* Always populate `stripes.config.rtr.activityEvents`; if it isn't defined at build-time via `stripes.config.js`, populate it with values from `RTR_ACTIVITY_EVENTS`. Previously, it was omitted if not defined in `stripes.config.js`, causing the KeepWorkingModal callback to dispatch an empty event, dismissing the modal but failing to actually prolong the session since no "activity" event was fired. Whoops. Related, this means we must supply `activityEvents` on `stripes` in tests of `<SessionEventContainer>` where it is rendered directly, without `<Root>` as a parent to call `configureRtr()`. * Populate `stripes.config.rtr.idleSessionTTL` and `stripes.config.rtr.idleModalTTL` from their corresponding constants instead of hard-coding magic strings in multiple places like an idiot. * Minor test clean up. We don't need to reassign `console` methods when we can just run jest with `--silent`. Refs STCOR-776 (cherry picked from commit 99b8948)
Not all authentication-related responses are created equal. If the response contains AT expiration data, i.e. it is a response from `/bl-users/login-with-expiry`, use that data to configure RTR. If the response does not contain such data, i.e. it is a response from `/bl-users/_self`, pull AT expiration data from the session and use that. If we still don't have any AT expiration data, cross your fingers and try rotating in 10 seconds. Previously, we did not ever check the session, so RTR always fired 10 seconds into a resumed, which is exactly the situation faced in e2e tests. This doesn't precisely handle the issue faced by e2e tests that stall when the rtr requests fires, but leveraging the session data should push the rtr request far enough into the future that most e2e tests will now avoid it. We should still try to understand that problem and solve it, but in the mean time this may enable us to work around it. Refs STCOR-776 (cherry picked from commit e93a5af)
We attempted to rebase onto master just after STCOR-776 merged (#1463). It didn't go smoothly but the results got pushed anyway, which made clean up tricky too. I think the changes here resolve the conflicts. One outstanding issue I am aware of is that the `/logout-timeout` redirect does not work correctly. When the session terminates, `<AuthnLogin>` redirects to keycloak no matter what. It's like the routing switch statement is falling through instead of stopping with `<LogoutTimeout>`. That's no good, but it's less no good than the current tip-of-branch, which doesn't redirect ever, making it impossible to authenticate. (cherry picked from commit a9b860d)
The `/authn/logout` request requires the `X-Okapi-Tenant` header to succeed. (cherry picked from commit eeaa34a)
The previous commit re-enabled logout by correctly passing the `x-okapi-tenant` header in the `/authn/logout` request. It turns out that if you want read the tenant from the store in a test, you have to mock the store in your test. WHO KNEW??? (cherry picked from commit 5bc64ce)
There are many small differences in how keycloak and okapi respond to authentication related requests. * permissions are structured differently in Okapi between `login` and `_self` requests and depending on whether `expandPermissions=true` is present on the request; keycloak always responds with a flattened list. * token expiration data is nested in the login-response in Okapi but is a root-level element in the `/authn/token` response from keycloak. STCOR-776, STCOR-846 (cherry picked from commit 2e162f6)
…nantOptions in stripes.config.js (#1487) * Retrieve clientId and tenant values from config.tenantOptions before login * Fix tenant gathering * Remove isSingleTenant param which is redundant * If user object not returned from local storage, then default user from /_self response * Update CHANGELOG.md * Revert PreLoginLanding which uses okapi values * Remove space * Rework flow to immediately set config to okapi for compatibility. * Lint fix * Fix unit test (cherry picked from commit e738a2f)
* Ensure okapi is being read from store after pulling from tenantOptions in AuthLogin (cherry picked from commit eed1ba5)
Stripes should render `<ModuleContainer>` either when discovery is complete or when okapi isn't present at all, i.e. when `stripes.config.js` doesn't even contain an `okapi` entry. What's most amazing about this bug is not the bug, which is a relatively simple typo, but that it didn't bite us for more than six years. BTOG init never conducted discovery, but _did_ pass an okapi object during application setup, which is another way of saying that our application didn't have anything that relied on the presence of this bug, but our test suite did. :| Ignore the "new" AuthnLogin test file; those tests were previously stashed in `RootWithIntl.test.js` for some reason and have just been relocated. Refs STCOR-864 (cherry picked from commit 6201292)
Two things happen when idle-session-timeout kicks in: 1. the redux store is updated to clear out the session 2. the URL is updated to `/logout-timeout` It sounds simple, but it gets messy when `<RootWithIntl>` re-renders when the store updates because that's where routes are defined. Previously, with event-handlers separately calling `logout()` to update the store and `history.push()` to update the URL, you could end up in an unexpected situation such as being logged-out before the URL updated to `/logout-timeout`, causing the default route-match handler to kick in and redirect to the login screen. The changes here consolidate calls to `logout()` into the components bound to `/logout` (`<Logout>`) and `/logout-timeout` (`<LogoutTimeout>`). Event handlers that previously did things like ``` return logout(...) // update redux and other storage .then(history.push(...)) // update URL ``` are now limited to updating the URL. This means directly accessing the routes `/logout` and `/logout-timeout` always terminates a session, and the logic around logout is both simpler and better contained within components whose purpose, by dint of their names, is blindingly clear. The minor changes in `<MainNav>` are just clean-up work, removing cruft that is no longer in use. Refs STCOR-865 (cherry picked from commit 8daa267)
The RTR cycle is kicked off when processing the response from an authentication-related request. `/users-keycloak/_self` was missing from the list, which meant that RTR would never kick off when a new tab was opened for an existing session. Refs STCOR-866 (cherry picked from commit f93f21d)
RTR may be implemented such that each refresh extends the session by a fixed interval, or the session-length may be fixed causing the RT TTL to gradually shrink until the session ends and the user is forced to re-authenticate. This PR implements handling for the latter scenario, showing a non-interactive "this session will expire" banner before the session expires and then redirecting to `/logout` to clear out session data. By default the warning is visible for one minute. It may be changed at build-time by setting the `stripes.config.js` value `config.rtr.fixedLengthSessionWarningTTL` to any value parseable by `ms()`, e.g. `30s`, `1m`, `1h`. Cache the current path in session storage prior to a timeout-logout, allowing the user to return directly to that page when re-authenticating. The "interesting" bits are mostly in `FFetch` where, in addition to scheduling AT rotation, there are two new `setTimer()` calls to dispatch the FLS-warning and FLS-timeout events. Handlers for these are events are located with other RTR event handlers in `SessionEventContainer`. There are corresponding reducer functions in `okapiActions`. Both it and `okapiReducer` were refactored to use constants instead of strings for their action-types. The refactor is otherwise insignificant. Refs STCOR-862 (cherry picked from commit 8b5274e)
Some of the commits being cherry-picked on this branch relied on other work that wasn't directly related to idle-session timeout or fixed-length-session timeout, and hence weren't picked onto the branch but were, in fact necessary, for this work to function correctly. * Restore the `/oidc-landing` route * Pass the correct arguments to `loadResources()` Refs STCOR-868
When a session ends due to timeout, the current location is stored in order to allow the subsequent session to begin where the previous one left off. If the "session timeout" event fires more than once**, however, this could lead to the `/logout` location being stored as the "return to" location with obvious dire consequences. There are two changes here: 1. Don't allow locations beginning with `/logout` to be stored. This fixes the symptom, not the root cause, but is still worthwhile. 2. Store the session-timeout interval ID in redux, and manage that timer via a redux action. Even though this _still_ shouldn't fire more than once, if it does, this allows us to cancel the previous timer before adding the next one. This is an attempt to fix the root cause. Refs STCOR-869
Whoops, no, all I want is the one commit. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
When a session ends due to timeout, the current location is stored in order to allow the subsequent session to begin where the previous one left off. If the "session timeout" event fires more than once**, however, this could lead to the
/logout
location being stored as the "return to" location with obvious dire consequences.There are two changes here:
/logout
to be stored. This fixes the symptom, not the root cause, but is still worthwhile.Refs STCOR-869