@oxyhq/core 2.2.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/utils/ssoBounce.js +19 -1
- package/dist/cjs/utils/ssoReturn.js +6 -2
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/ssoBounce.js +18 -1
- package/dist/esm/utils/ssoReturn.js +7 -3
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/ssoBounce.d.ts +15 -1
- package/dist/types/utils/ssoReturn.d.ts +2 -2
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/utils/__tests__/consumeSsoReturn.test.ts +30 -0
- package/src/utils/__tests__/ssoBounce.test.ts +2 -0
- package/src/utils/ssoBounce.ts +19 -1
- package/src/utils/ssoReturn.ts +7 -2
package/dist/esm/index.js
CHANGED
|
@@ -105,7 +105,7 @@ export { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX, resolveCentralAuthUrl } from './uti
|
|
|
105
105
|
export { parseSsoReturnFragment, consumeSsoReturn } from './utils/ssoReturn.js';
|
|
106
106
|
export { generateSsoState } from './mixins/OxyServices.sso.js';
|
|
107
107
|
// SSO bounce — per-origin sessionStorage keys, bounce URL builder, predicates
|
|
108
|
-
export { SSO_CALLBACK_PATH, SSO_GUARD_TTL_MS, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoNavigate, buildSsoBounceUrl, isCentralIdPOrigin, guardActive, } from './utils/ssoBounce.js';
|
|
108
|
+
export { SSO_CALLBACK_PATH, SSO_GUARD_TTL_MS, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoAttemptedKey, ssoNavigate, buildSsoBounceUrl, isCentralIdPOrigin, guardActive, } from './utils/ssoBounce.js';
|
|
109
109
|
export { runColdBoot } from './utils/coldBoot.js';
|
|
110
110
|
// ---------------------------------------------------------------------------
|
|
111
111
|
// Constants
|
|
@@ -26,11 +26,15 @@
|
|
|
26
26
|
* the session, then restores the original destination.
|
|
27
27
|
*
|
|
28
28
|
* Loop proof (logged-out): first load all steps skip → `sso-bounce` sets
|
|
29
|
-
* guard/state/dest
|
|
29
|
+
* guard/state/dest + the outcome-independent attempted-flag
|
|
30
|
+
* ({@link ssoAttemptedKey}) and navigates; the IdP (no central session) returns
|
|
30
31
|
* `#oxy_sso=none`; the callback load's `sso-return` sees `none`, sets the
|
|
31
32
|
* NO_SESSION flag ({@link ssoNoSessionKey}), and `sso-bounce` is then disabled.
|
|
32
33
|
* Exactly ONE bounce, no loop. An interrupted bounce (user hit back
|
|
33
34
|
* mid-redirect) self-heals once the {@link SSO_GUARD_TTL_MS} guard TTL lapses.
|
|
35
|
+
* The attempted-flag is the definitive, outcome-INDEPENDENT loop breaker: it is
|
|
36
|
+
* set pre-bounce so even if the return-side NO_SESSION write never lands, the
|
|
37
|
+
* bounce can never re-fire this tab after the self-heal TTL lapses.
|
|
34
38
|
*
|
|
35
39
|
* All state lives in `sessionStorage` (per tab, cleared on tab close) and is
|
|
36
40
|
* keyed per-origin so two RPs hosted in the same browser never collide. The
|
|
@@ -58,6 +62,7 @@ const STATE_KEY_PREFIX = 'oxy_sso_state:';
|
|
|
58
62
|
const GUARD_KEY_PREFIX = 'oxy_sso_guard:';
|
|
59
63
|
const DEST_KEY_PREFIX = 'oxy_sso_dest:';
|
|
60
64
|
const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
|
|
65
|
+
const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
|
|
61
66
|
/** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
|
|
62
67
|
export function ssoStateKey(origin) {
|
|
63
68
|
return `${STATE_KEY_PREFIX}${origin}`;
|
|
@@ -78,6 +83,18 @@ export function ssoDestKey(origin) {
|
|
|
78
83
|
export function ssoNoSessionKey(origin) {
|
|
79
84
|
return `${NO_SESSION_KEY_PREFIX}${origin}`;
|
|
80
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Per-origin, OUTCOME-INDEPENDENT once-guard. Set in `sessionStorage` BEFORE
|
|
88
|
+
* the terminal SSO bounce navigates. Gates the bounce so the silent
|
|
89
|
+
* cross-domain probe fires AT MOST ONCE per tab session — independent of
|
|
90
|
+
* whether the return-side NO_SESSION flag ever lands. The definitive loop
|
|
91
|
+
* breaker; survives the 30s self-heal `ssoGuardKey` TTL. Cleared only on an
|
|
92
|
+
* explicit sign-out/clear so a later cold boot (after the user signs in
|
|
93
|
+
* centrally) can probe again.
|
|
94
|
+
*/
|
|
95
|
+
export function ssoAttemptedKey(origin) {
|
|
96
|
+
return `${ATTEMPTED_KEY_PREFIX}${origin}`;
|
|
97
|
+
}
|
|
81
98
|
/**
|
|
82
99
|
* Perform the terminal top-level SSO bounce navigation.
|
|
83
100
|
*
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* an oxy_sso fragment at all (i.e. `oxy_sso` is absent or an unrecognised
|
|
21
21
|
* value), so the caller can ignore unrelated fragments without special-casing.
|
|
22
22
|
*/
|
|
23
|
-
import { SSO_CALLBACK_PATH, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, } from './ssoBounce.js';
|
|
23
|
+
import { SSO_CALLBACK_PATH, ssoStateKey, ssoGuardKey, ssoDestKey, ssoNoSessionKey, ssoAttemptedKey, } from './ssoBounce.js';
|
|
24
24
|
const VALID_KINDS = new Set(['ok', 'none', 'error']);
|
|
25
25
|
/**
|
|
26
26
|
* Parse an SSO return fragment.
|
|
@@ -86,8 +86,8 @@ export function parseSsoReturnFragment(hash) {
|
|
|
86
86
|
* or a `Referer` header even if a later step throws.
|
|
87
87
|
* - `state` must match (CSRF). A mismatch or a missing code sets the
|
|
88
88
|
* NO_SESSION flag so `sso-bounce` is disabled (no rebounce loop).
|
|
89
|
-
* - `none`/`error` outcomes set the NO_SESSION flag
|
|
90
|
-
* loop proof).
|
|
89
|
+
* - `none`/`error` outcomes set BOTH the NO_SESSION flag and the
|
|
90
|
+
* outcome-independent attempted-flag (the load2 half of the loop proof).
|
|
91
91
|
* - A throwing exchange is caught, reported via `onExchangeError`, and
|
|
92
92
|
* treated exactly like "no session" (never loops, never rethrows).
|
|
93
93
|
* - After a successful exchange landing on {@link SSO_CALLBACK_PATH}, the real
|
|
@@ -129,6 +129,10 @@ export async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
129
129
|
storage.removeItem(ssoGuardKey(origin));
|
|
130
130
|
const markNoSession = () => {
|
|
131
131
|
storage.setItem(ssoNoSessionKey(origin), '1');
|
|
132
|
+
// A return was consumed, so the probe definitively happened. Set the
|
|
133
|
+
// outcome-independent attempted-flag too so the bounce can never re-fire
|
|
134
|
+
// even if some consumer path skipped setting it pre-bounce.
|
|
135
|
+
storage.setItem(ssoAttemptedKey(origin), '1');
|
|
132
136
|
};
|
|
133
137
|
if (ret.kind === 'none' || ret.kind === 'error') {
|
|
134
138
|
// The central IdP had no session (or the bounce failed). Record it so we do
|