@oxyhq/core 5.2.1 → 5.4.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.
Files changed (61) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/CrossDomainAuth.js +32 -42
  3. package/dist/cjs/index.js +24 -2
  4. package/dist/cjs/mixins/OxyServices.sso.js +36 -0
  5. package/dist/cjs/mixins/OxyServices.user.js +50 -0
  6. package/dist/cjs/server/index.js +13 -1
  7. package/dist/cjs/session/SessionClient.js +152 -0
  8. package/dist/cjs/session/createSessionClient.js +26 -0
  9. package/dist/cjs/session/projectSessionState.js +75 -0
  10. package/dist/cjs/session/sessionClientHost.js +30 -0
  11. package/dist/cjs/session/socketLoader.js +55 -0
  12. package/dist/cjs/utils/ssoBounce.js +9 -9
  13. package/dist/cjs/utils/ssoEstablish.js +110 -0
  14. package/dist/esm/.tsbuildinfo +1 -1
  15. package/dist/esm/CrossDomainAuth.js +32 -42
  16. package/dist/esm/index.js +14 -0
  17. package/dist/esm/mixins/OxyServices.sso.js +36 -0
  18. package/dist/esm/mixins/OxyServices.user.js +50 -0
  19. package/dist/esm/server/index.js +10 -0
  20. package/dist/esm/session/SessionClient.js +148 -0
  21. package/dist/esm/session/createSessionClient.js +23 -0
  22. package/dist/esm/session/projectSessionState.js +69 -0
  23. package/dist/esm/session/sessionClientHost.js +27 -0
  24. package/dist/esm/session/socketLoader.js +19 -0
  25. package/dist/esm/utils/ssoBounce.js +9 -9
  26. package/dist/esm/utils/ssoEstablish.js +107 -0
  27. package/dist/types/.tsbuildinfo +1 -1
  28. package/dist/types/CrossDomainAuth.d.ts +33 -13
  29. package/dist/types/index.d.ts +7 -0
  30. package/dist/types/mixins/OxyServices.sso.d.ts +24 -0
  31. package/dist/types/mixins/OxyServices.user.d.ts +30 -0
  32. package/dist/types/server/index.d.ts +2 -0
  33. package/dist/types/session/SessionClient.d.ts +55 -0
  34. package/dist/types/session/createSessionClient.d.ts +23 -0
  35. package/dist/types/session/projectSessionState.d.ts +43 -0
  36. package/dist/types/session/sessionClientHost.d.ts +18 -0
  37. package/dist/types/session/socketLoader.d.ts +9 -0
  38. package/dist/types/utils/ssoBounce.d.ts +9 -9
  39. package/dist/types/utils/ssoEstablish.d.ts +85 -0
  40. package/package.json +1 -1
  41. package/src/CrossDomainAuth.ts +33 -44
  42. package/src/__tests__/crossDomainAuth.test.ts +33 -16
  43. package/src/index.ts +24 -0
  44. package/src/mixins/OxyServices.sso.ts +55 -0
  45. package/src/mixins/OxyServices.user.ts +54 -0
  46. package/src/mixins/__tests__/sso.test.ts +41 -0
  47. package/src/server/index.ts +12 -0
  48. package/src/session/SessionClient.ts +187 -0
  49. package/src/session/__tests__/SessionClient.diagnostics.test.ts +90 -0
  50. package/src/session/__tests__/SessionClient.httpIntegration.test.ts +65 -0
  51. package/src/session/__tests__/SessionClient.rest.test.ts +80 -0
  52. package/src/session/__tests__/SessionClient.socket.test.ts +133 -0
  53. package/src/session/__tests__/SessionClient.state.test.ts +64 -0
  54. package/src/session/__tests__/sessionIntegration.test.ts +202 -0
  55. package/src/session/createSessionClient.ts +31 -0
  56. package/src/session/projectSessionState.ts +83 -0
  57. package/src/session/sessionClientHost.ts +32 -0
  58. package/src/session/socketLoader.ts +28 -0
  59. package/src/utils/__tests__/ssoEstablish.test.ts +204 -0
  60. package/src/utils/ssoBounce.ts +9 -9
  61. package/src/utils/ssoEstablish.ts +174 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Post-claim durable-session establish hop (web device-flow / "Sign in with
3
+ * Oxy" QR).
4
+ *
5
+ * A WEB device-flow claim (`claimSessionByToken`) plants only IN-MEMORY tokens:
6
+ * unlike a redirect/FedCM/silent sign-in, it never causes the IdP to plant a
7
+ * `fedcm_session` cookie. So a reload has nothing to restore from — the
8
+ * silent-iframe and `/sso` paths find no IdP session and the session is lost.
9
+ *
10
+ * This primitive closes that gap. AFTER the claim has committed and the session
11
+ * has been durably persisted, it performs ONE top-level establish hop through
12
+ * the RP's own per-apex IdP host (`auth.<rp-apex>`), reusing the EXISTING
13
+ * `/sso/establish` endpoint: the server mints a short-lived, host+audience-bound
14
+ * establish-token and returns a fully-formed establish URL; navigating to it
15
+ * plants the durable first-party `fedcm_session` cookie and bounces back to the
16
+ * RP callback with an opaque code the standard `sso-return` cold-boot step
17
+ * exchanges.
18
+ *
19
+ * It reuses the SAME per-origin `sessionStorage` bounce contract
20
+ * (`ssoStateKey` / `ssoGuardKey` / `ssoDestKey`) that {@link buildSsoBounceUrl}
21
+ * primes for the terminal `/sso` bounce, so the post-bounce `sso-return` step
22
+ * (`consumeSsoReturn`) validates the CSRF `state`, exchanges the code, and
23
+ * restores the user's real destination with no extra wiring.
24
+ *
25
+ * Contract:
26
+ * - WEB only — off-web / native it is a no-op returning `false`.
27
+ * - NEVER fires while sitting on the central IdP origin (that would loop the
28
+ * IdP against itself).
29
+ * - Bounce state is persisted ONLY after the establish-URL request succeeds, so
30
+ * a failed request leaves no stale state behind.
31
+ * - SINGLE attempt: the caller invokes this exactly once per successful claim.
32
+ * On ANY failure it does NOT navigate and returns `false`, leaving the
33
+ * committed in-memory session exactly as-is (the user is no worse off than
34
+ * before this hop existed).
35
+ * - Total: never throws. Failures are reported via {@link deps.onError} only.
36
+ */
37
+ import { ssoStateKey, ssoGuardKey, ssoDestKey, ssoNavigate, isCentralIdPOrigin, } from './ssoBounce.js';
38
+ import { generateSsoState } from '../mixins/OxyServices.sso.js';
39
+ /**
40
+ * Perform the post-claim establish hop. Returns `true` when a navigation to the
41
+ * establish URL was initiated (the document is being torn down and replaced by
42
+ * the IdP), `false` on every no-op / failure path.
43
+ *
44
+ * @param client - The exchange surface (`oxyServices.requestSsoEstablishUrl`).
45
+ * @param deps - Injectable web seams; see {@link EstablishAfterClaimDeps}.
46
+ */
47
+ export async function establishIdpSessionAfterClaim(client, deps = {}) {
48
+ const isWeb = deps.isWeb ??
49
+ (() => typeof window !== 'undefined' &&
50
+ typeof window.sessionStorage !== 'undefined');
51
+ if (!isWeb()) {
52
+ return false;
53
+ }
54
+ const storage = deps.storage ?? window.sessionStorage;
55
+ const location = deps.location ?? window.location;
56
+ const navigate = deps.navigate ?? ssoNavigate;
57
+ const generateState = deps.generateState ?? generateSsoState;
58
+ const now = deps.now ?? (() => Date.now());
59
+ const origin = location.origin;
60
+ // Never establish while sitting on the central IdP itself — that would bounce
61
+ // the IdP against itself. (The device-flow claim can only happen on an RP.)
62
+ if (isCentralIdPOrigin(origin)) {
63
+ return false;
64
+ }
65
+ const state = generateState();
66
+ let establishUrl;
67
+ try {
68
+ const result = await client.requestSsoEstablishUrl(origin, state);
69
+ if (!result ||
70
+ typeof result.establishUrl !== 'string' ||
71
+ result.establishUrl.length === 0) {
72
+ return false;
73
+ }
74
+ establishUrl = result.establishUrl;
75
+ // Defense-in-depth before a TOP-LEVEL navigation: the establish URL is
76
+ // server-formed, but never follow anything that is not a well-formed https
77
+ // `/sso/establish` on an `auth.` host. A malformed or unexpected URL aborts
78
+ // silently (no navigation, no state persisted) — same total/no-throw
79
+ // contract — rather than navigating the document somewhere unintended.
80
+ let parsedEstablishUrl;
81
+ try {
82
+ parsedEstablishUrl = new URL(establishUrl);
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ if (parsedEstablishUrl.protocol !== 'https:' ||
88
+ parsedEstablishUrl.pathname !== '/sso/establish' ||
89
+ !parsedEstablishUrl.hostname.toLowerCase().startsWith('auth.')) {
90
+ return false;
91
+ }
92
+ // Persist the bounce state ONLY now that the request has succeeded and the
93
+ // URL is validated — so a failed/rejected request never leaves stale state
94
+ // behind. This is the exact contract the terminal `/sso` bounce primes via
95
+ // `buildSsoBounceUrl`, so the post-bounce `sso-return` step (`consumeSsoReturn`)
96
+ // validates `state`, exchanges the returned code, and restores the dest.
97
+ storage.setItem(ssoStateKey(origin), state);
98
+ storage.setItem(ssoGuardKey(origin), String(now()));
99
+ storage.setItem(ssoDestKey(origin), location.href);
100
+ }
101
+ catch (error) {
102
+ deps.onError?.(error);
103
+ return false;
104
+ }
105
+ navigate(establishUrl);
106
+ return true;
107
+ }