@oxyhq/services 8.3.1 → 8.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.
@@ -1,148 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * Central cross-domain SSO bounce — per-origin sessionStorage keys and small
5
- * pure predicates shared by the cold-boot `sso-return` / `sso-bounce` steps and
6
- * the bfcache `pageshow` re-evaluation.
7
- *
8
- * TRUE central SSO (Google/Meta/Clerk style) works like this for a Relying
9
- * Party (mention.earth, homiio.com, alia.onl, …) with no local session:
10
- *
11
- * 1. `sso-bounce` (terminal, once): top-level navigate to
12
- * `auth.oxy.so/sso?prompt=none&client_id=<origin>&return_to=<origin>/__oxy/sso-callback&state=<s>`.
13
- * Before navigating it records, in this origin's `sessionStorage`, the CSRF
14
- * `state`, a guard timestamp (loop breaker), and the real destination URL
15
- * to restore after the callback.
16
- * 2. The central IdP worker reads its first-party `fedcm_session`, mints a
17
- * session, stores it under an opaque single-use `code`, and 303-redirects
18
- * back to `<origin>/__oxy/sso-callback#oxy_sso=ok&code=<code>&state=<s>`
19
- * (or `#oxy_sso=none` / `#oxy_sso=error`).
20
- * 3. `sso-return` parses the fragment (`parseSsoReturnFragment` from core),
21
- * validates `state`, exchanges the `code` via `oxyServices.exchangeSsoCode`,
22
- * and commits the session — then restores the original destination.
23
- *
24
- * Loop proof (logged-out): first load all steps skip → `sso-bounce` sets
25
- * guard/state/dest and navigates; the IdP (no central session) returns
26
- * `#oxy_sso=none`; the callback load's `sso-return` sees `none`, sets the
27
- * no-session flag, and `sso-bounce` is then disabled. Exactly ONE bounce, no
28
- * loop. An interrupted bounce (user hit back mid-redirect) self-heals once the
29
- * 30s guard TTL lapses.
30
- *
31
- * All state lives in `sessionStorage` (per tab, cleared on tab close) and is
32
- * keyed per-origin so two RPs hosted in the same browser never collide. This
33
- * module is pure with respect to navigation: it only reads/writes
34
- * `sessionStorage` and parses URLs; it performs no redirects itself.
35
- */
36
-
37
- import { CENTRAL_AUTH_URL } from '@oxyhq/core';
38
-
39
- /**
40
- * The RP callback path the central IdP redirects back to. The SSO result is
41
- * delivered in the fragment of this URL; `sso-return` consumes it and then
42
- * restores the user's real destination.
43
- */
44
- export const SSO_CALLBACK_PATH = '/__oxy/sso-callback';
45
-
46
- /**
47
- * Self-healing TTL (ms) for the bounce guard. If a bounce is interrupted before
48
- * the callback lands (e.g. the user navigates back mid-redirect), the guard
49
- * would otherwise pin the RP signed-out forever. After this window the guard is
50
- * treated as stale and a fresh single bounce is permitted.
51
- */
52
- export const SSO_GUARD_TTL_MS = 30_000;
53
- const STATE_KEY_PREFIX = 'oxy_sso_state:';
54
- const GUARD_KEY_PREFIX = 'oxy_sso_guard:';
55
- const DEST_KEY_PREFIX = 'oxy_sso_dest:';
56
- const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
57
-
58
- /**
59
- * Perform the terminal top-level SSO bounce navigation.
60
- *
61
- * A thin wrapper over `window.location.assign(url)` so the single navigation
62
- * seam lives in one place (and stays mockable in tests, where jsdom's
63
- * `Location.assign` is a non-configurable native method). In production this is
64
- * exactly `window.location.assign` — the document is torn down and replaced by
65
- * the central IdP page. Off-browser it is a no-op (native never bounces).
66
- */
67
- export function ssoNavigate(url) {
68
- if (typeof window === 'undefined' || typeof window.location === 'undefined') {
69
- return;
70
- }
71
- window.location.assign(url);
72
- }
73
-
74
- /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
75
- export function ssoStateKey(origin) {
76
- return `${STATE_KEY_PREFIX}${origin}`;
77
- }
78
-
79
- /** Per-origin bounce guard key (a timestamp; loop breaker + self-heal TTL). */
80
- export function ssoGuardKey(origin) {
81
- return `${GUARD_KEY_PREFIX}${origin}`;
82
- }
83
-
84
- /** Per-origin destination key (the real URL to restore after the callback). */
85
- export function ssoDestKey(origin) {
86
- return `${DEST_KEY_PREFIX}${origin}`;
87
- }
88
-
89
- /**
90
- * Per-origin "the central IdP has no session for me" key. Set after a
91
- * `none`/`error` return (or a failed/forged exchange) so `sso-bounce` does not
92
- * fire again this tab — the definitive loop breaker.
93
- */
94
- export function ssoNoSessionKey(origin) {
95
- return `${NO_SESSION_KEY_PREFIX}${origin}`;
96
- }
97
-
98
- /**
99
- * Whether `origin` IS the central IdP origin. We must never bounce while on
100
- * `auth.oxy.so` itself (it would bounce to itself). Compared by URL origin so a
101
- * trailing-slash / path difference never defeats the guard.
102
- */
103
- export function isCentralIdPOrigin(origin) {
104
- let centralOrigin;
105
- try {
106
- centralOrigin = new URL(CENTRAL_AUTH_URL).origin;
107
- } catch {
108
- return false;
109
- }
110
- let candidateOrigin;
111
- try {
112
- candidateOrigin = new URL(origin).origin;
113
- } catch {
114
- return false;
115
- }
116
- return candidateOrigin === centralOrigin;
117
- }
118
-
119
- /**
120
- * Read the bounce guard and decide whether it is still ACTIVE.
121
- *
122
- * Active means: a guard value is present AND it parses to a finite timestamp AND
123
- * less than {@link SSO_GUARD_TTL_MS} has elapsed since it was set. An active
124
- * guard disables `sso-bounce` (a bounce is already in flight this tab). A
125
- * missing, malformed, or expired guard is NOT active, so a fresh bounce may
126
- * proceed (this is the 30s self-heal for an interrupted bounce).
127
- *
128
- * @param storage - The session storage to read (injected for testability).
129
- * @param origin - The page origin whose guard to evaluate.
130
- * @param now - Current epoch ms (injected for deterministic tests).
131
- */
132
- export function guardActive(storage, origin, now) {
133
- let raw;
134
- try {
135
- raw = storage.getItem(ssoGuardKey(origin));
136
- } catch {
137
- return false;
138
- }
139
- if (raw === null || raw.length === 0) {
140
- return false;
141
- }
142
- const stamp = Number(raw);
143
- if (!Number.isFinite(stamp)) {
144
- return false;
145
- }
146
- return now - stamp < SSO_GUARD_TTL_MS;
147
- }
148
- //# sourceMappingURL=ssoBounce.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["CENTRAL_AUTH_URL","SSO_CALLBACK_PATH","SSO_GUARD_TTL_MS","STATE_KEY_PREFIX","GUARD_KEY_PREFIX","DEST_KEY_PREFIX","NO_SESSION_KEY_PREFIX","ssoNavigate","url","window","location","assign","ssoStateKey","origin","ssoGuardKey","ssoDestKey","ssoNoSessionKey","isCentralIdPOrigin","centralOrigin","URL","candidateOrigin","guardActive","storage","now","raw","getItem","length","stamp","Number","isFinite"],"sourceRoot":"../../../../src","sources":["ui/utils/ssoBounce.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,gBAAgB,QAAQ,aAAa;;AAE9C;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,iBAAiB,GAAG,qBAAqB;;AAEtD;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,gBAAgB,GAAG,MAAM;AAEtC,MAAMC,gBAAgB,GAAG,gBAAgB;AACzC,MAAMC,gBAAgB,GAAG,gBAAgB;AACzC,MAAMC,eAAe,GAAG,eAAe;AACvC,MAAMC,qBAAqB,GAAG,qBAAqB;;AAEnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,WAAWA,CAACC,GAAW,EAAQ;EAC7C,IAAI,OAAOC,MAAM,KAAK,WAAW,IAAI,OAAOA,MAAM,CAACC,QAAQ,KAAK,WAAW,EAAE;IAC3E;EACF;EACAD,MAAM,CAACC,QAAQ,CAACC,MAAM,CAACH,GAAG,CAAC;AAC7B;;AAEA;AACA,OAAO,SAASI,WAAWA,CAACC,MAAc,EAAU;EAClD,OAAO,GAAGV,gBAAgB,GAAGU,MAAM,EAAE;AACvC;;AAEA;AACA,OAAO,SAASC,WAAWA,CAACD,MAAc,EAAU;EAClD,OAAO,GAAGT,gBAAgB,GAAGS,MAAM,EAAE;AACvC;;AAEA;AACA,OAAO,SAASE,UAAUA,CAACF,MAAc,EAAU;EACjD,OAAO,GAAGR,eAAe,GAAGQ,MAAM,EAAE;AACtC;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,eAAeA,CAACH,MAAc,EAAU;EACtD,OAAO,GAAGP,qBAAqB,GAAGO,MAAM,EAAE;AAC5C;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,kBAAkBA,CAACJ,MAAc,EAAW;EAC1D,IAAIK,aAAqB;EACzB,IAAI;IACFA,aAAa,GAAG,IAAIC,GAAG,CAACnB,gBAAgB,CAAC,CAACa,MAAM;EAClD,CAAC,CAAC,MAAM;IACN,OAAO,KAAK;EACd;EACA,IAAIO,eAAuB;EAC3B,IAAI;IACFA,eAAe,GAAG,IAAID,GAAG,CAACN,MAAM,CAAC,CAACA,MAAM;EAC1C,CAAC,CAAC,MAAM;IACN,OAAO,KAAK;EACd;EACA,OAAOO,eAAe,KAAKF,aAAa;AAC1C;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,WAAWA,CAACC,OAAgB,EAAET,MAAc,EAAEU,GAAW,EAAW;EAClF,IAAIC,GAAkB;EACtB,IAAI;IACFA,GAAG,GAAGF,OAAO,CAACG,OAAO,CAACX,WAAW,CAACD,MAAM,CAAC,CAAC;EAC5C,CAAC,CAAC,MAAM;IACN,OAAO,KAAK;EACd;EACA,IAAIW,GAAG,KAAK,IAAI,IAAIA,GAAG,CAACE,MAAM,KAAK,CAAC,EAAE;IACpC,OAAO,KAAK;EACd;EACA,MAAMC,KAAK,GAAGC,MAAM,CAACJ,GAAG,CAAC;EACzB,IAAI,CAACI,MAAM,CAACC,QAAQ,CAACF,KAAK,CAAC,EAAE;IAC3B,OAAO,KAAK;EACd;EACA,OAAOJ,GAAG,GAAGI,KAAK,GAAGzB,gBAAgB;AACvC","ignoreList":[]}
@@ -1,89 +0,0 @@
1
- /**
2
- * Central cross-domain SSO bounce — per-origin sessionStorage keys and small
3
- * pure predicates shared by the cold-boot `sso-return` / `sso-bounce` steps and
4
- * the bfcache `pageshow` re-evaluation.
5
- *
6
- * TRUE central SSO (Google/Meta/Clerk style) works like this for a Relying
7
- * Party (mention.earth, homiio.com, alia.onl, …) with no local session:
8
- *
9
- * 1. `sso-bounce` (terminal, once): top-level navigate to
10
- * `auth.oxy.so/sso?prompt=none&client_id=<origin>&return_to=<origin>/__oxy/sso-callback&state=<s>`.
11
- * Before navigating it records, in this origin's `sessionStorage`, the CSRF
12
- * `state`, a guard timestamp (loop breaker), and the real destination URL
13
- * to restore after the callback.
14
- * 2. The central IdP worker reads its first-party `fedcm_session`, mints a
15
- * session, stores it under an opaque single-use `code`, and 303-redirects
16
- * back to `<origin>/__oxy/sso-callback#oxy_sso=ok&code=<code>&state=<s>`
17
- * (or `#oxy_sso=none` / `#oxy_sso=error`).
18
- * 3. `sso-return` parses the fragment (`parseSsoReturnFragment` from core),
19
- * validates `state`, exchanges the `code` via `oxyServices.exchangeSsoCode`,
20
- * and commits the session — then restores the original destination.
21
- *
22
- * Loop proof (logged-out): first load all steps skip → `sso-bounce` sets
23
- * guard/state/dest and navigates; the IdP (no central session) returns
24
- * `#oxy_sso=none`; the callback load's `sso-return` sees `none`, sets the
25
- * no-session flag, and `sso-bounce` is then disabled. Exactly ONE bounce, no
26
- * loop. An interrupted bounce (user hit back mid-redirect) self-heals once the
27
- * 30s guard TTL lapses.
28
- *
29
- * All state lives in `sessionStorage` (per tab, cleared on tab close) and is
30
- * keyed per-origin so two RPs hosted in the same browser never collide. This
31
- * module is pure with respect to navigation: it only reads/writes
32
- * `sessionStorage` and parses URLs; it performs no redirects itself.
33
- */
34
- /**
35
- * The RP callback path the central IdP redirects back to. The SSO result is
36
- * delivered in the fragment of this URL; `sso-return` consumes it and then
37
- * restores the user's real destination.
38
- */
39
- export declare const SSO_CALLBACK_PATH = "/__oxy/sso-callback";
40
- /**
41
- * Self-healing TTL (ms) for the bounce guard. If a bounce is interrupted before
42
- * the callback lands (e.g. the user navigates back mid-redirect), the guard
43
- * would otherwise pin the RP signed-out forever. After this window the guard is
44
- * treated as stale and a fresh single bounce is permitted.
45
- */
46
- export declare const SSO_GUARD_TTL_MS = 30000;
47
- /**
48
- * Perform the terminal top-level SSO bounce navigation.
49
- *
50
- * A thin wrapper over `window.location.assign(url)` so the single navigation
51
- * seam lives in one place (and stays mockable in tests, where jsdom's
52
- * `Location.assign` is a non-configurable native method). In production this is
53
- * exactly `window.location.assign` — the document is torn down and replaced by
54
- * the central IdP page. Off-browser it is a no-op (native never bounces).
55
- */
56
- export declare function ssoNavigate(url: string): void;
57
- /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
58
- export declare function ssoStateKey(origin: string): string;
59
- /** Per-origin bounce guard key (a timestamp; loop breaker + self-heal TTL). */
60
- export declare function ssoGuardKey(origin: string): string;
61
- /** Per-origin destination key (the real URL to restore after the callback). */
62
- export declare function ssoDestKey(origin: string): string;
63
- /**
64
- * Per-origin "the central IdP has no session for me" key. Set after a
65
- * `none`/`error` return (or a failed/forged exchange) so `sso-bounce` does not
66
- * fire again this tab — the definitive loop breaker.
67
- */
68
- export declare function ssoNoSessionKey(origin: string): string;
69
- /**
70
- * Whether `origin` IS the central IdP origin. We must never bounce while on
71
- * `auth.oxy.so` itself (it would bounce to itself). Compared by URL origin so a
72
- * trailing-slash / path difference never defeats the guard.
73
- */
74
- export declare function isCentralIdPOrigin(origin: string): boolean;
75
- /**
76
- * Read the bounce guard and decide whether it is still ACTIVE.
77
- *
78
- * Active means: a guard value is present AND it parses to a finite timestamp AND
79
- * less than {@link SSO_GUARD_TTL_MS} has elapsed since it was set. An active
80
- * guard disables `sso-bounce` (a bounce is already in flight this tab). A
81
- * missing, malformed, or expired guard is NOT active, so a fresh bounce may
82
- * proceed (this is the 30s self-heal for an interrupted bounce).
83
- *
84
- * @param storage - The session storage to read (injected for testability).
85
- * @param origin - The page origin whose guard to evaluate.
86
- * @param now - Current epoch ms (injected for deterministic tests).
87
- */
88
- export declare function guardActive(storage: Storage, origin: string, now: number): boolean;
89
- //# sourceMappingURL=ssoBounce.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ssoBounce.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/ssoBounce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAOvC;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK7C;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAc1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAelF"}
@@ -1,89 +0,0 @@
1
- /**
2
- * Central cross-domain SSO bounce — per-origin sessionStorage keys and small
3
- * pure predicates shared by the cold-boot `sso-return` / `sso-bounce` steps and
4
- * the bfcache `pageshow` re-evaluation.
5
- *
6
- * TRUE central SSO (Google/Meta/Clerk style) works like this for a Relying
7
- * Party (mention.earth, homiio.com, alia.onl, …) with no local session:
8
- *
9
- * 1. `sso-bounce` (terminal, once): top-level navigate to
10
- * `auth.oxy.so/sso?prompt=none&client_id=<origin>&return_to=<origin>/__oxy/sso-callback&state=<s>`.
11
- * Before navigating it records, in this origin's `sessionStorage`, the CSRF
12
- * `state`, a guard timestamp (loop breaker), and the real destination URL
13
- * to restore after the callback.
14
- * 2. The central IdP worker reads its first-party `fedcm_session`, mints a
15
- * session, stores it under an opaque single-use `code`, and 303-redirects
16
- * back to `<origin>/__oxy/sso-callback#oxy_sso=ok&code=<code>&state=<s>`
17
- * (or `#oxy_sso=none` / `#oxy_sso=error`).
18
- * 3. `sso-return` parses the fragment (`parseSsoReturnFragment` from core),
19
- * validates `state`, exchanges the `code` via `oxyServices.exchangeSsoCode`,
20
- * and commits the session — then restores the original destination.
21
- *
22
- * Loop proof (logged-out): first load all steps skip → `sso-bounce` sets
23
- * guard/state/dest and navigates; the IdP (no central session) returns
24
- * `#oxy_sso=none`; the callback load's `sso-return` sees `none`, sets the
25
- * no-session flag, and `sso-bounce` is then disabled. Exactly ONE bounce, no
26
- * loop. An interrupted bounce (user hit back mid-redirect) self-heals once the
27
- * 30s guard TTL lapses.
28
- *
29
- * All state lives in `sessionStorage` (per tab, cleared on tab close) and is
30
- * keyed per-origin so two RPs hosted in the same browser never collide. This
31
- * module is pure with respect to navigation: it only reads/writes
32
- * `sessionStorage` and parses URLs; it performs no redirects itself.
33
- */
34
- /**
35
- * The RP callback path the central IdP redirects back to. The SSO result is
36
- * delivered in the fragment of this URL; `sso-return` consumes it and then
37
- * restores the user's real destination.
38
- */
39
- export declare const SSO_CALLBACK_PATH = "/__oxy/sso-callback";
40
- /**
41
- * Self-healing TTL (ms) for the bounce guard. If a bounce is interrupted before
42
- * the callback lands (e.g. the user navigates back mid-redirect), the guard
43
- * would otherwise pin the RP signed-out forever. After this window the guard is
44
- * treated as stale and a fresh single bounce is permitted.
45
- */
46
- export declare const SSO_GUARD_TTL_MS = 30000;
47
- /**
48
- * Perform the terminal top-level SSO bounce navigation.
49
- *
50
- * A thin wrapper over `window.location.assign(url)` so the single navigation
51
- * seam lives in one place (and stays mockable in tests, where jsdom's
52
- * `Location.assign` is a non-configurable native method). In production this is
53
- * exactly `window.location.assign` — the document is torn down and replaced by
54
- * the central IdP page. Off-browser it is a no-op (native never bounces).
55
- */
56
- export declare function ssoNavigate(url: string): void;
57
- /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
58
- export declare function ssoStateKey(origin: string): string;
59
- /** Per-origin bounce guard key (a timestamp; loop breaker + self-heal TTL). */
60
- export declare function ssoGuardKey(origin: string): string;
61
- /** Per-origin destination key (the real URL to restore after the callback). */
62
- export declare function ssoDestKey(origin: string): string;
63
- /**
64
- * Per-origin "the central IdP has no session for me" key. Set after a
65
- * `none`/`error` return (or a failed/forged exchange) so `sso-bounce` does not
66
- * fire again this tab — the definitive loop breaker.
67
- */
68
- export declare function ssoNoSessionKey(origin: string): string;
69
- /**
70
- * Whether `origin` IS the central IdP origin. We must never bounce while on
71
- * `auth.oxy.so` itself (it would bounce to itself). Compared by URL origin so a
72
- * trailing-slash / path difference never defeats the guard.
73
- */
74
- export declare function isCentralIdPOrigin(origin: string): boolean;
75
- /**
76
- * Read the bounce guard and decide whether it is still ACTIVE.
77
- *
78
- * Active means: a guard value is present AND it parses to a finite timestamp AND
79
- * less than {@link SSO_GUARD_TTL_MS} has elapsed since it was set. An active
80
- * guard disables `sso-bounce` (a bounce is already in flight this tab). A
81
- * missing, malformed, or expired guard is NOT active, so a fresh bounce may
82
- * proceed (this is the 30s self-heal for an interrupted bounce).
83
- *
84
- * @param storage - The session storage to read (injected for testability).
85
- * @param origin - The page origin whose guard to evaluate.
86
- * @param now - Current epoch ms (injected for deterministic tests).
87
- */
88
- export declare function guardActive(storage: Storage, origin: string, now: number): boolean;
89
- //# sourceMappingURL=ssoBounce.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ssoBounce.d.ts","sourceRoot":"","sources":["../../../../../src/ui/utils/ssoBounce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAOvC;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK7C;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAc1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAelF"}
@@ -1,146 +0,0 @@
1
- /**
2
- * Central cross-domain SSO bounce — per-origin sessionStorage keys and small
3
- * pure predicates shared by the cold-boot `sso-return` / `sso-bounce` steps and
4
- * the bfcache `pageshow` re-evaluation.
5
- *
6
- * TRUE central SSO (Google/Meta/Clerk style) works like this for a Relying
7
- * Party (mention.earth, homiio.com, alia.onl, …) with no local session:
8
- *
9
- * 1. `sso-bounce` (terminal, once): top-level navigate to
10
- * `auth.oxy.so/sso?prompt=none&client_id=<origin>&return_to=<origin>/__oxy/sso-callback&state=<s>`.
11
- * Before navigating it records, in this origin's `sessionStorage`, the CSRF
12
- * `state`, a guard timestamp (loop breaker), and the real destination URL
13
- * to restore after the callback.
14
- * 2. The central IdP worker reads its first-party `fedcm_session`, mints a
15
- * session, stores it under an opaque single-use `code`, and 303-redirects
16
- * back to `<origin>/__oxy/sso-callback#oxy_sso=ok&code=<code>&state=<s>`
17
- * (or `#oxy_sso=none` / `#oxy_sso=error`).
18
- * 3. `sso-return` parses the fragment (`parseSsoReturnFragment` from core),
19
- * validates `state`, exchanges the `code` via `oxyServices.exchangeSsoCode`,
20
- * and commits the session — then restores the original destination.
21
- *
22
- * Loop proof (logged-out): first load all steps skip → `sso-bounce` sets
23
- * guard/state/dest and navigates; the IdP (no central session) returns
24
- * `#oxy_sso=none`; the callback load's `sso-return` sees `none`, sets the
25
- * no-session flag, and `sso-bounce` is then disabled. Exactly ONE bounce, no
26
- * loop. An interrupted bounce (user hit back mid-redirect) self-heals once the
27
- * 30s guard TTL lapses.
28
- *
29
- * All state lives in `sessionStorage` (per tab, cleared on tab close) and is
30
- * keyed per-origin so two RPs hosted in the same browser never collide. This
31
- * module is pure with respect to navigation: it only reads/writes
32
- * `sessionStorage` and parses URLs; it performs no redirects itself.
33
- */
34
-
35
- import { CENTRAL_AUTH_URL } from '@oxyhq/core';
36
-
37
- /**
38
- * The RP callback path the central IdP redirects back to. The SSO result is
39
- * delivered in the fragment of this URL; `sso-return` consumes it and then
40
- * restores the user's real destination.
41
- */
42
- export const SSO_CALLBACK_PATH = '/__oxy/sso-callback';
43
-
44
- /**
45
- * Self-healing TTL (ms) for the bounce guard. If a bounce is interrupted before
46
- * the callback lands (e.g. the user navigates back mid-redirect), the guard
47
- * would otherwise pin the RP signed-out forever. After this window the guard is
48
- * treated as stale and a fresh single bounce is permitted.
49
- */
50
- export const SSO_GUARD_TTL_MS = 30_000;
51
-
52
- const STATE_KEY_PREFIX = 'oxy_sso_state:';
53
- const GUARD_KEY_PREFIX = 'oxy_sso_guard:';
54
- const DEST_KEY_PREFIX = 'oxy_sso_dest:';
55
- const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
56
-
57
- /**
58
- * Perform the terminal top-level SSO bounce navigation.
59
- *
60
- * A thin wrapper over `window.location.assign(url)` so the single navigation
61
- * seam lives in one place (and stays mockable in tests, where jsdom's
62
- * `Location.assign` is a non-configurable native method). In production this is
63
- * exactly `window.location.assign` — the document is torn down and replaced by
64
- * the central IdP page. Off-browser it is a no-op (native never bounces).
65
- */
66
- export function ssoNavigate(url: string): void {
67
- if (typeof window === 'undefined' || typeof window.location === 'undefined') {
68
- return;
69
- }
70
- window.location.assign(url);
71
- }
72
-
73
- /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
74
- export function ssoStateKey(origin: string): string {
75
- return `${STATE_KEY_PREFIX}${origin}`;
76
- }
77
-
78
- /** Per-origin bounce guard key (a timestamp; loop breaker + self-heal TTL). */
79
- export function ssoGuardKey(origin: string): string {
80
- return `${GUARD_KEY_PREFIX}${origin}`;
81
- }
82
-
83
- /** Per-origin destination key (the real URL to restore after the callback). */
84
- export function ssoDestKey(origin: string): string {
85
- return `${DEST_KEY_PREFIX}${origin}`;
86
- }
87
-
88
- /**
89
- * Per-origin "the central IdP has no session for me" key. Set after a
90
- * `none`/`error` return (or a failed/forged exchange) so `sso-bounce` does not
91
- * fire again this tab — the definitive loop breaker.
92
- */
93
- export function ssoNoSessionKey(origin: string): string {
94
- return `${NO_SESSION_KEY_PREFIX}${origin}`;
95
- }
96
-
97
- /**
98
- * Whether `origin` IS the central IdP origin. We must never bounce while on
99
- * `auth.oxy.so` itself (it would bounce to itself). Compared by URL origin so a
100
- * trailing-slash / path difference never defeats the guard.
101
- */
102
- export function isCentralIdPOrigin(origin: string): boolean {
103
- let centralOrigin: string;
104
- try {
105
- centralOrigin = new URL(CENTRAL_AUTH_URL).origin;
106
- } catch {
107
- return false;
108
- }
109
- let candidateOrigin: string;
110
- try {
111
- candidateOrigin = new URL(origin).origin;
112
- } catch {
113
- return false;
114
- }
115
- return candidateOrigin === centralOrigin;
116
- }
117
-
118
- /**
119
- * Read the bounce guard and decide whether it is still ACTIVE.
120
- *
121
- * Active means: a guard value is present AND it parses to a finite timestamp AND
122
- * less than {@link SSO_GUARD_TTL_MS} has elapsed since it was set. An active
123
- * guard disables `sso-bounce` (a bounce is already in flight this tab). A
124
- * missing, malformed, or expired guard is NOT active, so a fresh bounce may
125
- * proceed (this is the 30s self-heal for an interrupted bounce).
126
- *
127
- * @param storage - The session storage to read (injected for testability).
128
- * @param origin - The page origin whose guard to evaluate.
129
- * @param now - Current epoch ms (injected for deterministic tests).
130
- */
131
- export function guardActive(storage: Storage, origin: string, now: number): boolean {
132
- let raw: string | null;
133
- try {
134
- raw = storage.getItem(ssoGuardKey(origin));
135
- } catch {
136
- return false;
137
- }
138
- if (raw === null || raw.length === 0) {
139
- return false;
140
- }
141
- const stamp = Number(raw);
142
- if (!Number.isFinite(stamp)) {
143
- return false;
144
- }
145
- return now - stamp < SSO_GUARD_TTL_MS;
146
- }