@burdenoff/microfe-auth 2026.718.1 → 2026.718.3
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/auth/pages/LoginPage.js +271 -257
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/SSOClearPage.js +20 -26
- package/dist/auth/pages/SSOClearPage.js.map +1 -1
- package/dist/auth/pages/SSODepositPage.js +25 -31
- package/dist/auth/pages/SSODepositPage.js.map +1 -1
- package/dist/auth/pages/SSORelayPage.js +105 -75
- package/dist/auth/pages/SSORelayPage.js.map +1 -1
- package/dist/auth/pages/SessionBridgePage.js +85 -95
- package/dist/auth/pages/SessionBridgePage.js.map +1 -1
- package/dist/auth/utils/ssoOrigins.js +19 -0
- package/dist/auth/utils/ssoOrigins.js.map +1 -0
- package/package.json +2 -2
|
@@ -1,45 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { isAllowedSsoOrigin as e } from "../utils/ssoOrigins.js";
|
|
2
|
+
import { useEffect as t } from "react";
|
|
3
|
+
import { useSearchParams as n } from "react-router-dom";
|
|
4
|
+
import { jsx as r } from "react/jsx-runtime";
|
|
5
|
+
import { mintSsoSessionTokens as i } from "@burdenoff/fe-libs/shared/utils";
|
|
6
|
+
import { resolveGatewayUrls as a } from "@burdenoff/fe-libs/shared/config/gatewayUrls";
|
|
4
7
|
//#region src/auth/pages/SSORelayPage.tsx
|
|
5
|
-
var
|
|
8
|
+
var o = "relayChain", s = "sso_failed", c = new Set(["localhost", "127.0.0.1"]), l = {
|
|
6
9
|
"bf-sso": /(?:^|;\s*)bf-sso=([^;]*)/,
|
|
7
10
|
"bf-sso-profile": /(?:^|;\s*)bf-sso-profile=([^;]*)/
|
|
8
11
|
};
|
|
9
|
-
function
|
|
10
|
-
if (e.hostname === "localhost" || e.hostname === "127.0.0.1" || e.hostname.endsWith(".local.burdenoff.com")) return !0;
|
|
11
|
-
if (e.protocol !== "https:") return !1;
|
|
12
|
-
for (let t of r) if (e.hostname === t || e.hostname === `app.${t}` || e.hostname === `alphaapp.${t}` || e.hostname === `admin.${t}` || e.hostname === `alphaadmin.${t}` || e.hostname === `www.${t}`) return !0;
|
|
13
|
-
return !1;
|
|
14
|
-
}
|
|
15
|
-
function l() {
|
|
12
|
+
function u() {
|
|
16
13
|
try {
|
|
17
14
|
let e = window.location.hostname, t = e.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0;
|
|
18
|
-
for (let n of ["bf-sso", "bf-sso-profile"]) document.cookie = `${n}=; path=/; max-age=0`, t && (document.cookie = `${n}=; path=/; domain=${t}; max-age=0`), e && !
|
|
15
|
+
for (let n of ["bf-sso", "bf-sso-profile"]) document.cookie = `${n}=; path=/; max-age=0`, t && (document.cookie = `${n}=; path=/; domain=${t}; max-age=0`), e && !c.has(e) && (document.cookie = `${n}=; path=/; domain=${e}; max-age=0`);
|
|
19
16
|
} catch {}
|
|
20
17
|
}
|
|
21
|
-
function
|
|
18
|
+
function d() {
|
|
22
19
|
try {
|
|
23
|
-
return (new URLSearchParams(window.location.search).get(
|
|
20
|
+
return (new URLSearchParams(window.location.search).get(o) || "").split("|").filter((e) => e.length > 0);
|
|
24
21
|
} catch {
|
|
25
22
|
return [];
|
|
26
23
|
}
|
|
27
24
|
}
|
|
28
|
-
function
|
|
25
|
+
function f(t) {
|
|
29
26
|
try {
|
|
30
|
-
let
|
|
31
|
-
return
|
|
27
|
+
let n = new URL(t, window.location.origin);
|
|
28
|
+
return n.origin !== window.location.origin && !e(n) ? "/auth/login" : (n.searchParams.set(s, "1"), n.href);
|
|
32
29
|
} catch {
|
|
33
30
|
return "/auth/login?sso_failed=1";
|
|
34
31
|
}
|
|
35
32
|
}
|
|
36
|
-
function f() {
|
|
37
|
-
return p()?.id ?? null;
|
|
38
|
-
}
|
|
39
33
|
function p() {
|
|
34
|
+
return m()?.id ?? null;
|
|
35
|
+
}
|
|
36
|
+
function m() {
|
|
40
37
|
try {
|
|
41
38
|
for (let e of ["bf-sso", "bf-sso-profile"]) {
|
|
42
|
-
let t = document.cookie.match(
|
|
39
|
+
let t = document.cookie.match(l[e]);
|
|
43
40
|
if (!t) continue;
|
|
44
41
|
let n = JSON.parse(decodeURIComponent(t[1]));
|
|
45
42
|
if (typeof n == "string") return {
|
|
@@ -56,36 +53,36 @@ function p() {
|
|
|
56
53
|
}
|
|
57
54
|
return null;
|
|
58
55
|
}
|
|
59
|
-
function
|
|
56
|
+
function h(t) {
|
|
60
57
|
try {
|
|
61
|
-
let
|
|
62
|
-
if (!
|
|
63
|
-
let
|
|
64
|
-
if (!
|
|
65
|
-
let
|
|
66
|
-
if (
|
|
67
|
-
let a = new URL("/auth/sso-relay",
|
|
68
|
-
return a.searchParams.set("returnTo",
|
|
58
|
+
let n = m();
|
|
59
|
+
if (!n?.origin) return !1;
|
|
60
|
+
let r = new URL(n.origin);
|
|
61
|
+
if (!e(r) || r.origin === window.location.origin) return !1;
|
|
62
|
+
let i = d();
|
|
63
|
+
if (i.includes(r.origin) || i.length >= 3) return !1;
|
|
64
|
+
let a = new URL("/auth/sso-relay", r.origin);
|
|
65
|
+
return a.searchParams.set("returnTo", t), a.searchParams.set(o, Array.from(new Set([...i, window.location.origin])).join("|")), window.location.replace(a.href), !0;
|
|
69
66
|
} catch {
|
|
70
67
|
return !1;
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
|
-
function
|
|
70
|
+
function g() {
|
|
74
71
|
return localStorage.getItem("bf-profiles");
|
|
75
72
|
}
|
|
76
|
-
function
|
|
73
|
+
function _() {
|
|
77
74
|
try {
|
|
78
|
-
let e =
|
|
75
|
+
let e = g();
|
|
79
76
|
return (e ? JSON.parse(e) : []).map((e) => e.id).filter((e) => typeof e == "string" && e.length > 0);
|
|
80
77
|
} catch {
|
|
81
78
|
return [];
|
|
82
79
|
}
|
|
83
80
|
}
|
|
84
|
-
function
|
|
81
|
+
function v() {
|
|
85
82
|
let e = [
|
|
86
|
-
|
|
83
|
+
p(),
|
|
87
84
|
sessionStorage.getItem("bf-active-profile"),
|
|
88
|
-
...
|
|
85
|
+
..._()
|
|
89
86
|
].filter((e) => typeof e == "string" && e.length > 0);
|
|
90
87
|
for (let t of Array.from(new Set(e))) {
|
|
91
88
|
let e = localStorage.getItem(`bf-p-${t}-tokens`), n = localStorage.getItem(`bf-p-${t}-user`);
|
|
@@ -93,51 +90,84 @@ function _() {
|
|
|
93
90
|
}
|
|
94
91
|
return null;
|
|
95
92
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
let o = btoa(JSON.stringify({
|
|
117
|
-
profileId: t,
|
|
118
|
-
tokens: n,
|
|
119
|
-
user: i,
|
|
120
|
-
profiles: a
|
|
121
|
-
})), s;
|
|
122
|
-
try {
|
|
123
|
-
let t = new URL(e);
|
|
124
|
-
if (!c(t)) {
|
|
93
|
+
function y(e) {
|
|
94
|
+
try {
|
|
95
|
+
localStorage.removeItem(`bf-p-${e}-tokens`), localStorage.removeItem(`bf-p-${e}-user`), localStorage.removeItem(`bf-p-${e}-workspace-token`), localStorage.removeItem(`bf-p-${e}-auth-storage`), sessionStorage.removeItem("bf-active-profile");
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
function b(e) {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(e);
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function x() {
|
|
106
|
+
let [o] = n();
|
|
107
|
+
return t(() => {
|
|
108
|
+
let t = !1;
|
|
109
|
+
return (async () => {
|
|
110
|
+
let n = o.get("returnTo");
|
|
111
|
+
if (!n) {
|
|
125
112
|
window.location.replace("/");
|
|
126
113
|
return;
|
|
127
114
|
}
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
let r = v();
|
|
116
|
+
if (!r) {
|
|
117
|
+
if (h(n)) return;
|
|
118
|
+
u(), window.location.replace(f(n));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
let s = localStorage.getItem(`bf-p-${r}-tokens`), c = localStorage.getItem(`bf-p-${r}-user`), l = g();
|
|
122
|
+
if (!s || !c) {
|
|
123
|
+
if (h(n)) return;
|
|
124
|
+
u(), window.location.replace(f(n));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let d;
|
|
128
|
+
try {
|
|
129
|
+
d = new URL(n);
|
|
130
|
+
} catch {
|
|
131
|
+
window.location.replace(n);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!e(d)) {
|
|
135
|
+
window.location.replace("/");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let p = b(s), m = null;
|
|
139
|
+
if (p?.refreshToken) {
|
|
140
|
+
let { globalBaseUrl: e } = a(), { tokens: o, denied: s } = await i(`${e}/global/graphql`, p.refreshToken);
|
|
141
|
+
if (t) return;
|
|
142
|
+
if (o) m = JSON.stringify(o);
|
|
143
|
+
else if (s) {
|
|
144
|
+
if (y(r), h(n)) return;
|
|
145
|
+
u(), window.location.replace(f(n));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!m) if (p?.accessToken && typeof p.expiresAt == "number" && p.expiresAt - Date.now() > 6e4) m = s;
|
|
150
|
+
else {
|
|
151
|
+
if (y(r), h(n)) return;
|
|
152
|
+
u(), window.location.replace(f(n));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let _ = btoa(JSON.stringify({
|
|
156
|
+
profileId: r,
|
|
157
|
+
tokens: m,
|
|
158
|
+
user: c,
|
|
159
|
+
profiles: l
|
|
160
|
+
})), x = d.pathname + d.search, S = `${d.origin}/auth/sso-callback?returnTo=${encodeURIComponent(x)}#sso=${_}`;
|
|
161
|
+
window.location.replace(S);
|
|
162
|
+
})(), () => {
|
|
163
|
+
t = !0;
|
|
164
|
+
};
|
|
165
|
+
}, [o]), /* @__PURE__ */ r("div", {
|
|
136
166
|
className: "flex items-center justify-center min-h-screen bg-bg-canvas",
|
|
137
|
-
children: /* @__PURE__ */
|
|
167
|
+
children: /* @__PURE__ */ r("div", { className: "animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent" })
|
|
138
168
|
});
|
|
139
169
|
}
|
|
140
170
|
//#endregion
|
|
141
|
-
export {
|
|
171
|
+
export { x as SSORelayPage };
|
|
142
172
|
|
|
143
173
|
//# sourceMappingURL=SSORelayPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSORelayPage.js","names":[],"sources":["../../../src/auth/pages/SSORelayPage.tsx"],"sourcesContent":["/**\n * SSORelayPage — Cross-domain SSO token relay.\n *\n * This page is loaded on the ORIGIN where the user is logged in.\n * It reads tokens from localStorage and redirects to the TARGET domain's\n * /auth/sso-callback with the tokens in a URL fragment (not query param\n * for security — fragments are not sent to servers).\n *\n * Flow:\n * 1. User visits productA.com → not logged in → detects SSO cookie\n * 2. Redirect to productB.com/auth/sso-relay?returnTo=productA.com/dashboard\n * (productB is where the user IS logged in — identified by cookie)\n * 3. This page reads tokens from productB's localStorage\n * 4. Redirects to productA.com/auth/sso-callback#token=<base64-encoded-tokens>\n * 5. SSORelayCallbackPage on productA decodes and stores tokens, redirects to /dashboard\n */\n\nimport { useEffect } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\n\nconst ALLOWED_RELAY_DOMAINS = new Set([\n \"burdenoff.com\",\n \"vibecontrols.com\",\n \"fluidgrids.com\",\n \"botlit.ai\",\n \"bigconsole.com\",\n \"gethealthybowl.com\",\n \"headshotmarketing.com\",\n \"timecampus.com\",\n \"ggnomad.com\",\n \"movethewheels.com\",\n \"crewfoundry.com\",\n \"planmagnet.com\",\n \"eventfullymanaged.com\",\n \"kadaikodi.com\",\n \"assethandler.com\",\n \"technospam.com\",\n \"semanticfed.com\",\n \"manufacturedops.com\",\n \"theglobalfuel.com\",\n \"housingvista.com\",\n \"proserviceworld.com\",\n \"mybodyshield.com\",\n \"artistrybase.com\",\n \"collabkin.com\",\n \"brainyrich.com\",\n \"theculturepost.com\",\n \"comradecircle.com\",\n \"cosmicintersection.com\",\n \"govcitizenhub.com\",\n \"intelwatchtower.com\",\n \"ecoimpacthub.com\",\n \"buildmyiq.com\",\n \"fluidgrids.ai\",\n \"atheletebridge.com\",\n \"adaptercloud.com\",\n \"coolandlovely.com\",\n \"subscriberbot.com\",\n \"labsofscience.com\",\n \"hookmovies.com\",\n]);\n\nconst RELAY_CHAIN_PARAM = \"relayChain\";\nconst RELAY_FAILED_PARAM = \"sso_failed\";\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\"]);\n\nconst SSO_COOKIE_PATTERNS = {\n \"bf-sso\": /(?:^|;\\s*)bf-sso=([^;]*)/,\n \"bf-sso-profile\": /(?:^|;\\s*)bf-sso-profile=([^;]*)/,\n} as const;\n\nfunction isAllowedRelayTarget(url: URL): boolean {\n if (url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\") {\n return true;\n }\n if (url.hostname.endsWith(\".local.burdenoff.com\")) {\n return true;\n }\n if (url.protocol !== \"https:\") {\n return false;\n }\n for (const domain of ALLOWED_RELAY_DOMAINS) {\n if (\n url.hostname === domain ||\n url.hostname === `app.${domain}` ||\n url.hostname === `alphaapp.${domain}` ||\n // Admin app shells (admin.burdenoff.com / alphaadmin.burdenoff.com) are\n // first-party SSO relay targets. Without these the relay-back to the\n // admin shell fails isAllowedRelayTarget() and SSORelayPage falls through\n // to window.location.replace(\"/\"), stranding the user on app.burdenoff.com.\n url.hostname === `admin.${domain}` ||\n url.hostname === `alphaadmin.${domain}` ||\n url.hostname === `www.${domain}`\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction clearRelayCookies(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n document.cookie = `${cookieName}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${cookieName}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && !LOCAL_HOSTNAMES.has(hostname)) {\n document.cookie = `${cookieName}=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* best effort */\n }\n}\n\nfunction getRelayChain(): string[] {\n try {\n return (\n new URLSearchParams(window.location.search).get(RELAY_CHAIN_PARAM) || \"\"\n )\n .split(\"|\")\n .filter((origin) => origin.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction getFailedReturnTo(returnTo: string): string {\n try {\n const targetUrl = new URL(returnTo, window.location.origin);\n if (\n targetUrl.origin !== window.location.origin &&\n !isAllowedRelayTarget(targetUrl)\n ) {\n return \"/auth/login\";\n }\n targetUrl.searchParams.set(RELAY_FAILED_PARAM, \"1\");\n return targetUrl.href;\n } catch {\n return \"/auth/login?sso_failed=1\";\n }\n}\n\nfunction readSsoCookieProfileId(): string | null {\n return readSsoCookieData()?.id ?? null;\n}\n\nfunction readSsoCookieData(): {\n id: string | null;\n origin: string | null;\n} | null {\n try {\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n const match = document.cookie.match(SSO_COOKIE_PATTERNS[cookieName]);\n if (!match) continue;\n\n const parsed = JSON.parse(decodeURIComponent(match[1])) as\n | string\n | { id?: unknown; origin?: unknown };\n if (typeof parsed === \"string\") return { id: parsed, origin: null };\n if (typeof parsed.id === \"string\" && parsed.id.length > 0) {\n return {\n id: parsed.id,\n origin: typeof parsed.origin === \"string\" ? parsed.origin : null,\n };\n }\n }\n } catch {\n return null;\n }\n return null;\n}\n\nfunction redirectToCookieRelayOrigin(returnTo: string): boolean {\n try {\n const ssoCookie = readSsoCookieData();\n if (!ssoCookie?.origin) return false;\n\n const relayUrl = new URL(ssoCookie.origin);\n if (!isAllowedRelayTarget(relayUrl)) return false;\n if (relayUrl.origin === window.location.origin) return false;\n\n const relayChain = getRelayChain();\n if (relayChain.includes(relayUrl.origin) || relayChain.length >= 3) {\n return false;\n }\n\n const nextRelayUrl = new URL(\"/auth/sso-relay\", relayUrl.origin);\n nextRelayUrl.searchParams.set(\"returnTo\", returnTo);\n nextRelayUrl.searchParams.set(\n RELAY_CHAIN_PARAM,\n Array.from(new Set([...relayChain, window.location.origin])).join(\"|\"),\n );\n\n window.location.replace(nextRelayUrl.href);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction readProfilesRaw(): string | null {\n return localStorage.getItem(\"bf-profiles\");\n}\n\nfunction readProfileIds(): string[] {\n try {\n const profilesRaw = readProfilesRaw();\n const profiles = profilesRaw\n ? (JSON.parse(profilesRaw) as Array<{ id?: unknown }>)\n : [];\n return profiles\n .map((profile) => profile.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction resolveRelayProfileId(): string | null {\n const candidates = [\n readSsoCookieProfileId(),\n sessionStorage.getItem(\"bf-active-profile\"),\n ...readProfileIds(),\n ].filter((id): id is string => typeof id === \"string\" && id.length > 0);\n\n for (const profileId of Array.from(new Set(candidates))) {\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (tokensRaw && userRaw) {\n sessionStorage.setItem(\"bf-active-profile\", profileId);\n return profileId;\n }\n }\n\n return null;\n}\n\nexport function SSORelayPage() {\n const [searchParams] = useSearchParams();\n\n useEffect(() => {\n const returnTo = searchParams.get(\"returnTo\");\n if (!returnTo) {\n // No relay target — there is nothing to relay to. Do NOT strand the user\n // on a perpetual spinner (HM-016): send them to the app root, which routes\n // to the dashboard when authenticated or the landing/login when not.\n window.location.replace(\"/\");\n return;\n }\n\n // Read tokens from localStorage (this origin has them). Prefer the shared\n // SSO cookie profile because a relay often runs in a new tab with empty\n // sessionStorage and stale historical profiles in localStorage.\n const profileId = resolveRelayProfileId();\n if (!profileId) {\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n const profilesRaw = readProfilesRaw();\n\n if (!tokensRaw || !userRaw) {\n // No tokens — redirect back without them\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n // Encode the auth state as a base64 URL fragment\n const payload = {\n profileId,\n tokens: tokensRaw,\n user: userRaw,\n profiles: profilesRaw,\n };\n const encoded = btoa(JSON.stringify(payload));\n\n // Build the callback URL on the target domain\n // Parse returnTo to get the target origin\n let callbackUrl: string;\n try {\n const targetUrl = new URL(returnTo);\n if (!isAllowedRelayTarget(targetUrl)) {\n window.location.replace(\"/\");\n return;\n }\n const returnPath = targetUrl.pathname + targetUrl.search;\n callbackUrl = `${targetUrl.origin}/auth/sso-callback?returnTo=${encodeURIComponent(returnPath)}#sso=${encoded}`;\n } catch {\n // returnTo is a relative path — can't do cross-domain relay\n window.location.replace(returnTo);\n return;\n }\n\n // Redirect to the callback\n window.location.replace(callbackUrl);\n }, [searchParams]);\n\n return (\n <div className=\"flex items-center justify-center min-h-screen bg-bg-canvas\">\n <div className=\"animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n"],"mappings":";;;;AAoBA,IAAM,IAAwB,IAAI,IAAI,8qBAwCrC,CAAC,EAEI,IAAoB,cACpB,IAAqB,cACrB,IAAkB,IAAI,IAAI,CAAC,aAAa,YAAY,CAAC,EAErD,IAAsB;CAC1B,UAAU;CACV,kBAAkB;CACnB;AAED,SAAS,EAAqB,GAAmB;AAI/C,KAHI,EAAI,aAAa,eAAe,EAAI,aAAa,eAGjD,EAAI,SAAS,SAAS,uBAAuB,CAC/C,QAAO;AAET,KAAI,EAAI,aAAa,SACnB,QAAO;AAET,MAAK,IAAM,KAAU,EACnB,KACE,EAAI,aAAa,KACjB,EAAI,aAAa,OAAO,OACxB,EAAI,aAAa,YAAY,OAK7B,EAAI,aAAa,SAAS,OAC1B,EAAI,aAAa,cAAc,OAC/B,EAAI,aAAa,OAAO,IAExB,QAAO;AAGX,QAAO;;AAGT,SAAS,IAA0B;AACjC,KAAI;EACF,IAAM,IAAW,OAAO,SAAS,UAC3B,IAAS,EAAS,SAAS,uBAAuB,GACpD,yBACA,KAAA;AACJ,OAAK,IAAM,KAAc,CAAC,UAAU,iBAAiB,CAKnD,CAJA,SAAS,SAAS,GAAG,EAAW,uBAC5B,MACF,SAAS,SAAS,GAAG,EAAW,oBAAoB,EAAO,eAEzD,KAAY,CAAC,EAAgB,IAAI,EAAS,KAC5C,SAAS,SAAS,GAAG,EAAW,oBAAoB,EAAS;SAG3D;;AAKV,SAAS,IAA0B;AACjC,KAAI;AACF,UACE,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAAC,IAAI,EAAkB,IAAI,IAErE,MAAM,IAAI,CACV,QAAQ,MAAW,EAAO,SAAS,EAAE;SAClC;AACN,SAAO,EAAE;;;AAIb,SAAS,EAAkB,GAA0B;AACnD,KAAI;EACF,IAAM,IAAY,IAAI,IAAI,GAAU,OAAO,SAAS,OAAO;AAQ3D,SANE,EAAU,WAAW,OAAO,SAAS,UACrC,CAAC,EAAqB,EAAU,GAEzB,iBAET,EAAU,aAAa,IAAI,GAAoB,IAAI,EAC5C,EAAU;SACX;AACN,SAAO;;;AAIX,SAAS,IAAwC;AAC/C,QAAO,GAAmB,EAAE,MAAM;;AAGpC,SAAS,IAGA;AACP,KAAI;AACF,OAAK,IAAM,KAAc,CAAC,UAAU,iBAAiB,EAAW;GAC9D,IAAM,IAAQ,SAAS,OAAO,MAAM,EAAoB,GAAY;AACpE,OAAI,CAAC,EAAO;GAEZ,IAAM,IAAS,KAAK,MAAM,mBAAmB,EAAM,GAAG,CAAC;AAGvD,OAAI,OAAO,KAAW,SAAU,QAAO;IAAE,IAAI;IAAQ,QAAQ;IAAM;AACnE,OAAI,OAAO,EAAO,MAAO,YAAY,EAAO,GAAG,SAAS,EACtD,QAAO;IACL,IAAI,EAAO;IACX,QAAQ,OAAO,EAAO,UAAW,WAAW,EAAO,SAAS;IAC7D;;SAGC;AACN,SAAO;;AAET,QAAO;;AAGT,SAAS,EAA4B,GAA2B;AAC9D,KAAI;EACF,IAAM,IAAY,GAAmB;AACrC,MAAI,CAAC,GAAW,OAAQ,QAAO;EAE/B,IAAM,IAAW,IAAI,IAAI,EAAU,OAAO;AAE1C,MADI,CAAC,EAAqB,EAAS,IAC/B,EAAS,WAAW,OAAO,SAAS,OAAQ,QAAO;EAEvD,IAAM,IAAa,GAAe;AAClC,MAAI,EAAW,SAAS,EAAS,OAAO,IAAI,EAAW,UAAU,EAC/D,QAAO;EAGT,IAAM,IAAe,IAAI,IAAI,mBAAmB,EAAS,OAAO;AAQhE,SAPA,EAAa,aAAa,IAAI,YAAY,EAAS,EACnD,EAAa,aAAa,IACxB,GACA,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,GAAY,OAAO,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CACvE,EAED,OAAO,SAAS,QAAQ,EAAa,KAAK,EACnC;SACD;AACN,SAAO;;;AAIX,SAAS,IAAiC;AACxC,QAAO,aAAa,QAAQ,cAAc;;AAG5C,SAAS,IAA2B;AAClC,KAAI;EACF,IAAM,IAAc,GAAiB;AAIrC,UAHiB,IACZ,KAAK,MAAM,EAAY,GACxB,EAAE,EAEH,KAAK,MAAY,EAAQ,GAAG,CAC5B,QAAQ,MAAqB,OAAO,KAAO,YAAY,EAAG,SAAS,EAAE;SAClE;AACN,SAAO,EAAE;;;AAIb,SAAS,IAAuC;CAC9C,IAAM,IAAa;EACjB,GAAwB;EACxB,eAAe,QAAQ,oBAAoB;EAC3C,GAAG,GAAgB;EACpB,CAAC,QAAQ,MAAqB,OAAO,KAAO,YAAY,EAAG,SAAS,EAAE;AAEvE,MAAK,IAAM,KAAa,MAAM,KAAK,IAAI,IAAI,EAAW,CAAC,EAAE;EACvD,IAAM,IAAY,aAAa,QAAQ,QAAQ,EAAU,SAAS,EAC5D,IAAU,aAAa,QAAQ,QAAQ,EAAU,OAAO;AAC9D,MAAI,KAAa,EAEf,QADA,eAAe,QAAQ,qBAAqB,EAAU,EAC/C;;AAIX,QAAO;;AAGT,SAAgB,IAAe;CAC7B,IAAM,CAAC,KAAgB,GAAiB;AAiExC,QA/DA,QAAgB;EACd,IAAM,IAAW,EAAa,IAAI,WAAW;AAC7C,MAAI,CAAC,GAAU;AAIb,UAAO,SAAS,QAAQ,IAAI;AAC5B;;EAMF,IAAM,IAAY,GAAuB;AACzC,MAAI,CAAC,GAAW;AACd,OAAI,EAA4B,EAAS,CAAE;AAE3C,GADA,GAAmB,EACnB,OAAO,SAAS,QAAQ,EAAkB,EAAS,CAAC;AACpD;;EAGF,IAAM,IAAY,aAAa,QAAQ,QAAQ,EAAU,SAAS,EAC5D,IAAU,aAAa,QAAQ,QAAQ,EAAU,OAAO,EACxD,IAAc,GAAiB;AAErC,MAAI,CAAC,KAAa,CAAC,GAAS;AAE1B,OAAI,EAA4B,EAAS,CAAE;AAE3C,GADA,GAAmB,EACnB,OAAO,SAAS,QAAQ,EAAkB,EAAS,CAAC;AACpD;;EAUF,IAAM,IAAU,KAAK,KAAK,UANV;GACd;GACA,QAAQ;GACR,MAAM;GACN,UAAU;GACX,CAC2C,CAAC,EAIzC;AACJ,MAAI;GACF,IAAM,IAAY,IAAI,IAAI,EAAS;AACnC,OAAI,CAAC,EAAqB,EAAU,EAAE;AACpC,WAAO,SAAS,QAAQ,IAAI;AAC5B;;GAEF,IAAM,IAAa,EAAU,WAAW,EAAU;AAClD,OAAc,GAAG,EAAU,OAAO,8BAA8B,mBAAmB,EAAW,CAAC,OAAO;UAChG;AAEN,UAAO,SAAS,QAAQ,EAAS;AACjC;;AAIF,SAAO,SAAS,QAAQ,EAAY;IACnC,CAAC,EAAa,CAAC,EAGhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,2FAA4F,CAAA;EACvG,CAAA"}
|
|
1
|
+
{"version":3,"file":"SSORelayPage.js","names":[],"sources":["../../../src/auth/pages/SSORelayPage.tsx"],"sourcesContent":["/**\n * SSORelayPage — Cross-domain SSO token relay.\n *\n * This page is loaded on the ORIGIN where the user is logged in.\n * It reads tokens from localStorage and redirects to the TARGET domain's\n * /auth/sso-callback with the tokens in a URL fragment (not query param\n * for security — fragments are not sent to servers).\n *\n * Flow:\n * 1. User visits productA.com → not logged in → detects SSO cookie\n * 2. Redirect to productB.com/auth/sso-relay?returnTo=productA.com/dashboard\n * (productB is where the user IS logged in — identified by cookie)\n * 3. This page reads tokens from productB's localStorage\n * 4. Redirects to productA.com/auth/sso-callback#token=<base64-encoded-tokens>\n * 5. SSORelayCallbackPage on productA decodes and stores tokens, redirects to /dashboard\n */\n\nimport { useEffect } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { resolveGatewayUrls } from \"@burdenoff/fe-libs/shared/config/gatewayUrls\";\nimport { mintSsoSessionTokens } from \"@burdenoff/fe-libs/shared/utils\";\nimport { isAllowedSsoOrigin } from \"../utils/ssoOrigins\";\n\nconst RELAY_CHAIN_PARAM = \"relayChain\";\nconst RELAY_FAILED_PARAM = \"sso_failed\";\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\"]);\n\nconst SSO_COOKIE_PATTERNS = {\n \"bf-sso\": /(?:^|;\\s*)bf-sso=([^;]*)/,\n \"bf-sso-profile\": /(?:^|;\\s*)bf-sso-profile=([^;]*)/,\n} as const;\n\nfunction clearRelayCookies(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n document.cookie = `${cookieName}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${cookieName}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && !LOCAL_HOSTNAMES.has(hostname)) {\n document.cookie = `${cookieName}=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* best effort */\n }\n}\n\nfunction getRelayChain(): string[] {\n try {\n return (\n new URLSearchParams(window.location.search).get(RELAY_CHAIN_PARAM) || \"\"\n )\n .split(\"|\")\n .filter((origin) => origin.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction getFailedReturnTo(returnTo: string): string {\n try {\n const targetUrl = new URL(returnTo, window.location.origin);\n if (\n targetUrl.origin !== window.location.origin &&\n !isAllowedSsoOrigin(targetUrl)\n ) {\n return \"/auth/login\";\n }\n targetUrl.searchParams.set(RELAY_FAILED_PARAM, \"1\");\n return targetUrl.href;\n } catch {\n return \"/auth/login?sso_failed=1\";\n }\n}\n\nfunction readSsoCookieProfileId(): string | null {\n return readSsoCookieData()?.id ?? null;\n}\n\nfunction readSsoCookieData(): {\n id: string | null;\n origin: string | null;\n} | null {\n try {\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n const match = document.cookie.match(SSO_COOKIE_PATTERNS[cookieName]);\n if (!match) continue;\n\n const parsed = JSON.parse(decodeURIComponent(match[1])) as\n string | { id?: unknown; origin?: unknown };\n if (typeof parsed === \"string\") return { id: parsed, origin: null };\n if (typeof parsed.id === \"string\" && parsed.id.length > 0) {\n return {\n id: parsed.id,\n origin: typeof parsed.origin === \"string\" ? parsed.origin : null,\n };\n }\n }\n } catch {\n return null;\n }\n return null;\n}\n\nfunction redirectToCookieRelayOrigin(returnTo: string): boolean {\n try {\n const ssoCookie = readSsoCookieData();\n if (!ssoCookie?.origin) return false;\n\n const relayUrl = new URL(ssoCookie.origin);\n if (!isAllowedSsoOrigin(relayUrl)) return false;\n if (relayUrl.origin === window.location.origin) return false;\n\n const relayChain = getRelayChain();\n if (relayChain.includes(relayUrl.origin) || relayChain.length >= 3) {\n return false;\n }\n\n const nextRelayUrl = new URL(\"/auth/sso-relay\", relayUrl.origin);\n nextRelayUrl.searchParams.set(\"returnTo\", returnTo);\n nextRelayUrl.searchParams.set(\n RELAY_CHAIN_PARAM,\n Array.from(new Set([...relayChain, window.location.origin])).join(\"|\"),\n );\n\n window.location.replace(nextRelayUrl.href);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction readProfilesRaw(): string | null {\n return localStorage.getItem(\"bf-profiles\");\n}\n\nfunction readProfileIds(): string[] {\n try {\n const profilesRaw = readProfilesRaw();\n const profiles = profilesRaw\n ? (JSON.parse(profilesRaw) as Array<{ id?: unknown }>)\n : [];\n return profiles\n .map((profile) => profile.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction resolveRelayProfileId(): string | null {\n const candidates = [\n readSsoCookieProfileId(),\n sessionStorage.getItem(\"bf-active-profile\"),\n ...readProfileIds(),\n ].filter((id): id is string => typeof id === \"string\" && id.length > 0);\n\n for (const profileId of Array.from(new Set(candidates))) {\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (tokensRaw && userRaw) {\n sessionStorage.setItem(\"bf-active-profile\", profileId);\n return profileId;\n }\n }\n\n return null;\n}\n\n/** Wipe a dead/stale profile session from THIS origin's storage so future\n * relays stop offering it (BOFF-4463). */\nfunction clearStaleRelayProfile(profileId: string): void {\n try {\n localStorage.removeItem(`bf-p-${profileId}-tokens`);\n localStorage.removeItem(`bf-p-${profileId}-user`);\n localStorage.removeItem(`bf-p-${profileId}-workspace-token`);\n localStorage.removeItem(`bf-p-${profileId}-auth-storage`);\n sessionStorage.removeItem(\"bf-active-profile\");\n } catch {\n /* best effort */\n }\n}\n\nfunction parseStoredTokens(tokensRaw: string): {\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n} | null {\n try {\n return JSON.parse(tokensRaw) as {\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n };\n } catch {\n return null;\n }\n}\n\nexport function SSORelayPage() {\n const [searchParams] = useSearchParams();\n\n useEffect(() => {\n let cancelled = false;\n\n const run = async () => {\n const returnTo = searchParams.get(\"returnTo\");\n if (!returnTo) {\n // No relay target — there is nothing to relay to. Do NOT strand the user\n // on a perpetual spinner (HM-016): send them to the app root, which routes\n // to the dashboard when authenticated or the landing/login when not.\n window.location.replace(\"/\");\n return;\n }\n\n // Read tokens from localStorage (this origin has them). Prefer the shared\n // SSO cookie profile because a relay often runs in a new tab with empty\n // sessionStorage and stale historical profiles in localStorage.\n const profileId = resolveRelayProfileId();\n if (!profileId) {\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n const profilesRaw = readProfilesRaw();\n\n if (!tokensRaw || !userRaw) {\n // No tokens — redirect back without them\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n // Validate the target BEFORE any server round-trip.\n let targetUrl: URL;\n try {\n targetUrl = new URL(returnTo);\n } catch {\n // returnTo is a relative path — can't do cross-domain relay\n window.location.replace(returnTo);\n return;\n }\n if (!isAllowedSsoOrigin(targetUrl)) {\n window.location.replace(\"/\");\n return;\n }\n\n // BOFF-4463: relay a FRESH, dedicated token lineage to the target\n // instead of copying this origin's pair. Copied pairs share one\n // rotating refresh token — the first origin to refresh revoked it for\n // every other origin, and this page previously relayed even EXPIRED\n // pairs, dropping the target into a session that force-logged-out\n // minutes later.\n const stored = parseStoredTokens(tokensRaw);\n let relayTokensJson: string | null = null;\n\n if (stored?.refreshToken) {\n const { globalBaseUrl } = resolveGatewayUrls();\n const { tokens: minted, denied } = await mintSsoSessionTokens(\n `${globalBaseUrl}/global/graphql`,\n stored.refreshToken,\n );\n if (cancelled) return;\n if (minted) {\n relayTokensJson = JSON.stringify(minted);\n } else if (denied) {\n // The session behind this stored pair is dead (revoked/rotated\n // away). Never hand it to the target — clear it and fail over.\n clearStaleRelayProfile(profileId);\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n }\n\n if (!relayTokensJson) {\n // Mint unavailable (rollout gap / transient). Legacy fallback is only\n // safe while the stored access token is actually still valid.\n const accessStillValid =\n !!stored?.accessToken &&\n typeof stored.expiresAt === \"number\" &&\n stored.expiresAt - Date.now() > 60_000;\n if (accessStillValid) {\n relayTokensJson = tokensRaw;\n } else {\n clearStaleRelayProfile(profileId);\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n }\n\n // Encode the auth state as a base64 URL fragment\n const payload = {\n profileId,\n tokens: relayTokensJson,\n user: userRaw,\n profiles: profilesRaw,\n };\n const encoded = btoa(JSON.stringify(payload));\n\n const returnPath = targetUrl.pathname + targetUrl.search;\n const callbackUrl = `${targetUrl.origin}/auth/sso-callback?returnTo=${encodeURIComponent(returnPath)}#sso=${encoded}`;\n\n // Redirect to the callback\n window.location.replace(callbackUrl);\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [searchParams]);\n\n return (\n <div className=\"flex items-center justify-center min-h-screen bg-bg-canvas\">\n <div className=\"animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,30 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isAllowedSsoOriginString as e } from "../utils/ssoOrigins.js";
|
|
2
|
+
import { useEffect as t, useRef as n } from "react";
|
|
3
|
+
import { jsx as r } from "react/jsx-runtime";
|
|
3
4
|
//#region src/auth/pages/SessionBridgePage.tsx
|
|
4
|
-
var
|
|
5
|
-
function
|
|
5
|
+
var i = "burdenoff-auth-bridge", a = "burdenoff-auth-client", o = "bf-p", s = "bf-profiles", c = "bf-active-profile";
|
|
6
|
+
function l(e) {
|
|
6
7
|
return e !== null && typeof e.postMessage == "function";
|
|
7
8
|
}
|
|
8
|
-
function
|
|
9
|
+
function u(e) {
|
|
9
10
|
return typeof e == "object" && !!e && typeof e.type == "string";
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
function d(e) {
|
|
12
|
+
function d() {
|
|
13
13
|
try {
|
|
14
|
-
let t =
|
|
15
|
-
if (t.hostname === "localhost" || t.hostname === "127.0.0.1" || t.hostname.endsWith(".local.burdenoff.com")) return !0;
|
|
16
|
-
for (let e of u) if (t.hostname === e || t.hostname === `app.${e}` || t.hostname === `alphaapp.${e}` || t.hostname === `admin.${e}` || t.hostname === `alphaadmin.${e}` || t.hostname === `www.${e}`) return !0;
|
|
17
|
-
return !1;
|
|
18
|
-
} catch {
|
|
19
|
-
return !1;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function f() {
|
|
23
|
-
try {
|
|
24
|
-
let e = localStorage.getItem(o), t = sessionStorage.getItem(s);
|
|
14
|
+
let e = localStorage.getItem(s), t = sessionStorage.getItem(c);
|
|
25
15
|
if (!t && e) try {
|
|
26
16
|
let n = JSON.parse(e);
|
|
27
|
-
n.length > 0 && (t = n[0].id, sessionStorage.setItem(
|
|
17
|
+
n.length > 0 && (t = n[0].id, sessionStorage.setItem(c, t));
|
|
28
18
|
} catch {}
|
|
29
19
|
if (!t) return {
|
|
30
20
|
user: null,
|
|
@@ -32,7 +22,7 @@ function f() {
|
|
|
32
22
|
profiles: e ? JSON.parse(e) : [],
|
|
33
23
|
activeProfileId: null
|
|
34
24
|
};
|
|
35
|
-
let n = localStorage.getItem(`${
|
|
25
|
+
let n = localStorage.getItem(`${o}-${t}-user`), r = localStorage.getItem(`${o}-${t}-tokens`), i = localStorage.getItem(`${o}-${t}-workspace-token`);
|
|
36
26
|
return {
|
|
37
27
|
user: n ? JSON.parse(n) : null,
|
|
38
28
|
tokens: r ? {
|
|
@@ -51,64 +41,64 @@ function f() {
|
|
|
51
41
|
};
|
|
52
42
|
}
|
|
53
43
|
}
|
|
54
|
-
function
|
|
55
|
-
let t = e.activeProfileId ?? e.user?.id ?? sessionStorage.getItem(
|
|
44
|
+
function f(e) {
|
|
45
|
+
let t = e.activeProfileId ?? e.user?.id ?? sessionStorage.getItem(c);
|
|
56
46
|
if (t) {
|
|
57
|
-
if (e.user && localStorage.setItem(`${
|
|
47
|
+
if (e.user && localStorage.setItem(`${o}-${t}-user`, JSON.stringify(e.user)), e.tokens && typeof e.tokens == "object") {
|
|
58
48
|
let { workspaceToken: n, ...r } = e.tokens;
|
|
59
|
-
localStorage.setItem(`${
|
|
49
|
+
localStorage.setItem(`${o}-${t}-tokens`, JSON.stringify(r)), n && localStorage.setItem(`${o}-${t}-workspace-token`, String(n));
|
|
60
50
|
}
|
|
61
|
-
e.profiles && localStorage.setItem(
|
|
51
|
+
e.profiles && localStorage.setItem(s, JSON.stringify(e.profiles)), sessionStorage.setItem(c, t);
|
|
62
52
|
}
|
|
63
53
|
}
|
|
64
|
-
function
|
|
65
|
-
let e = sessionStorage.getItem(
|
|
54
|
+
function p() {
|
|
55
|
+
let e = sessionStorage.getItem(c);
|
|
66
56
|
if (e) {
|
|
67
|
-
localStorage.removeItem(`${
|
|
57
|
+
localStorage.removeItem(`${o}-${e}-user`), localStorage.removeItem(`${o}-${e}-tokens`), localStorage.removeItem(`${o}-${e}-workspace-token`), localStorage.removeItem(`${o}-${e}-auth-storage`);
|
|
68
58
|
try {
|
|
69
|
-
let t = localStorage.getItem(
|
|
70
|
-
localStorage.setItem(
|
|
59
|
+
let t = localStorage.getItem(s), n = t ? JSON.parse(t) : [];
|
|
60
|
+
localStorage.setItem(s, JSON.stringify(n.filter((t) => t.id !== e)));
|
|
71
61
|
} catch {}
|
|
72
|
-
sessionStorage.removeItem(
|
|
62
|
+
sessionStorage.removeItem(c);
|
|
73
63
|
}
|
|
74
64
|
localStorage.removeItem("auth-storage");
|
|
75
65
|
}
|
|
76
|
-
function
|
|
66
|
+
function m() {
|
|
77
67
|
try {
|
|
78
|
-
let e = localStorage.getItem(
|
|
79
|
-
for (let e of t) localStorage.removeItem(`${
|
|
68
|
+
let e = localStorage.getItem(s), t = e ? JSON.parse(e) : [];
|
|
69
|
+
for (let e of t) localStorage.removeItem(`${o}-${e.id}-user`), localStorage.removeItem(`${o}-${e.id}-tokens`), localStorage.removeItem(`${o}-${e.id}-workspace-token`), localStorage.removeItem(`${o}-${e.id}-auth-storage`), localStorage.removeItem(`${o}-${e.id}-context`);
|
|
80
70
|
} catch {}
|
|
81
|
-
for (let e of Object.keys(localStorage)) e.startsWith(`${
|
|
82
|
-
localStorage.removeItem(
|
|
71
|
+
for (let e of Object.keys(localStorage)) e.startsWith(`${o}-`) && e.endsWith("-auth-storage") && localStorage.removeItem(e);
|
|
72
|
+
localStorage.removeItem(s), localStorage.removeItem("auth-storage"), sessionStorage.removeItem(c);
|
|
83
73
|
}
|
|
84
|
-
var
|
|
85
|
-
function
|
|
74
|
+
var h = "bf-sso-profile", g = "bf-sso";
|
|
75
|
+
function _(e, t) {
|
|
86
76
|
try {
|
|
87
77
|
let n = window.location.hostname.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0, r = n ? `; domain=${n}` : "", i = window.location.protocol === "https:" ? "; secure" : "", a = JSON.stringify({
|
|
88
78
|
id: e,
|
|
89
79
|
origin: t
|
|
90
80
|
});
|
|
91
|
-
document.cookie = `${
|
|
81
|
+
document.cookie = `${h}=${encodeURIComponent(a)}${r}; path=/; samesite=lax${i}; max-age=86400`;
|
|
92
82
|
} catch {}
|
|
93
83
|
}
|
|
94
|
-
function
|
|
84
|
+
function v(e, t) {
|
|
95
85
|
try {
|
|
96
86
|
let n = window.location.hostname.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0, r = n ? `; domain=${n}` : "", i = window.location.protocol === "https:" ? "; secure" : "", a = JSON.stringify({
|
|
97
87
|
id: e,
|
|
98
88
|
origin: t
|
|
99
89
|
});
|
|
100
|
-
document.cookie = `${
|
|
90
|
+
document.cookie = `${g}=${encodeURIComponent(a)}${r}; path=/; samesite=lax${i}; max-age=86400`;
|
|
101
91
|
} catch {}
|
|
102
92
|
}
|
|
103
|
-
function
|
|
93
|
+
function y() {
|
|
104
94
|
try {
|
|
105
95
|
let e = window.location.hostname, t = e.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0;
|
|
106
|
-
document.cookie = `${
|
|
96
|
+
document.cookie = `${h}=; path=/; max-age=0`, document.cookie = `${g}=; path=/; max-age=0`, t && (document.cookie = `${h}=; path=/; domain=${t}; max-age=0`, document.cookie = `${g}=; path=/; domain=${t}; max-age=0`), e && !["localhost", "127.0.0.1"].includes(e) && (document.cookie = `${h}=; path=/; domain=${e}; max-age=0`, document.cookie = `${g}=; path=/; domain=${e}; max-age=0`);
|
|
107
97
|
} catch {}
|
|
108
98
|
}
|
|
109
|
-
function
|
|
110
|
-
let
|
|
111
|
-
return
|
|
99
|
+
function b() {
|
|
100
|
+
let c = n(/* @__PURE__ */ new Map()), h = n(/* @__PURE__ */ new Map()), g = n(!1);
|
|
101
|
+
return t(() => {
|
|
112
102
|
async function e() {
|
|
113
103
|
try {
|
|
114
104
|
if (!document.requestStorageAccess) {
|
|
@@ -125,95 +115,95 @@ function x() {
|
|
|
125
115
|
}
|
|
126
116
|
}
|
|
127
117
|
e();
|
|
128
|
-
}, []),
|
|
129
|
-
function
|
|
130
|
-
return
|
|
118
|
+
}, []), t(() => {
|
|
119
|
+
function t(t) {
|
|
120
|
+
return e(t);
|
|
131
121
|
}
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
|
|
122
|
+
function n(e, t) {
|
|
123
|
+
c.current.forEach((n, r) => {
|
|
124
|
+
r !== t && n.source.postMessage({
|
|
135
125
|
...e,
|
|
136
|
-
source:
|
|
126
|
+
source: i,
|
|
137
127
|
nonce: n.nonce
|
|
138
128
|
}, n.origin);
|
|
139
129
|
});
|
|
140
130
|
}
|
|
141
|
-
function
|
|
142
|
-
if (!e
|
|
143
|
-
let
|
|
131
|
+
function r(e) {
|
|
132
|
+
if (!t(e.origin) || !u(e.data) || e.data.source !== a) return;
|
|
133
|
+
let r = e.data, o = l(e.source) ? e.source : null;
|
|
144
134
|
if (!o) return;
|
|
145
|
-
let
|
|
146
|
-
if (
|
|
147
|
-
if (!
|
|
148
|
-
} else if (!
|
|
149
|
-
switch (
|
|
135
|
+
let s = typeof r.nonce == "string" && r.nonce.length > 0 ? r.nonce : null, g = c.current.get(o);
|
|
136
|
+
if (r.type !== "AUTH_INIT") {
|
|
137
|
+
if (!s || g?.nonce !== s) return;
|
|
138
|
+
} else if (!s) return;
|
|
139
|
+
switch (r.type) {
|
|
150
140
|
case "AUTH_INIT": {
|
|
151
|
-
if (
|
|
141
|
+
if (c.current.set(o, {
|
|
152
142
|
source: o,
|
|
153
|
-
origin:
|
|
154
|
-
nonce:
|
|
155
|
-
}),
|
|
156
|
-
let e =
|
|
157
|
-
e.user && e.tokens &&
|
|
143
|
+
origin: e.origin,
|
|
144
|
+
nonce: s
|
|
145
|
+
}), r.payload && typeof r.payload == "object") {
|
|
146
|
+
let e = r.payload;
|
|
147
|
+
e.user && e.tokens && f(e);
|
|
158
148
|
}
|
|
159
|
-
let
|
|
160
|
-
|
|
149
|
+
let t = d(), n = c.current.get(o);
|
|
150
|
+
n && (n.source.postMessage({
|
|
161
151
|
type: "AUTH_STATE",
|
|
162
|
-
payload:
|
|
163
|
-
source:
|
|
164
|
-
nonce:
|
|
165
|
-
},
|
|
152
|
+
payload: t,
|
|
153
|
+
source: i,
|
|
154
|
+
nonce: n.nonce
|
|
155
|
+
}, e.origin), h.current.get(o) !== n.nonce && (h.current.set(o, n.nonce), n.source.postMessage({
|
|
166
156
|
type: "AUTH_BRIDGE_READY",
|
|
167
|
-
source:
|
|
168
|
-
nonce:
|
|
169
|
-
},
|
|
157
|
+
source: i,
|
|
158
|
+
nonce: n.nonce
|
|
159
|
+
}, e.origin)));
|
|
170
160
|
break;
|
|
171
161
|
}
|
|
172
162
|
case "AUTH_LOGIN":
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
let
|
|
176
|
-
|
|
163
|
+
if (r.payload) {
|
|
164
|
+
f(r.payload);
|
|
165
|
+
let t = r.payload, i = t.activeProfileId ?? t.user?.id;
|
|
166
|
+
i && (y(), _(i, e.origin), v(i, e.origin)), n({
|
|
177
167
|
type: "AUTH_STATE_CHANGED",
|
|
178
168
|
payload: {
|
|
179
|
-
...
|
|
169
|
+
...d(),
|
|
180
170
|
trigger: "login"
|
|
181
171
|
}
|
|
182
172
|
}, o);
|
|
183
173
|
}
|
|
184
174
|
break;
|
|
185
175
|
case "AUTH_LOGOUT":
|
|
186
|
-
|
|
176
|
+
p(), y(), n({
|
|
187
177
|
type: "AUTH_STATE_CHANGED",
|
|
188
178
|
payload: {
|
|
189
|
-
...
|
|
179
|
+
...d(),
|
|
190
180
|
trigger: "logout"
|
|
191
181
|
}
|
|
192
182
|
}, o);
|
|
193
183
|
break;
|
|
194
184
|
case "AUTH_LOGOUT_ALL":
|
|
195
|
-
|
|
185
|
+
m(), y(), n({
|
|
196
186
|
type: "AUTH_STATE_CHANGED",
|
|
197
187
|
payload: {
|
|
198
|
-
...
|
|
188
|
+
...d(),
|
|
199
189
|
trigger: "logout_all"
|
|
200
190
|
}
|
|
201
191
|
}, o);
|
|
202
192
|
break;
|
|
203
193
|
case "AUTH_TOKEN_REFRESH":
|
|
204
|
-
|
|
194
|
+
r.payload?.tokens && (f({ tokens: r.payload.tokens }), n({
|
|
205
195
|
type: "AUTH_STATE_CHANGED",
|
|
206
196
|
payload: {
|
|
207
|
-
...
|
|
197
|
+
...d(),
|
|
208
198
|
trigger: "refresh"
|
|
209
199
|
}
|
|
210
200
|
}, o));
|
|
211
201
|
break;
|
|
212
202
|
case "AUTH_ACCOUNT_SWITCH":
|
|
213
|
-
|
|
203
|
+
r.payload && (f(r.payload), n({
|
|
214
204
|
type: "AUTH_STATE_CHANGED",
|
|
215
205
|
payload: {
|
|
216
|
-
...
|
|
206
|
+
...d(),
|
|
217
207
|
trigger: "account_switch"
|
|
218
208
|
}
|
|
219
209
|
}, o));
|
|
@@ -221,24 +211,24 @@ function x() {
|
|
|
221
211
|
}
|
|
222
212
|
}
|
|
223
213
|
function g(e) {
|
|
224
|
-
(e.key?.startsWith(
|
|
214
|
+
(e.key?.startsWith(o) || e.key === s) && n({
|
|
225
215
|
type: "AUTH_STATE_CHANGED",
|
|
226
216
|
payload: {
|
|
227
|
-
...
|
|
217
|
+
...d(),
|
|
228
218
|
trigger: "storage_sync"
|
|
229
219
|
}
|
|
230
220
|
});
|
|
231
221
|
}
|
|
232
|
-
return window.addEventListener("message",
|
|
233
|
-
window.removeEventListener("message",
|
|
222
|
+
return window.addEventListener("message", r), window.addEventListener("storage", g), () => {
|
|
223
|
+
window.removeEventListener("message", r), window.removeEventListener("storage", g);
|
|
234
224
|
};
|
|
235
|
-
}, []), /* @__PURE__ */
|
|
225
|
+
}, []), /* @__PURE__ */ r("div", {
|
|
236
226
|
id: "auth-session-bridge-root",
|
|
237
227
|
"data-bridge": "active",
|
|
238
228
|
style: { display: "none" }
|
|
239
229
|
});
|
|
240
230
|
}
|
|
241
231
|
//#endregion
|
|
242
|
-
export {
|
|
232
|
+
export { b as SessionBridgePage };
|
|
243
233
|
|
|
244
234
|
//# sourceMappingURL=SessionBridgePage.js.map
|