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-876] Remember requested URL path on Login (Regression bug) #1525

Closed
wants to merge 59 commits into from

Conversation

ryandberger
Copy link
Member

  • Refs STCOR-876.
  • Ensure setUnauthorizedPathToSession is called in AuthnLogin to remember requested path on login. I added a more explicit default path handler when user is unauthorized. I also switched from useEffect to useLayoutEffect in AuthnLogin so that logic is called before anything on the UI is drawn.
  • Also adding window.location.search to session storage so URL params are remembered.

usavkov-epam and others added 30 commits June 11, 2024 10:27
…#1415)

To support Single Sign-On (SSO) authorization in consortium mode,
it's necessary to explicitly pass the tenant in the request header
to load data.

(cherry picked from commit d8db8b7)
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 <[email protected]>
* 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 <[email protected]>
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
…r modules. (#1383) (#1424)

(cherry picked from commit 190d87e)

Co-authored-by: Dmytro-Melnyshyn <[email protected]>
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
This reverts commit 13b9dc4.

The conditional here checked `okapi.tenantOptions`; it must check `config.tenantOptions`.
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 <[email protected]>
* 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 <[email protected]>
…" (#1433)

Numerous bugs indicate this work was not ready for primetime:
* "Keep working?" prompt may not be dismissible
* missing translations in the "Keep working?" prompt
* console errors indicate attempts to post messages to closed BroadcastChannels

Reverts #1431

This reverts commit 6aabfc5.
…LFetch` (#1438)

* STCOR-821 Add `idName` and `limit` as passable parameters to `useChunkedCQLFetch`

* Update CHANGELOG
…in the path or query arguments (#1445)

* STCOR-820 Add support for optionaly passing token by URL param

* Remove console.log

* Update CHANGELOG.md
zburke and others added 26 commits June 11, 2024 11:21
Provide user-tenant-permissions functionality, both centralizing this
functionality and insulating other applications from needing to depend
on the permissions interface.

* `useUserTenantPermissions` provides permissions for the currently
  authenticated user in a single tenant
* `getUserTenantsPermissions` provides permissions for the currently
  authenticated user across an array of tenants

Refs STCOR-830
…#1415)

To support Single Sign-On (SSO) authorization in consortium mode,
it's necessary to explicitly pass the tenant in the request header
to load data.

(cherry picked from commit d8db8b7)
* Navigate to base URL if using Eureka since backend doesn't recognize /login
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.
The `/authn/logout` request requires the `X-Okapi-Tenant` header to
succeed.
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???
The request to `/authn/token` pulls an OTP from the query string and
exchanges it for AT/RT cookies. If, somehow, the browser already has
cookies and sends them along on this request, it causes a negative
feedback look because the OTP and the cookies are out of sync.
The old AT/RT cookies will cause the endpoint to return 4xx, which will
result in a redirect back to keycloak, which will find its (still
perfectly valid) authentication cookies, which will cause it redirect
back to stripes with a new OTP ... and the cycle repeats.

Thus, when we are exchanging an OTP, we don't want to send any cookies.
We want stripes to send the OTP and have new cookies from the response
overwrite anything that was previously stored.

Refs STCOR-853
…1480)" (#1486)

This reverts commit d6e7af8.

We don't want to _send_ old cookies, but we do want to _receive_ new
cookies. `omit` ignores both. From
https://developer.mozilla.org/en-US/docs/Web/API/fetch#credentials:

> `omit`: Tells browsers to exclude credentials from the request, and
> ignore any credentials sent back in the response (e.g., any Set-Cookie
> header).

We may still have a cookie exchange problem, but if we do, `credentials:
"omit"` won't solve it.
…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
Follow-up to the original PR (#1385, STCOR-773). There were at
least two gotchas there:
1. The attribute key in the response changed from `ui-modules` to
   `uiModules`
2. Since frontend and backend applications are stored under separate
   keys, the discovery reducer needed to grab values from both keys.

Refs STCOR-859
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
* Ensure okapi is being read from store after pulling from tenantOptions in AuthLogin
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
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
…rmissions instead of okapi permissions if roles interface is presented (#1491)

Refs STCOR-834.
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
* Add permission display names lookup table to Redux

* Sonar fixes
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
so that state is not dropped when adding permission display names. The previous code was improper Redux behavior.
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
…e expires (#1513)

* Added a small time margin to wait so that cookie is not deleted before /logout request

* Fix test and lint issue
#1524)

Without a `key` prop to distinguish the elements rendered by
`<SessionEventContainer>`, they could interact badly. In particular, if
both elements (`<KeepWorkingModal>`, `<FixedLengthSessionWarning>`) were
displayed, dismissing the former would cause the latter to remount,
thus restarting the timer and putting it out of sync with when the
session will actually end.

When React warns you about missing keys, it ain't foolin'!

Refs STCOR-874

(cherry picked from commit d4e9f1d)
…edCQLFetch` for manipulations in the context of a specific tenant (#1519)

* STCOR-873 Ensure support for the passed 'tenantId' value by 'useChunkedCQLFetch' for manipulations in the context of a specific tenant

* resolve description issues

* tests
@ryandberger ryandberger deleted the STCOR-876 branch August 23, 2024 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants