mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
4854377603
Two reports, one line: the automatic bounce to the provider in useLogin, which fires whenever password login is off. Both reporters are on an OIDC-only install, which is what arms it. #2126, the redirect loop. The re-entry guard sat inside the oidc_code branch, but the exchange strips oidc_code from the URL before it navigates away. Any re-run of the effect during that window - and t is a dependency, so a language change is enough, which every non-English install does on the login page - read an empty search, missed the guard, fell through to the config probe and bounced to the provider. The provider answered silently, landed back on /login?oidc_code=, and round it went. Each pass mints a valid session, which is why closing the tab and reopening lands the reporter logged in. The guard is now the first statement of the effect, so no re-run can reach the redirect. #2123, logout that logs you back in. The brake for the bounce was noRedirect in router location state, and it does not survive the trip: logout flips isAuthenticated on a sync lane while the router defers its location update, so ProtectedRoute renders once on the old route and emits <Navigate to=/login replace> with no state, overwriting the entry the logout handler had just pushed. Location state cannot cross a full document load either, and api/client.ts performs one on any 401. The fact now lives in sessionStorage, set in authStore.logout() so all seven sign-out call sites are covered, and cleared on any successful auth and on the manual SSO links. Per tab, so a fresh tab still single-signs-on - that being the point of single sign-on. Both tests were confirmed red without their fix and green with it. The first needed rewriting to do that: the obvious version held the page mounted past the window the race lives in and passed either way. Not included: RP-initiated logout at the provider. TREK clears its own session correctly, and ending the provider's is a separate decision - it needs an end_session_endpoint the provider may not advertise, a post_logout_redirect_uri registered on their side, and an answer to whether signing out of TREK should sign you out everywhere.
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
/**
|
|
* "This tab just signed out" — a fact that has to survive more than a render.
|
|
*
|
|
* On an OIDC-only install the login page bounces to the provider on its own
|
|
* (`useLogin`), which is what makes signing out impossible: the provider still
|
|
* has a live session, answers the silent authorize immediately, and the user is
|
|
* back in TREK without having asked to be (#2123).
|
|
*
|
|
* The brake for that bounce used to be `noRedirect` in react-router location
|
|
* state, and it does not survive the trip. `authStore.logout()` flips
|
|
* `isAuthenticated` on a synchronous lane while BrowserRouter defers its own
|
|
* location update, so React commits one render still on the old route;
|
|
* ProtectedRoute sees a logged-out user and emits `<Navigate to="/login"
|
|
* replace />` with no state, which overwrites the history entry the logout
|
|
* handler had just pushed. Location state also cannot cross a full document
|
|
* load, and `api/client.ts` performs one on any 401.
|
|
*
|
|
* sessionStorage is the right lifetime for it: it outlives both, and it is gone
|
|
* when the tab is. A brand-new tab should auto-SSO again — that is the point of
|
|
* single sign-on. What must not happen is auto-SSO in the tab where somebody
|
|
* just pressed "Sign out".
|
|
*
|
|
* Every accessor is wrapped: Safari in private mode throws on sessionStorage,
|
|
* and losing the brake must degrade to today's behaviour rather than to a blank
|
|
* page.
|
|
*/
|
|
const KEY = 'trek_signed_out'
|
|
|
|
/** Called from `authStore.logout()`, so all seven sign-out call sites are covered at once. */
|
|
export function markSignedOut(): void {
|
|
try {
|
|
sessionStorage.setItem(KEY, '1')
|
|
} catch {
|
|
/* private mode — the location-state brake still covers the common path */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleared the moment the user asks to come back: any successful authentication,
|
|
* and the click on the manual "Sign in with SSO" link. Without that, an
|
|
* OIDC-only user would be stranded on a login page that refuses to auto-SSO for
|
|
* the life of the tab.
|
|
*/
|
|
export function clearSignedOut(): void {
|
|
try {
|
|
sessionStorage.removeItem(KEY)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
export function wasSignedOut(): boolean {
|
|
try {
|
|
return sessionStorage.getItem(KEY) === '1'
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|