@oxyhq/core 3.14.0 → 3.16.0
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 +3 -1
- package/dist/cjs/mixins/OxyServices.applications.js +43 -0
- package/dist/cjs/mixins/OxyServices.assets.js +14 -35
- package/dist/cjs/mixins/OxyServices.auth.js +12 -1
- package/dist/cjs/mixins/OxyServices.links.js +68 -0
- package/dist/cjs/mixins/OxyServices.nodes.js +175 -0
- package/dist/cjs/mixins/index.js +8 -0
- package/dist/cjs/utils/ssoBounce.js +48 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/mixins/OxyServices.applications.js +43 -0
- package/dist/esm/mixins/OxyServices.assets.js +14 -35
- package/dist/esm/mixins/OxyServices.auth.js +12 -1
- package/dist/esm/mixins/OxyServices.links.js +65 -0
- package/dist/esm/mixins/OxyServices.nodes.js +172 -0
- package/dist/esm/mixins/index.js +8 -0
- package/dist/esm/utils/ssoBounce.js +46 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/mixins/OxyServices.applications.d.ts +51 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +8 -29
- package/dist/types/mixins/OxyServices.auth.d.ts +8 -0
- package/dist/types/mixins/OxyServices.links.d.ts +102 -0
- package/dist/types/mixins/OxyServices.nodes.d.ts +242 -0
- package/dist/types/mixins/index.d.ts +3 -1
- package/dist/types/utils/ssoBounce.d.ts +61 -0
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/mixins/OxyServices.applications.ts +79 -0
- package/src/mixins/OxyServices.assets.ts +14 -44
- package/src/mixins/OxyServices.auth.ts +35 -1
- package/src/mixins/OxyServices.links.ts +103 -0
- package/src/mixins/OxyServices.nodes.ts +348 -0
- package/src/mixins/__tests__/OxyServices.links.test.ts +154 -0
- package/src/mixins/__tests__/OxyServices.nodes.test.ts +341 -0
- package/src/mixins/__tests__/commonsSignIn.test.ts +41 -16
- package/src/mixins/__tests__/connectedApps.test.ts +123 -0
- package/src/mixins/__tests__/getFileDownloadUrl.test.ts +10 -24
- package/src/mixins/index.ts +10 -0
- package/src/utils/__tests__/ssoBounce.test.ts +28 -0
- package/src/utils/ssoBounce.ts +69 -0
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
ssoDestKey,
|
|
16
16
|
ssoNoSessionKey,
|
|
17
17
|
ssoAttemptedKey,
|
|
18
|
+
ssoPriorSessionKey,
|
|
18
19
|
buildSsoBounceUrl,
|
|
19
20
|
isCentralIdPOrigin,
|
|
20
21
|
guardActive,
|
|
22
|
+
allowSsoBounce,
|
|
21
23
|
} from '../ssoBounce';
|
|
22
24
|
import { CENTRAL_AUTH_URL } from '../authWebUrl';
|
|
23
25
|
|
|
@@ -37,10 +39,36 @@ describe('per-origin key builders', () => {
|
|
|
37
39
|
expect(ssoDestKey(origin)).toBe('oxy_sso_dest:https://mention.earth');
|
|
38
40
|
expect(ssoNoSessionKey(origin)).toBe('oxy_sso_no_session:https://mention.earth');
|
|
39
41
|
expect(ssoAttemptedKey(origin)).toBe('oxy_sso_attempted:https://mention.earth');
|
|
42
|
+
expect(ssoPriorSessionKey(origin)).toBe('oxy_sso_prior_session:https://mention.earth');
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
it('namespaces keys per origin so two RPs never collide', () => {
|
|
43
46
|
expect(ssoStateKey('https://a.test')).not.toBe(ssoStateKey('https://b.test'));
|
|
47
|
+
expect(ssoPriorSessionKey('https://a.test')).not.toBe(ssoPriorSessionKey('https://b.test'));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('allowSsoBounce (smart returning-visitor gate)', () => {
|
|
52
|
+
it('ALLOWS a returning visitor (prior-session hint) with no local session', () => {
|
|
53
|
+
// The core of fix B: a returning user whose local session has expired still
|
|
54
|
+
// gets ONE establish bounce so a central-only cross-domain session recovers.
|
|
55
|
+
expect(
|
|
56
|
+
allowSsoBounce({ hasPriorSession: true, hasLocalSession: false }),
|
|
57
|
+
).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('SUPPRESSES a truly first-time anonymous visitor (no hint, no local session)', () => {
|
|
61
|
+
// The smart default (the ONLY behaviour): a first-time visitor browses
|
|
62
|
+
// anonymously instead of being force-redirected to the central IdP.
|
|
63
|
+
expect(
|
|
64
|
+
allowSsoBounce({ hasPriorSession: false, hasLocalSession: false }),
|
|
65
|
+
).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('ALLOWS when a local session was recovered this boot (spec fidelity)', () => {
|
|
69
|
+
expect(
|
|
70
|
+
allowSsoBounce({ hasPriorSession: false, hasLocalSession: true }),
|
|
71
|
+
).toBe(true);
|
|
44
72
|
});
|
|
45
73
|
});
|
|
46
74
|
|
package/src/utils/ssoBounce.ts
CHANGED
|
@@ -68,6 +68,7 @@ const DEST_KEY_PREFIX = 'oxy_sso_dest:';
|
|
|
68
68
|
const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
|
|
69
69
|
const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
|
|
70
70
|
const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
|
|
71
|
+
const PRIOR_SESSION_KEY_PREFIX = 'oxy_sso_prior_session:';
|
|
71
72
|
|
|
72
73
|
/** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
|
|
73
74
|
export function ssoStateKey(origin: string): string {
|
|
@@ -106,6 +107,24 @@ export function ssoAttemptedKey(origin: string): string {
|
|
|
106
107
|
return `${ATTEMPTED_KEY_PREFIX}${origin}`;
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Per-origin DURABLE "this device/origin has had a signed-in Oxy session
|
|
112
|
+
* before" hint.
|
|
113
|
+
*
|
|
114
|
+
* Unlike every other key in this module — which lives in per-tab
|
|
115
|
+
* `sessionStorage` — this hint is written to DURABLE storage (web
|
|
116
|
+
* `localStorage`; the services provider uses its own `storageKeyPrefix`-scoped
|
|
117
|
+
* key in `@oxyhq/services`). It is set whenever a session is established or
|
|
118
|
+
* restored and survives a session expiring; it is cleared ONLY on an explicit
|
|
119
|
+
* full sign-out. It exists purely to drive {@link allowSsoBounce}: a returning
|
|
120
|
+
* visitor (hint present) whose local session has lapsed still gets ONE terminal
|
|
121
|
+
* `/sso` establish bounce to recover a session that lives only at the central
|
|
122
|
+
* IdP, while a truly first-time anonymous visitor is never force-bounced.
|
|
123
|
+
*/
|
|
124
|
+
export function ssoPriorSessionKey(origin: string): string {
|
|
125
|
+
return `${PRIOR_SESSION_KEY_PREFIX}${origin}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
109
128
|
/**
|
|
110
129
|
* Per-origin marker written by the pre-hydration callback bootstrap.
|
|
111
130
|
*
|
|
@@ -247,3 +266,53 @@ export function guardActive(
|
|
|
247
266
|
}
|
|
248
267
|
return now - ts < SSO_GUARD_TTL_MS;
|
|
249
268
|
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Inputs to the smart {@link allowSsoBounce} gate.
|
|
272
|
+
*/
|
|
273
|
+
export interface SsoBounceGate {
|
|
274
|
+
/**
|
|
275
|
+
* Whether this device/origin has had a signed-in Oxy session before (the
|
|
276
|
+
* durable {@link ssoPriorSessionKey} hint). Set whenever a session is
|
|
277
|
+
* established or restored; survives session expiry; cleared only on explicit
|
|
278
|
+
* full sign-out. `true` ⇒ a returning visitor.
|
|
279
|
+
*/
|
|
280
|
+
readonly hasPriorSession: boolean;
|
|
281
|
+
/**
|
|
282
|
+
* Whether a local/stored session was recovered earlier this cold boot. At the
|
|
283
|
+
* terminal bounce gate this is effectively always `false` (an earlier step
|
|
284
|
+
* would have won and short-circuited), but it is part of the contract — "no
|
|
285
|
+
* prior hint AND no local session" — so it is passed explicitly for fidelity
|
|
286
|
+
* and robustness.
|
|
287
|
+
*/
|
|
288
|
+
readonly hasLocalSession: boolean;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Decide whether the terminal `/sso` establish-bounce is ALLOWED for this
|
|
293
|
+
* visitor (the smart `enabled` gate for the `sso-bounce` cold-boot step).
|
|
294
|
+
*
|
|
295
|
+
* The terminal bounce is the ONLY cold-boot step that can recover a session
|
|
296
|
+
* that lives SOLELY at the central IdP — the cross-apex Relying-Party case
|
|
297
|
+
* (e.g. `mention.earth`, a different apex from `oxy.so`) whose device-local
|
|
298
|
+
* session has expired and whose `Domain=oxy.so` refresh cookie never reaches
|
|
299
|
+
* `api.<apex>`. It is also what plants the first-party per-apex `fedcm_session`
|
|
300
|
+
* cookie that the EARLIER `silent-iframe` step later relies on. So it must fire
|
|
301
|
+
* for a RETURNING user, yet it must NOT force a truly first-time anonymous
|
|
302
|
+
* visitor off to the IdP.
|
|
303
|
+
*
|
|
304
|
+
* - ALLOW when there is a prior-signed-in hint OR a local session was
|
|
305
|
+
* recovered this boot (a returning user) — so a central-only cross-domain
|
|
306
|
+
* session recovers via ONE bounce, after which the per-apex cookie is
|
|
307
|
+
* planted and subsequent loads restore silently with no bounce.
|
|
308
|
+
* - else (no hint, no local session) SUPPRESS — a first-time anonymous
|
|
309
|
+
* visitor browses without a forced redirect.
|
|
310
|
+
*
|
|
311
|
+
* This is the smart DEFAULT and the ONLY behaviour: apps never configure it.
|
|
312
|
+
* It is also the GATE DECISION ONLY — callers still apply the per-tab loop
|
|
313
|
+
* guards (`ssoAttemptedKey`, `ssoNoSessionKey`, {@link guardActive}) so an
|
|
314
|
+
* allowed bounce still fires at most once per cold boot.
|
|
315
|
+
*/
|
|
316
|
+
export function allowSsoBounce(gate: SsoBounceGate): boolean {
|
|
317
|
+
return gate.hasPriorSession || gate.hasLocalSession;
|
|
318
|
+
}
|