@burdenoff/microfe-auth 2026.718.1 → 2026.718.2
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/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 +56 -61
- 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 +1 -1
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { isAllowedSsoOrigin as e } from "../utils/ssoOrigins.js";
|
|
2
|
+
import { useEffect as t } from "react";
|
|
3
|
+
import { clearLocalAuthState as n } from "@burdenoff/fe-libs/shared/components";
|
|
4
|
+
import { useSearchParams as r } from "react-router-dom";
|
|
5
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
6
|
+
import { clearShellSsoCookies as a } from "@burdenoff/fe-libs/shared/utils";
|
|
7
|
+
import { BrandedSplash as o } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
import { getAuthBridge as s } from "@burdenoff/fe-libs/shared/providers/shell/AuthBridgeClient";
|
|
8
9
|
//#region src/auth/pages/SSOClearPage.tsx
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (e.hostname === "localhost" || e.hostname === "127.0.0.1" || e.hostname.endsWith(".local.burdenoff.com")) return !0;
|
|
12
|
-
if (e.protocol !== "https:") return !1;
|
|
13
|
-
for (let t of s) 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;
|
|
14
|
-
return !1;
|
|
15
|
-
}
|
|
16
|
-
function l(e) {
|
|
17
|
-
if (!e) return "/auth/login?reason=logout";
|
|
10
|
+
function c(t) {
|
|
11
|
+
if (!t) return "/auth/login?reason=logout";
|
|
18
12
|
try {
|
|
19
|
-
let
|
|
20
|
-
if (
|
|
13
|
+
let n = new URL(t, window.location.origin);
|
|
14
|
+
if (n.origin === window.location.origin || e(n)) return n.href;
|
|
21
15
|
} catch {}
|
|
22
16
|
return "/auth/login?reason=logout";
|
|
23
17
|
}
|
|
24
|
-
function
|
|
25
|
-
let [
|
|
26
|
-
return
|
|
27
|
-
let
|
|
18
|
+
function l() {
|
|
19
|
+
let [e] = r();
|
|
20
|
+
return t(() => {
|
|
21
|
+
let t = c(e.get("returnTo"));
|
|
28
22
|
try {
|
|
29
|
-
|
|
23
|
+
n(), a(), s()?.notifyLogoutAll();
|
|
30
24
|
} catch {}
|
|
31
|
-
window.location.replace(
|
|
32
|
-
}, [
|
|
25
|
+
window.location.replace(t);
|
|
26
|
+
}, [e]), /* @__PURE__ */ i(o, { label: "Signing you out…" });
|
|
33
27
|
}
|
|
34
28
|
//#endregion
|
|
35
|
-
export {
|
|
29
|
+
export { l as SSOClearPage };
|
|
36
30
|
|
|
37
31
|
//# sourceMappingURL=SSOClearPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSOClearPage.js","names":[],"sources":["../../../src/auth/pages/SSOClearPage.tsx"],"sourcesContent":["/**\n * SSOClearPage — Cross-Domain Logout at the Central Bridge Host\n *\n * Companion to SSODepositPage. When a user signs out on a product origin,\n * the product app cleared its own localStorage / cookies but the bridge\n * host (alphaapp/app.burdenoff.com) still held the deposited session in\n * first-party (unpartitioned) localStorage. A later visit to any product\n * would then bridge-host-relay that stale session back, effectively\n * undoing the logout.\n *\n * AuthProvider.logout() / logoutAll() on a non-bridge-host origin redirect\n * here via a top-level navigation:\n *\n * product.com (logout)\n * → app.burdenoff.com/auth/sso-clear?returnTo=<encoded-return-url>\n * — wipes bridge-host localStorage + bf-sso cookie\n * → <returnTo>\n *\n * URL: /auth/sso-clear\n */\n\nimport { useEffect } from \"react\";\nimport { BrandedSplash } from \"@burdenoff/fe-libs/ui\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { clearShellSsoCookies } from \"@burdenoff/fe-libs/shared/utils\";\nimport { clearLocalAuthState } from \"@burdenoff/fe-libs/shared/components\";\nimport { getAuthBridge } from \"@burdenoff/fe-libs/shared/providers/shell/AuthBridgeClient\";\
|
|
1
|
+
{"version":3,"file":"SSOClearPage.js","names":[],"sources":["../../../src/auth/pages/SSOClearPage.tsx"],"sourcesContent":["/**\n * SSOClearPage — Cross-Domain Logout at the Central Bridge Host\n *\n * Companion to SSODepositPage. When a user signs out on a product origin,\n * the product app cleared its own localStorage / cookies but the bridge\n * host (alphaapp/app.burdenoff.com) still held the deposited session in\n * first-party (unpartitioned) localStorage. A later visit to any product\n * would then bridge-host-relay that stale session back, effectively\n * undoing the logout.\n *\n * AuthProvider.logout() / logoutAll() on a non-bridge-host origin redirect\n * here via a top-level navigation:\n *\n * product.com (logout)\n * → app.burdenoff.com/auth/sso-clear?returnTo=<encoded-return-url>\n * — wipes bridge-host localStorage + bf-sso cookie\n * → <returnTo>\n *\n * URL: /auth/sso-clear\n */\n\nimport { useEffect } from \"react\";\nimport { BrandedSplash } from \"@burdenoff/fe-libs/ui\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { clearShellSsoCookies } from \"@burdenoff/fe-libs/shared/utils\";\nimport { clearLocalAuthState } from \"@burdenoff/fe-libs/shared/components\";\nimport { getAuthBridge } from \"@burdenoff/fe-libs/shared/providers/shell/AuthBridgeClient\";\nimport { isAllowedSsoOrigin } from \"../utils/ssoOrigins\";\n\nfunction resolveSafeReturn(returnTo: string | null): string {\n if (!returnTo) return \"/auth/login?reason=logout\";\n try {\n const url = new URL(returnTo, window.location.origin);\n if (url.origin === window.location.origin || isAllowedSsoOrigin(url)) {\n return url.href;\n }\n } catch {\n /* ignore */\n }\n return \"/auth/login?reason=logout\";\n}\n\nexport function SSOClearPage() {\n const [searchParams] = useSearchParams();\n\n useEffect(() => {\n const returnTo = resolveSafeReturn(searchParams.get(\"returnTo\"));\n\n try {\n clearLocalAuthState();\n clearShellSsoCookies();\n // Broadcast to any same-origin shells still listening so they drop the\n // session immediately too (best-effort — only fires on the bridge\n // host's origin, which is the only place where iframe storage isn't\n // partitioned cross-product).\n getAuthBridge()?.notifyLogoutAll();\n } catch {\n /* ignore — proceed with the redirect regardless */\n }\n\n window.location.replace(returnTo);\n }, [searchParams]);\n\n return <BrandedSplash label=\"Signing you out…\" />;\n}\n"],"mappings":";;;;;;;;;AA6BA,SAAS,EAAkB,GAAiC;AAC1D,KAAI,CAAC,EAAU,QAAO;AACtB,KAAI;EACF,IAAM,IAAM,IAAI,IAAI,GAAU,OAAO,SAAS,OAAO;AACrD,MAAI,EAAI,WAAW,OAAO,SAAS,UAAU,EAAmB,EAAI,CAClE,QAAO,EAAI;SAEP;AAGR,QAAO;;AAGT,SAAgB,IAAe;CAC7B,IAAM,CAAC,KAAgB,GAAiB;AAoBxC,QAlBA,QAAgB;EACd,IAAM,IAAW,EAAkB,EAAa,IAAI,WAAW,CAAC;AAEhE,MAAI;AAOF,GANA,GAAqB,EACrB,GAAsB,EAKtB,GAAe,EAAE,iBAAiB;UAC5B;AAIR,SAAO,SAAS,QAAQ,EAAS;IAChC,CAAC,EAAa,CAAC,EAEX,kBAAC,GAAD,EAAe,OAAM,oBAAqB,CAAA"}
|
|
@@ -1,52 +1,46 @@
|
|
|
1
1
|
import { useAuthStore as e } from "../store/authStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { isAllowedSsoOrigin as t } from "../utils/ssoOrigins.js";
|
|
3
|
+
import { useEffect as n } from "react";
|
|
4
|
+
import { useSearchParams as r } from "react-router-dom";
|
|
5
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
6
|
+
import { setSSoCookie as a } from "@burdenoff/fe-libs/shared/utils";
|
|
7
|
+
import { BrandedSplash as o } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
import { getAuthBridge as s } from "@burdenoff/fe-libs/shared/providers/shell/AuthBridgeClient";
|
|
8
9
|
//#region src/auth/pages/SSODepositPage.tsx
|
|
9
|
-
var s = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,botlit.ai,bigconsole.com,gethealthybowl.com,headshotmarketing.com,timecampus.com,ggnomad.com,movethewheels.com,crewfoundry.com,planmagnet.com,eventfullymanaged.com,kadaikodi.com,assethandler.com,technospam.com,semanticfed.com,manufacturedops.com,theglobalfuel.com,housingvista.com,proserviceworld.com,mybodyshield.com,artistrybase.com,collabkin.com,brainyrich.com,theculturepost.com,comradecircle.com,cosmicintersection.com,govcitizenhub.com,intelwatchtower.com,ecoimpacthub.com,buildmyiq.com,fluidgrids.ai,atheletebridge.com,adaptercloud.com,coolandlovely.com,subscriberbot.com,labsofscience.com,hookmovies.com".split(","));
|
|
10
10
|
function c(e) {
|
|
11
|
-
if (e.hostname === "localhost" || e.hostname === "127.0.0.1" || e.hostname.endsWith(".local.burdenoff.com")) return !0;
|
|
12
|
-
if (e.protocol !== "https:") return !1;
|
|
13
|
-
for (let t of s) 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;
|
|
14
|
-
return !1;
|
|
15
|
-
}
|
|
16
|
-
function l(e) {
|
|
17
11
|
if (!e) return "/";
|
|
18
12
|
try {
|
|
19
|
-
let
|
|
20
|
-
if (
|
|
13
|
+
let n = new URL(e, window.location.origin);
|
|
14
|
+
if (n.origin === window.location.origin || t(n)) return n.href;
|
|
21
15
|
} catch {}
|
|
22
16
|
return "/";
|
|
23
17
|
}
|
|
24
|
-
function
|
|
25
|
-
let [
|
|
26
|
-
return
|
|
27
|
-
let
|
|
28
|
-
if (!
|
|
29
|
-
window.location.replace(
|
|
18
|
+
function l() {
|
|
19
|
+
let [t] = r();
|
|
20
|
+
return n(() => {
|
|
21
|
+
let n = c(t.get("returnTo")), r = window.location.hash;
|
|
22
|
+
if (!r.startsWith("#sso=")) {
|
|
23
|
+
window.location.replace(n);
|
|
30
24
|
return;
|
|
31
25
|
}
|
|
32
26
|
try {
|
|
33
|
-
let
|
|
34
|
-
if (!
|
|
35
|
-
window.location.replace(
|
|
27
|
+
let t = r.slice(5), { profileId: i, tokens: o, user: c, profiles: l } = JSON.parse(atob(t));
|
|
28
|
+
if (!i || !o) {
|
|
29
|
+
window.location.replace(n);
|
|
36
30
|
return;
|
|
37
31
|
}
|
|
38
|
-
localStorage.setItem(`bf-p-${
|
|
32
|
+
localStorage.setItem(`bf-p-${i}-tokens`, o), c && localStorage.setItem(`bf-p-${i}-user`, c), l && localStorage.setItem("bf-profiles", l), sessionStorage.setItem("bf-active-profile", i), a(i), e.getState().loadFromProfile(i), window.dispatchEvent(new Event("bf-profile-storage-change")), s()?.notifyLogin({
|
|
39
33
|
user: c ? JSON.parse(c) : null,
|
|
40
|
-
tokens:
|
|
41
|
-
activeProfileId:
|
|
34
|
+
tokens: o ? JSON.parse(o) : null,
|
|
35
|
+
activeProfileId: i,
|
|
42
36
|
profiles: l ? JSON.parse(l) : []
|
|
43
|
-
}), window.location.replace(
|
|
37
|
+
}), window.location.replace(n);
|
|
44
38
|
} catch {
|
|
45
|
-
window.location.replace(
|
|
39
|
+
window.location.replace(n);
|
|
46
40
|
}
|
|
47
|
-
}, [
|
|
41
|
+
}, [t]), /* @__PURE__ */ i(o, { label: "Signing you in…" });
|
|
48
42
|
}
|
|
49
43
|
//#endregion
|
|
50
|
-
export {
|
|
44
|
+
export { l as SSODepositPage };
|
|
51
45
|
|
|
52
46
|
//# sourceMappingURL=SSODepositPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSODepositPage.js","names":[],"sources":["../../../src/auth/pages/SSODepositPage.tsx"],"sourcesContent":["/**\n * SSODepositPage — Cross-Domain Token Deposit at the Central Bridge Host\n *\n * Loaded as a top-level navigation (NOT an iframe) on the bridge host\n * (alphaapp/app.burdenoff.com) right after a user signs in on any other\n * product origin. It receives the freshly-issued tokens in the URL fragment\n * (`#sso=<base64>` — fragments are never sent to servers) and stores them\n * in this origin's first-party (unpartitioned) localStorage so that:\n *\n * 1. A later top-level visit to the bridge host renders authenticated.\n * 2. Other product origins can discover the session via\n * `/auth/sso-relay?returnTo=...` against this host.\n *\n * Flow:\n * product.com/auth/login → user signs in\n * product.com → POST-success redirect to:\n * app.burdenoff.com/auth/sso-deposit?returnTo=<encoded-return-url>#sso=<base64>\n * → writes tokens here, sets bf-sso cookie\n * <returnTo> → user lands back at intended dashboard\n *\n * URL: /auth/sso-deposit\n */\n\nimport { useEffect } from \"react\";\nimport { BrandedSplash } from \"@burdenoff/fe-libs/ui\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { getAuthBridge } from \"@burdenoff/fe-libs/shared/providers/shell/AuthBridgeClient\";\nimport { setSSoCookie } from \"@burdenoff/fe-libs/shared/utils\";\nimport { useAuthStore } from \"../store/authStore\";\
|
|
1
|
+
{"version":3,"file":"SSODepositPage.js","names":[],"sources":["../../../src/auth/pages/SSODepositPage.tsx"],"sourcesContent":["/**\n * SSODepositPage — Cross-Domain Token Deposit at the Central Bridge Host\n *\n * Loaded as a top-level navigation (NOT an iframe) on the bridge host\n * (alphaapp/app.burdenoff.com) right after a user signs in on any other\n * product origin. It receives the freshly-issued tokens in the URL fragment\n * (`#sso=<base64>` — fragments are never sent to servers) and stores them\n * in this origin's first-party (unpartitioned) localStorage so that:\n *\n * 1. A later top-level visit to the bridge host renders authenticated.\n * 2. Other product origins can discover the session via\n * `/auth/sso-relay?returnTo=...` against this host.\n *\n * Flow:\n * product.com/auth/login → user signs in\n * product.com → POST-success redirect to:\n * app.burdenoff.com/auth/sso-deposit?returnTo=<encoded-return-url>#sso=<base64>\n * → writes tokens here, sets bf-sso cookie\n * <returnTo> → user lands back at intended dashboard\n *\n * URL: /auth/sso-deposit\n */\n\nimport { useEffect } from \"react\";\nimport { BrandedSplash } from \"@burdenoff/fe-libs/ui\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { getAuthBridge } from \"@burdenoff/fe-libs/shared/providers/shell/AuthBridgeClient\";\nimport { setSSoCookie } from \"@burdenoff/fe-libs/shared/utils\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { isAllowedSsoOrigin } from \"../utils/ssoOrigins\";\n\nfunction resolveSafeReturn(returnTo: string | null): string {\n if (!returnTo) return \"/\";\n try {\n const url = new URL(returnTo, window.location.origin);\n if (url.origin === window.location.origin || isAllowedSsoOrigin(url)) {\n return url.href;\n }\n } catch {\n /* ignore */\n }\n return \"/\";\n}\n\nexport function SSODepositPage() {\n const [searchParams] = useSearchParams();\n\n useEffect(() => {\n const returnTo = resolveSafeReturn(searchParams.get(\"returnTo\"));\n const hash = window.location.hash;\n\n if (!hash.startsWith(\"#sso=\")) {\n window.location.replace(returnTo);\n return;\n }\n\n try {\n const encoded = hash.slice(5);\n const payload = JSON.parse(atob(encoded)) as {\n profileId?: string;\n tokens?: string;\n user?: string;\n profiles?: string;\n };\n const { profileId, tokens, user, profiles } = payload;\n\n if (!profileId || !tokens) {\n window.location.replace(returnTo);\n return;\n }\n\n // First-party (unpartitioned) writes on the bridge host origin —\n // future top-level visits here see the session.\n localStorage.setItem(`bf-p-${profileId}-tokens`, tokens);\n if (user) {\n localStorage.setItem(`bf-p-${profileId}-user`, user);\n }\n if (profiles) {\n localStorage.setItem(\"bf-profiles\", profiles);\n }\n sessionStorage.setItem(\"bf-active-profile\", profileId);\n setSSoCookie(profileId);\n useAuthStore.getState().loadFromProfile(profileId);\n window.dispatchEvent(new Event(\"bf-profile-storage-change\"));\n\n // Notify the same-origin auth bridge so any other open shells on\n // this origin pick up the new session immediately.\n getAuthBridge()?.notifyLogin({\n user: user ? (JSON.parse(user) as Record<string, unknown>) : null,\n tokens: tokens ? (JSON.parse(tokens) as { accessToken: string }) : null,\n activeProfileId: profileId,\n profiles: profiles\n ? (JSON.parse(profiles) as Array<Record<string, unknown>>)\n : [],\n });\n\n window.location.replace(returnTo);\n } catch {\n window.location.replace(returnTo);\n }\n }, [searchParams]);\n\n return <BrandedSplash label=\"Signing you in…\" />;\n}\n"],"mappings":";;;;;;;;;AA+BA,SAAS,EAAkB,GAAiC;AAC1D,KAAI,CAAC,EAAU,QAAO;AACtB,KAAI;EACF,IAAM,IAAM,IAAI,IAAI,GAAU,OAAO,SAAS,OAAO;AACrD,MAAI,EAAI,WAAW,OAAO,SAAS,UAAU,EAAmB,EAAI,CAClE,QAAO,EAAI;SAEP;AAGR,QAAO;;AAGT,SAAgB,IAAiB;CAC/B,IAAM,CAAC,KAAgB,GAAiB;AAyDxC,QAvDA,QAAgB;EACd,IAAM,IAAW,EAAkB,EAAa,IAAI,WAAW,CAAC,EAC1D,IAAO,OAAO,SAAS;AAE7B,MAAI,CAAC,EAAK,WAAW,QAAQ,EAAE;AAC7B,UAAO,SAAS,QAAQ,EAAS;AACjC;;AAGF,MAAI;GACF,IAAM,IAAU,EAAK,MAAM,EAAE,EAOvB,EAAE,cAAW,WAAQ,SAAM,gBANjB,KAAK,MAAM,KAAK,EAAQ,CAAC;AAQzC,OAAI,CAAC,KAAa,CAAC,GAAQ;AACzB,WAAO,SAAS,QAAQ,EAAS;AACjC;;AA4BF,GAvBA,aAAa,QAAQ,QAAQ,EAAU,UAAU,EAAO,EACpD,KACF,aAAa,QAAQ,QAAQ,EAAU,QAAQ,EAAK,EAElD,KACF,aAAa,QAAQ,eAAe,EAAS,EAE/C,eAAe,QAAQ,qBAAqB,EAAU,EACtD,EAAa,EAAU,EACvB,EAAa,UAAU,CAAC,gBAAgB,EAAU,EAClD,OAAO,cAAc,IAAI,MAAM,4BAA4B,CAAC,EAI5D,GAAe,EAAE,YAAY;IAC3B,MAAM,IAAQ,KAAK,MAAM,EAAK,GAA+B;IAC7D,QAAQ,IAAU,KAAK,MAAM,EAAO,GAA+B;IACnE,iBAAiB;IACjB,UAAU,IACL,KAAK,MAAM,EAAS,GACrB,EAAE;IACP,CAAC,EAEF,OAAO,SAAS,QAAQ,EAAS;UAC3B;AACN,UAAO,SAAS,QAAQ,EAAS;;IAElC,CAAC,EAAa,CAAC,EAEX,kBAAC,GAAD,EAAe,OAAM,mBAAoB,CAAA"}
|
|
@@ -1,42 +1,37 @@
|
|
|
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";
|
|
4
5
|
//#region src/auth/pages/SSORelayPage.tsx
|
|
5
|
-
var
|
|
6
|
+
var i = "relayChain", a = "sso_failed", o = new Set(["localhost", "127.0.0.1"]), s = {
|
|
6
7
|
"bf-sso": /(?:^|;\s*)bf-sso=([^;]*)/,
|
|
7
8
|
"bf-sso-profile": /(?:^|;\s*)bf-sso-profile=([^;]*)/
|
|
8
9
|
};
|
|
9
|
-
function c(
|
|
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() {
|
|
10
|
+
function c() {
|
|
16
11
|
try {
|
|
17
12
|
let e = window.location.hostname, t = e.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0;
|
|
18
13
|
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 && !o.has(e) && (document.cookie = `${n}=; path=/; domain=${e}; max-age=0`);
|
|
19
14
|
} catch {}
|
|
20
15
|
}
|
|
21
|
-
function
|
|
16
|
+
function l() {
|
|
22
17
|
try {
|
|
23
18
|
return (new URLSearchParams(window.location.search).get(i) || "").split("|").filter((e) => e.length > 0);
|
|
24
19
|
} catch {
|
|
25
20
|
return [];
|
|
26
21
|
}
|
|
27
22
|
}
|
|
28
|
-
function
|
|
23
|
+
function u(t) {
|
|
29
24
|
try {
|
|
30
|
-
let
|
|
31
|
-
return
|
|
25
|
+
let n = new URL(t, window.location.origin);
|
|
26
|
+
return n.origin !== window.location.origin && !e(n) ? "/auth/login" : (n.searchParams.set(a, "1"), n.href);
|
|
32
27
|
} catch {
|
|
33
28
|
return "/auth/login?sso_failed=1";
|
|
34
29
|
}
|
|
35
30
|
}
|
|
36
|
-
function
|
|
37
|
-
return
|
|
31
|
+
function d() {
|
|
32
|
+
return f()?.id ?? null;
|
|
38
33
|
}
|
|
39
|
-
function
|
|
34
|
+
function f() {
|
|
40
35
|
try {
|
|
41
36
|
for (let e of ["bf-sso", "bf-sso-profile"]) {
|
|
42
37
|
let t = document.cookie.match(s[e]);
|
|
@@ -56,36 +51,36 @@ function p() {
|
|
|
56
51
|
}
|
|
57
52
|
return null;
|
|
58
53
|
}
|
|
59
|
-
function
|
|
54
|
+
function p(t) {
|
|
60
55
|
try {
|
|
61
|
-
let
|
|
62
|
-
if (!
|
|
63
|
-
let
|
|
64
|
-
if (!
|
|
65
|
-
let
|
|
66
|
-
if (
|
|
67
|
-
let
|
|
68
|
-
return
|
|
56
|
+
let n = f();
|
|
57
|
+
if (!n?.origin) return !1;
|
|
58
|
+
let r = new URL(n.origin);
|
|
59
|
+
if (!e(r) || r.origin === window.location.origin) return !1;
|
|
60
|
+
let a = l();
|
|
61
|
+
if (a.includes(r.origin) || a.length >= 3) return !1;
|
|
62
|
+
let o = new URL("/auth/sso-relay", r.origin);
|
|
63
|
+
return o.searchParams.set("returnTo", t), o.searchParams.set(i, Array.from(new Set([...a, window.location.origin])).join("|")), window.location.replace(o.href), !0;
|
|
69
64
|
} catch {
|
|
70
65
|
return !1;
|
|
71
66
|
}
|
|
72
67
|
}
|
|
73
|
-
function
|
|
68
|
+
function m() {
|
|
74
69
|
return localStorage.getItem("bf-profiles");
|
|
75
70
|
}
|
|
76
|
-
function
|
|
71
|
+
function h() {
|
|
77
72
|
try {
|
|
78
|
-
let e =
|
|
73
|
+
let e = m();
|
|
79
74
|
return (e ? JSON.parse(e) : []).map((e) => e.id).filter((e) => typeof e == "string" && e.length > 0);
|
|
80
75
|
} catch {
|
|
81
76
|
return [];
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
|
-
function
|
|
79
|
+
function g() {
|
|
85
80
|
let e = [
|
|
86
|
-
|
|
81
|
+
d(),
|
|
87
82
|
sessionStorage.getItem("bf-active-profile"),
|
|
88
|
-
...
|
|
83
|
+
...h()
|
|
89
84
|
].filter((e) => typeof e == "string" && e.length > 0);
|
|
90
85
|
for (let t of Array.from(new Set(e))) {
|
|
91
86
|
let e = localStorage.getItem(`bf-p-${t}-tokens`), n = localStorage.getItem(`bf-p-${t}-user`);
|
|
@@ -93,51 +88,51 @@ function _() {
|
|
|
93
88
|
}
|
|
94
89
|
return null;
|
|
95
90
|
}
|
|
96
|
-
function
|
|
97
|
-
let [
|
|
98
|
-
return
|
|
99
|
-
let
|
|
100
|
-
if (!
|
|
91
|
+
function _() {
|
|
92
|
+
let [i] = n();
|
|
93
|
+
return t(() => {
|
|
94
|
+
let t = i.get("returnTo");
|
|
95
|
+
if (!t) {
|
|
101
96
|
window.location.replace("/");
|
|
102
97
|
return;
|
|
103
98
|
}
|
|
104
|
-
let
|
|
105
|
-
if (!
|
|
106
|
-
if (
|
|
107
|
-
|
|
99
|
+
let n = g();
|
|
100
|
+
if (!n) {
|
|
101
|
+
if (p(t)) return;
|
|
102
|
+
c(), window.location.replace(u(t));
|
|
108
103
|
return;
|
|
109
104
|
}
|
|
110
|
-
let
|
|
111
|
-
if (!
|
|
112
|
-
if (
|
|
113
|
-
|
|
105
|
+
let r = localStorage.getItem(`bf-p-${n}-tokens`), a = localStorage.getItem(`bf-p-${n}-user`), o = m();
|
|
106
|
+
if (!r || !a) {
|
|
107
|
+
if (p(t)) return;
|
|
108
|
+
c(), window.location.replace(u(t));
|
|
114
109
|
return;
|
|
115
110
|
}
|
|
116
|
-
let
|
|
117
|
-
profileId:
|
|
118
|
-
tokens:
|
|
119
|
-
user:
|
|
120
|
-
profiles:
|
|
121
|
-
})),
|
|
111
|
+
let s = btoa(JSON.stringify({
|
|
112
|
+
profileId: n,
|
|
113
|
+
tokens: r,
|
|
114
|
+
user: a,
|
|
115
|
+
profiles: o
|
|
116
|
+
})), l;
|
|
122
117
|
try {
|
|
123
|
-
let
|
|
124
|
-
if (!
|
|
118
|
+
let n = new URL(t);
|
|
119
|
+
if (!e(n)) {
|
|
125
120
|
window.location.replace("/");
|
|
126
121
|
return;
|
|
127
122
|
}
|
|
128
|
-
let
|
|
129
|
-
|
|
123
|
+
let r = n.pathname + n.search;
|
|
124
|
+
l = `${n.origin}/auth/sso-callback?returnTo=${encodeURIComponent(r)}#sso=${s}`;
|
|
130
125
|
} catch {
|
|
131
|
-
window.location.replace(
|
|
126
|
+
window.location.replace(t);
|
|
132
127
|
return;
|
|
133
128
|
}
|
|
134
|
-
window.location.replace(
|
|
135
|
-
}, [
|
|
129
|
+
window.location.replace(l);
|
|
130
|
+
}, [i]), /* @__PURE__ */ r("div", {
|
|
136
131
|
className: "flex items-center justify-center min-h-screen bg-bg-canvas",
|
|
137
|
-
children: /* @__PURE__ */
|
|
132
|
+
children: /* @__PURE__ */ r("div", { className: "animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent" })
|
|
138
133
|
});
|
|
139
134
|
}
|
|
140
135
|
//#endregion
|
|
141
|
-
export {
|
|
136
|
+
export { _ as SSORelayPage };
|
|
142
137
|
|
|
143
138
|
//# 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":"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"}
|
|
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 { 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\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 (!isAllowedSsoOrigin(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":"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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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionBridgePage.js","names":[],"sources":["../../../src/auth/pages/SessionBridgePage.tsx"],"sourcesContent":["/**\n * SessionBridgePage — Cross-Domain Auth Session Bridge (Profile-Scoped)\n *\n * Embedded as hidden iframe by other app shells. Manages profile-scoped\n * auth tokens in localStorage and relays state via postMessage.\n *\n * Storage uses ProfileStorageManager keys:\n * bf-profiles, bf-p-{id}-user, bf-p-{id}-tokens, bf-p-{id}-workspace-token\n * bf-active-profile (sessionStorage, per-tab)\n *\n * URL: /auth/session-bridge\n */\n\nimport { useEffect, useRef } from \"react\";\n\nconst BRIDGE_SOURCE = \"burdenoff-auth-bridge\";\nconst PARENT_SOURCE = \"burdenoff-auth-client\";\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\n\ninterface BridgeMessage {\n type: string;\n payload?: Record<string, unknown>;\n nonce?: string;\n source?: string;\n}\n\ninterface ConnectedParent {\n source: Window;\n origin: string;\n nonce: string;\n}\n\nfunction isWindowMessageSource(\n source: MessageEventSource | null,\n): source is Window {\n return (\n source !== null &&\n typeof (source as { postMessage?: unknown }).postMessage === \"function\"\n );\n}\n\nfunction isBridgeMessage(value: unknown): value is BridgeMessage {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\"\n );\n}\n\n/**\n * Dynamically validates allowed origins for the auth bridge.\n * Accepts all Burdenoff product domains (prod + alpha + local dev).\n */\nconst ALLOWED_PRODUCT_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\nfunction isAllowedOrigin(origin: string): boolean {\n try {\n const url = new URL(origin);\n // Allow all localhost ports (local dev)\n if (url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\") {\n return true;\n }\n // Allow *.local.burdenoff.com (local dev with nginx)\n if (url.hostname.endsWith(\".local.burdenoff.com\")) {\n return true;\n }\n // Allow app.{domain} and alphaapp.{domain} for all known product domains\n for (const domain of ALLOWED_PRODUCT_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)\n // embed this bridge iframe too. Without trusting their origin, the\n // bridge ignores their postMessages and the admin shell never receives\n // the shared session.\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 } catch {\n return false;\n }\n}\n\nfunction readAuthState(): Record<string, unknown> {\n try {\n const profiles = localStorage.getItem(BF_PROFILES);\n let activeId = sessionStorage.getItem(BF_ACTIVE);\n\n // Fall back: if sessionStorage is empty but localStorage has profiles,\n // pick the first profile (same logic as resolveActiveProfileId)\n if (!activeId && profiles) {\n try {\n const profileList = JSON.parse(profiles) as Array<{ id: string }>;\n if (profileList.length > 0) {\n activeId = profileList[0].id;\n sessionStorage.setItem(BF_ACTIVE, activeId);\n }\n } catch {\n // ignore\n }\n }\n\n if (!activeId) {\n return {\n user: null,\n tokens: null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: null,\n };\n }\n const user = localStorage.getItem(`${BF_PREFIX}-${activeId}-user`);\n const tokens = localStorage.getItem(`${BF_PREFIX}-${activeId}-tokens`);\n const wsToken = localStorage.getItem(\n `${BF_PREFIX}-${activeId}-workspace-token`,\n );\n\n return {\n user: user ? JSON.parse(user) : null,\n tokens: tokens\n ? { ...JSON.parse(tokens), workspaceToken: wsToken ?? undefined }\n : null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: activeId,\n };\n } catch {\n return { user: null, tokens: null, profiles: [], activeProfileId: null };\n }\n}\n\nfunction writeAuthState(payload: Record<string, unknown>): void {\n const profileId =\n (payload.activeProfileId as string) ??\n ((payload.user as Record<string, unknown>)?.id as string) ??\n sessionStorage.getItem(BF_ACTIVE);\n if (!profileId) return;\n\n if (payload.user) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-user`,\n JSON.stringify(payload.user),\n );\n }\n if (payload.tokens && typeof payload.tokens === \"object\") {\n const t = payload.tokens as Record<string, unknown>;\n const { workspaceToken, ...rest } = t;\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-tokens`,\n JSON.stringify(rest),\n );\n if (workspaceToken) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-workspace-token`,\n String(workspaceToken),\n );\n }\n }\n if (payload.profiles) {\n localStorage.setItem(BF_PROFILES, JSON.stringify(payload.profiles));\n }\n sessionStorage.setItem(BF_ACTIVE, profileId);\n}\n\nfunction clearAuthState(): void {\n const activeId = sessionStorage.getItem(BF_ACTIVE);\n if (activeId) {\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-auth-storage`);\n // Remove from profiles list\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles = raw ? JSON.parse(raw) : [];\n localStorage.setItem(\n BF_PROFILES,\n JSON.stringify(\n profiles.filter((p: { id: string }) => p.id !== activeId),\n ),\n );\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n }\n localStorage.removeItem(\"auth-storage\");\n}\n\nfunction clearAllAuthState(): void {\n // Get all profiles and remove their data\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles: Array<{ id: string }> = raw ? JSON.parse(raw) : [];\n for (const p of profiles) {\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-auth-storage`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-context`);\n }\n } catch {\n /* ignore */\n }\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(`${BF_PREFIX}-`) && key.endsWith(\"-auth-storage\")) {\n localStorage.removeItem(key);\n }\n }\n localStorage.removeItem(BF_PROFILES);\n localStorage.removeItem(\"auth-storage\");\n sessionStorage.removeItem(BF_ACTIVE);\n}\n\n// SSO cookie helpers (shared domain cookie for local dev fast-path)\nconst SSO_COOKIE_NAME = \"bf-sso-profile\";\nconst SHELL_SSO_COOKIE_NAME = \"bf-sso\";\nfunction setSSoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SSO_COOKIE_NAME}=${encodeURIComponent(cookieValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction setShellSsoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction clearSSoCookieBridge(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n }\n } catch {\n /* ignore */\n }\n}\n\nexport function SessionBridgePage() {\n const connectedParents = useRef<Map<Window, ConnectedParent>>(new Map());\n const readySentTo = useRef<Map<Window, string>>(new Map());\n const storageAccessGranted = useRef(false);\n\n // Request unpartitioned storage access (Storage Access API).\n // This is critical for cross-domain SSO in modern browsers that\n // partition third-party iframe localStorage.\n useEffect(() => {\n async function requestAccess() {\n try {\n // Check if Storage Access API is available\n if (!document.requestStorageAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Check if we already have access\n const hasAccess = await document.hasStorageAccess();\n if (hasAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Request unpartitioned storage access\n await document.requestStorageAccess();\n storageAccessGranted.current = true;\n } catch {\n // Storage Access API not available or denied — fall through\n // to cookie-based fallback. localStorage will be partitioned.\n storageAccessGranted.current = false;\n }\n }\n void requestAccess();\n }, []);\n\n useEffect(() => {\n function isOriginAllowed(origin: string): boolean {\n return isAllowedOrigin(origin);\n }\n\n function broadcastToAll(\n message: BridgeMessage,\n excludeSource?: Window | null,\n ): void {\n connectedParents.current.forEach((connection, parent) => {\n if (parent !== excludeSource) {\n connection.source.postMessage(\n { ...message, source: BRIDGE_SOURCE, nonce: connection.nonce },\n connection.origin,\n );\n }\n });\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!isOriginAllowed(event.origin)) return;\n if (!isBridgeMessage(event.data) || event.data.source !== PARENT_SOURCE) {\n return;\n }\n const message = event.data;\n const parentSource = isWindowMessageSource(event.source)\n ? event.source\n : null;\n if (!parentSource) return;\n\n const incomingNonce =\n typeof message.nonce === \"string\" && message.nonce.length > 0\n ? message.nonce\n : null;\n const existingConnection = connectedParents.current.get(parentSource);\n\n if (message.type !== \"AUTH_INIT\") {\n if (!incomingNonce || existingConnection?.nonce !== incomingNonce) {\n return;\n }\n } else if (!incomingNonce) {\n return;\n }\n\n switch (message.type) {\n case \"AUTH_INIT\": {\n connectedParents.current.set(parentSource, {\n source: parentSource,\n origin: event.origin,\n nonce: incomingNonce,\n });\n\n // If the parent sends its current auth state in AUTH_INIT,\n // store it so other cross-domain shells can pick it up.\n if (message.payload && typeof message.payload === \"object\") {\n const initPayload = message.payload as Record<string, unknown>;\n if (initPayload.user && initPayload.tokens) {\n writeAuthState(initPayload);\n }\n }\n\n const state = readAuthState();\n const connection = connectedParents.current.get(parentSource);\n if (connection) {\n connection.source.postMessage(\n {\n type: \"AUTH_STATE\",\n payload: state,\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n if (readySentTo.current.get(parentSource) !== connection.nonce) {\n readySentTo.current.set(parentSource, connection.nonce);\n connection.source.postMessage(\n {\n type: \"AUTH_BRIDGE_READY\",\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n }\n }\n break;\n }\n case \"AUTH_LOGIN\": {\n if (message.payload) {\n writeAuthState(message.payload);\n // Set SSO cookie for cross-domain fast-path detection\n const loginPayload = message.payload as Record<string, unknown>;\n const loginProfileId =\n (loginPayload.activeProfileId as string) ??\n ((loginPayload.user as Record<string, unknown>)?.id as string);\n if (loginProfileId) {\n clearSSoCookieBridge();\n setSSoCookieBridge(loginProfileId, event.origin);\n setShellSsoCookieBridge(loginProfileId, event.origin);\n }\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"login\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_LOGOUT\": {\n clearAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_LOGOUT_ALL\": {\n clearAllAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout_all\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_TOKEN_REFRESH\": {\n if (message.payload?.tokens) {\n writeAuthState({ tokens: message.payload.tokens });\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"refresh\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_ACCOUNT_SWITCH\": {\n if (message.payload) {\n writeAuthState(message.payload);\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"account_switch\" },\n },\n parentSource,\n );\n }\n break;\n }\n }\n }\n\n function handleStorageChange(event: StorageEvent): void {\n if (event.key?.startsWith(BF_PREFIX) || event.key === BF_PROFILES) {\n broadcastToAll({\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"storage_sync\" },\n });\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n window.addEventListener(\"storage\", handleStorageChange);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n window.removeEventListener(\"storage\", handleStorageChange);\n // Don't clear connectedParents or readySentTo on cleanup to maintain state across StrictMode remounts\n };\n }, []);\n\n return (\n <div\n id=\"auth-session-bridge-root\"\n data-bridge=\"active\"\n style={{ display: \"none\" }}\n />\n );\n}\n"],"mappings":";;;AAeA,IAAM,IAAgB,yBAChB,IAAgB,yBAChB,IAAY,QACZ,IAAc,eACd,IAAY;AAelB,SAAS,EACP,GACkB;AAClB,QACE,MAAW,QACX,OAAQ,EAAqC,eAAgB;;AAIjE,SAAS,EAAgB,GAAwC;AAC/D,QACE,OAAO,KAAU,cACjB,KACA,OAAQ,EAA6B,QAAS;;AAQlD,IAAM,IAA0B,IAAI,IAAI,8qBAwCvC,CAAC;AAEF,SAAS,EAAgB,GAAyB;AAChD,KAAI;EACF,IAAM,IAAM,IAAI,IAAI,EAAO;AAM3B,MAJI,EAAI,aAAa,eAAe,EAAI,aAAa,eAIjD,EAAI,SAAS,SAAS,uBAAuB,CAC/C,QAAO;AAGT,OAAK,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,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,IAAyC;AAChD,KAAI;EACF,IAAM,IAAW,aAAa,QAAQ,EAAY,EAC9C,IAAW,eAAe,QAAQ,EAAU;AAIhD,MAAI,CAAC,KAAY,EACf,KAAI;GACF,IAAM,IAAc,KAAK,MAAM,EAAS;AACxC,GAAI,EAAY,SAAS,MACvB,IAAW,EAAY,GAAG,IAC1B,eAAe,QAAQ,GAAW,EAAS;UAEvC;AAKV,MAAI,CAAC,EACH,QAAO;GACL,MAAM;GACN,QAAQ;GACR,UAAU,IAAW,KAAK,MAAM,EAAS,GAAG,EAAE;GAC9C,iBAAiB;GAClB;EAEH,IAAM,IAAO,aAAa,QAAQ,GAAG,EAAU,GAAG,EAAS,OAAO,EAC5D,IAAS,aAAa,QAAQ,GAAG,EAAU,GAAG,EAAS,SAAS,EAChE,IAAU,aAAa,QAC3B,GAAG,EAAU,GAAG,EAAS,kBAC1B;AAED,SAAO;GACL,MAAM,IAAO,KAAK,MAAM,EAAK,GAAG;GAChC,QAAQ,IACJ;IAAE,GAAG,KAAK,MAAM,EAAO;IAAE,gBAAgB,KAAW,KAAA;IAAW,GAC/D;GACJ,UAAU,IAAW,KAAK,MAAM,EAAS,GAAG,EAAE;GAC9C,iBAAiB;GAClB;SACK;AACN,SAAO;GAAE,MAAM;GAAM,QAAQ;GAAM,UAAU,EAAE;GAAE,iBAAiB;GAAM;;;AAI5E,SAAS,EAAe,GAAwC;CAC9D,IAAM,IACH,EAAQ,mBACP,EAAQ,MAAkC,MAC5C,eAAe,QAAQ,EAAU;AAC9B,QAQL;MANI,EAAQ,QACV,aAAa,QACX,GAAG,EAAU,GAAG,EAAU,QAC1B,KAAK,UAAU,EAAQ,KAAK,CAC7B,EAEC,EAAQ,UAAU,OAAO,EAAQ,UAAW,UAAU;GAExD,IAAM,EAAE,mBAAgB,GAAG,MADjB,EAAQ;AAMlB,GAJA,aAAa,QACX,GAAG,EAAU,GAAG,EAAU,UAC1B,KAAK,UAAU,EAAK,CACrB,EACG,KACF,aAAa,QACX,GAAG,EAAU,GAAG,EAAU,mBAC1B,OAAO,EAAe,CACvB;;AAML,EAHI,EAAQ,YACV,aAAa,QAAQ,GAAa,KAAK,UAAU,EAAQ,SAAS,CAAC,EAErE,eAAe,QAAQ,GAAW,EAAU;;;AAG9C,SAAS,IAAuB;CAC9B,IAAM,IAAW,eAAe,QAAQ,EAAU;AAClD,KAAI,GAAU;AAIZ,EAHA,aAAa,WAAW,GAAG,EAAU,GAAG,EAAS,OAAO,EACxD,aAAa,WAAW,GAAG,EAAU,GAAG,EAAS,SAAS,EAC1D,aAAa,WAAW,GAAG,EAAU,GAAG,EAAS,kBAAkB,EACnE,aAAa,WAAW,GAAG,EAAU,GAAG,EAAS,eAAe;AAEhE,MAAI;GACF,IAAM,IAAM,aAAa,QAAQ,EAAY,EACvC,IAAW,IAAM,KAAK,MAAM,EAAI,GAAG,EAAE;AAC3C,gBAAa,QACX,GACA,KAAK,UACH,EAAS,QAAQ,MAAsB,EAAE,OAAO,EAAS,CAC1D,CACF;UACK;AAGR,iBAAe,WAAW,EAAU;;AAEtC,cAAa,WAAW,eAAe;;AAGzC,SAAS,IAA0B;AAEjC,KAAI;EACF,IAAM,IAAM,aAAa,QAAQ,EAAY,EACvC,IAAkC,IAAM,KAAK,MAAM,EAAI,GAAG,EAAE;AAClE,OAAK,IAAM,KAAK,EAKd,CAJA,aAAa,WAAW,GAAG,EAAU,GAAG,EAAE,GAAG,OAAO,EACpD,aAAa,WAAW,GAAG,EAAU,GAAG,EAAE,GAAG,SAAS,EACtD,aAAa,WAAW,GAAG,EAAU,GAAG,EAAE,GAAG,kBAAkB,EAC/D,aAAa,WAAW,GAAG,EAAU,GAAG,EAAE,GAAG,eAAe,EAC5D,aAAa,WAAW,GAAG,EAAU,GAAG,EAAE,GAAG,UAAU;SAEnD;AAGR,MAAK,IAAM,KAAO,OAAO,KAAK,aAAa,CACzC,CAAI,EAAI,WAAW,GAAG,EAAU,GAAG,IAAI,EAAI,SAAS,gBAAgB,IAClE,aAAa,WAAW,EAAI;AAKhC,CAFA,aAAa,WAAW,EAAY,EACpC,aAAa,WAAW,eAAe,EACvC,eAAe,WAAW,EAAU;;AAItC,IAAM,IAAkB,kBAClB,IAAwB;AAC9B,SAAS,EAAmB,GAAmB,GAAsB;AACnE,KAAI;EAEF,IAAM,IADW,OAAO,SAAS,SACT,SAAS,uBAAuB,GACpD,yBACA,KAAA,GACE,IAAa,IAAS,YAAY,MAAW,IAC7C,IAAa,OAAO,SAAS,aAAa,WAAW,aAAa,IAClE,IAAc,KAAK,UAAU;GAAE,IAAI;GAAW;GAAQ,CAAC;AAC7D,WAAS,SAAS,GAAG,EAAgB,GAAG,mBAAmB,EAAY,GAAG,EAAW,wBAAwB,EAAW;SAClH;;AAKV,SAAS,EAAwB,GAAmB,GAAsB;AACxE,KAAI;EAEF,IAAM,IADW,OAAO,SAAS,SACT,SAAS,uBAAuB,GACpD,yBACA,KAAA,GACE,IAAa,IAAS,YAAY,MAAW,IAC7C,IAAa,OAAO,SAAS,aAAa,WAAW,aAAa,IAClE,IAAW,KAAK,UAAU;GAAE,IAAI;GAAW;GAAQ,CAAC;AAC1D,WAAS,SAAS,GAAG,EAAsB,GAAG,mBAAmB,EAAS,GAAG,EAAW,wBAAwB,EAAW;SACrH;;AAKV,SAAS,IAA6B;AACpC,KAAI;EACF,IAAM,IAAW,OAAO,SAAS,UAC3B,IAAS,EAAS,SAAS,uBAAuB,GACpD,yBACA,KAAA;AAOJ,EANA,SAAS,SAAS,GAAG,EAAgB,uBACrC,SAAS,SAAS,GAAG,EAAsB,uBACvC,MACF,SAAS,SAAS,GAAG,EAAgB,oBAAoB,EAAO,cAChE,SAAS,SAAS,GAAG,EAAsB,oBAAoB,EAAO,eAEpE,KAAY,CAAC,CAAC,aAAa,YAAY,CAAC,SAAS,EAAS,KAC5D,SAAS,SAAS,GAAG,EAAgB,oBAAoB,EAAS,cAClE,SAAS,SAAS,GAAG,EAAsB,oBAAoB,EAAS;SAEpE;;AAKV,SAAgB,IAAoB;CAClC,IAAM,IAAmB,kBAAqC,IAAI,KAAK,CAAC,EAClE,IAAc,kBAA4B,IAAI,KAAK,CAAC,EACpD,IAAuB,EAAO,GAAM;AAqN1C,QAhNA,QAAgB;EACd,eAAe,IAAgB;AAC7B,OAAI;AAEF,QAAI,CAAC,SAAS,sBAAsB;AAClC,OAAqB,UAAU;AAC/B;;AAIF,QADkB,MAAM,SAAS,kBAAkB,EACpC;AACb,OAAqB,UAAU;AAC/B;;AAIF,IADA,MAAM,SAAS,sBAAsB,EACrC,EAAqB,UAAU;WACzB;AAGN,MAAqB,UAAU;;;AAG9B,KAAe;IACnB,EAAE,CAAC,EAEN,QAAgB;EACd,SAAS,EAAgB,GAAyB;AAChD,UAAO,EAAgB,EAAO;;EAGhC,SAAS,EACP,GACA,GACM;AACN,KAAiB,QAAQ,SAAS,GAAY,MAAW;AACvD,IAAI,MAAW,KACb,EAAW,OAAO,YAChB;KAAE,GAAG;KAAS,QAAQ;KAAe,OAAO,EAAW;KAAO,EAC9D,EAAW,OACZ;KAEH;;EAGJ,SAAS,EAAc,GAA2B;AAEhD,OADI,CAAC,EAAgB,EAAM,OAAO,IAC9B,CAAC,EAAgB,EAAM,KAAK,IAAI,EAAM,KAAK,WAAW,EACxD;GAEF,IAAM,IAAU,EAAM,MAChB,IAAe,EAAsB,EAAM,OAAO,GACpD,EAAM,SACN;AACJ,OAAI,CAAC,EAAc;GAEnB,IAAM,IACJ,OAAO,EAAQ,SAAU,YAAY,EAAQ,MAAM,SAAS,IACxD,EAAQ,QACR,MACA,IAAqB,EAAiB,QAAQ,IAAI,EAAa;AAErE,OAAI,EAAQ,SAAS;QACf,CAAC,KAAiB,GAAoB,UAAU,EAClD;cAEO,CAAC,EACV;AAGF,WAAQ,EAAQ,MAAhB;IACE,KAAK,aAAa;AAShB,SARA,EAAiB,QAAQ,IAAI,GAAc;MACzC,QAAQ;MACR,QAAQ,EAAM;MACd,OAAO;MACR,CAAC,EAIE,EAAQ,WAAW,OAAO,EAAQ,WAAY,UAAU;MAC1D,IAAM,IAAc,EAAQ;AAC5B,MAAI,EAAY,QAAQ,EAAY,UAClC,EAAe,EAAY;;KAI/B,IAAM,IAAQ,GAAe,EACvB,IAAa,EAAiB,QAAQ,IAAI,EAAa;AAC7D,KAAI,MACF,EAAW,OAAO,YAChB;MACE,MAAM;MACN,SAAS;MACT,QAAQ;MACR,OAAO,EAAW;MACnB,EACD,EAAM,OACP,EACG,EAAY,QAAQ,IAAI,EAAa,KAAK,EAAW,UACvD,EAAY,QAAQ,IAAI,GAAc,EAAW,MAAM,EACvD,EAAW,OAAO,YAChB;MACE,MAAM;MACN,QAAQ;MACR,OAAO,EAAW;MACnB,EACD,EAAM,OACP;AAGL;;IAEF,KAAK;AACH,SAAI,EAAQ,SAAS;AACnB,QAAe,EAAQ,QAAQ;MAE/B,IAAM,IAAe,EAAQ,SACvB,IACH,EAAa,mBACZ,EAAa,MAAkC;AAMnD,MALI,MACF,GAAsB,EACtB,EAAmB,GAAgB,EAAM,OAAO,EAChD,EAAwB,GAAgB,EAAM,OAAO,GAEvD,EACE;OACE,MAAM;OACN,SAAS;QAAE,GAAG,GAAe;QAAE,SAAS;QAAS;OAClD,EACD,EACD;;AAEH;IAEF,KAAK;AAGH,KAFA,GAAgB,EAChB,GAAsB,EACtB,EACE;MACE,MAAM;MACN,SAAS;OAAE,GAAG,GAAe;OAAE,SAAS;OAAU;MACnD,EACD,EACD;AACD;IAEF,KAAK;AAGH,KAFA,GAAmB,EACnB,GAAsB,EACtB,EACE;MACE,MAAM;MACN,SAAS;OAAE,GAAG,GAAe;OAAE,SAAS;OAAc;MACvD,EACD,EACD;AACD;IAEF,KAAK;AACH,KAAI,EAAQ,SAAS,WACnB,EAAe,EAAE,QAAQ,EAAQ,QAAQ,QAAQ,CAAC,EAClD,EACE;MACE,MAAM;MACN,SAAS;OAAE,GAAG,GAAe;OAAE,SAAS;OAAW;MACpD,EACD,EACD;AAEH;IAEF,KAAK;AACH,KAAI,EAAQ,YACV,EAAe,EAAQ,QAAQ,EAC/B,EACE;MACE,MAAM;MACN,SAAS;OAAE,GAAG,GAAe;OAAE,SAAS;OAAkB;MAC3D,EACD,EACD;AAEH;;;EAKN,SAAS,EAAoB,GAA2B;AACtD,IAAI,EAAM,KAAK,WAAW,EAAU,IAAI,EAAM,QAAQ,MACpD,EAAe;IACb,MAAM;IACN,SAAS;KAAE,GAAG,GAAe;KAAE,SAAS;KAAgB;IACzD,CAAC;;AAON,SAHA,OAAO,iBAAiB,WAAW,EAAc,EACjD,OAAO,iBAAiB,WAAW,EAAoB,QAE1C;AAEX,GADA,OAAO,oBAAoB,WAAW,EAAc,EACpD,OAAO,oBAAoB,WAAW,EAAoB;;IAG3D,EAAE,CAAC,EAGJ,kBAAC,OAAD;EACE,IAAG;EACH,eAAY;EACZ,OAAO,EAAE,SAAS,QAAQ;EAC1B,CAAA"}
|
|
1
|
+
{"version":3,"file":"SessionBridgePage.js","names":[],"sources":["../../../src/auth/pages/SessionBridgePage.tsx"],"sourcesContent":["/**\n * SessionBridgePage — Cross-Domain Auth Session Bridge (Profile-Scoped)\n *\n * Embedded as hidden iframe by other app shells. Manages profile-scoped\n * auth tokens in localStorage and relays state via postMessage.\n *\n * Storage uses ProfileStorageManager keys:\n * bf-profiles, bf-p-{id}-user, bf-p-{id}-tokens, bf-p-{id}-workspace-token\n * bf-active-profile (sessionStorage, per-tab)\n *\n * URL: /auth/session-bridge\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { isAllowedSsoOriginString } from \"../utils/ssoOrigins\";\n\nconst BRIDGE_SOURCE = \"burdenoff-auth-bridge\";\nconst PARENT_SOURCE = \"burdenoff-auth-client\";\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\n\ninterface BridgeMessage {\n type: string;\n payload?: Record<string, unknown>;\n nonce?: string;\n source?: string;\n}\n\ninterface ConnectedParent {\n source: Window;\n origin: string;\n nonce: string;\n}\n\nfunction isWindowMessageSource(\n source: MessageEventSource | null,\n): source is Window {\n return (\n source !== null &&\n typeof (source as { postMessage?: unknown }).postMessage === \"function\"\n );\n}\n\nfunction isBridgeMessage(value: unknown): value is BridgeMessage {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\"\n );\n}\n\nfunction readAuthState(): Record<string, unknown> {\n try {\n const profiles = localStorage.getItem(BF_PROFILES);\n let activeId = sessionStorage.getItem(BF_ACTIVE);\n\n // Fall back: if sessionStorage is empty but localStorage has profiles,\n // pick the first profile (same logic as resolveActiveProfileId)\n if (!activeId && profiles) {\n try {\n const profileList = JSON.parse(profiles) as Array<{ id: string }>;\n if (profileList.length > 0) {\n activeId = profileList[0].id;\n sessionStorage.setItem(BF_ACTIVE, activeId);\n }\n } catch {\n // ignore\n }\n }\n\n if (!activeId) {\n return {\n user: null,\n tokens: null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: null,\n };\n }\n const user = localStorage.getItem(`${BF_PREFIX}-${activeId}-user`);\n const tokens = localStorage.getItem(`${BF_PREFIX}-${activeId}-tokens`);\n const wsToken = localStorage.getItem(\n `${BF_PREFIX}-${activeId}-workspace-token`,\n );\n\n return {\n user: user ? JSON.parse(user) : null,\n tokens: tokens\n ? { ...JSON.parse(tokens), workspaceToken: wsToken ?? undefined }\n : null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: activeId,\n };\n } catch {\n return { user: null, tokens: null, profiles: [], activeProfileId: null };\n }\n}\n\nfunction writeAuthState(payload: Record<string, unknown>): void {\n const profileId =\n (payload.activeProfileId as string) ??\n ((payload.user as Record<string, unknown>)?.id as string) ??\n sessionStorage.getItem(BF_ACTIVE);\n if (!profileId) return;\n\n if (payload.user) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-user`,\n JSON.stringify(payload.user),\n );\n }\n if (payload.tokens && typeof payload.tokens === \"object\") {\n const t = payload.tokens as Record<string, unknown>;\n const { workspaceToken, ...rest } = t;\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-tokens`,\n JSON.stringify(rest),\n );\n if (workspaceToken) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-workspace-token`,\n String(workspaceToken),\n );\n }\n }\n if (payload.profiles) {\n localStorage.setItem(BF_PROFILES, JSON.stringify(payload.profiles));\n }\n sessionStorage.setItem(BF_ACTIVE, profileId);\n}\n\nfunction clearAuthState(): void {\n const activeId = sessionStorage.getItem(BF_ACTIVE);\n if (activeId) {\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-auth-storage`);\n // Remove from profiles list\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles = raw ? JSON.parse(raw) : [];\n localStorage.setItem(\n BF_PROFILES,\n JSON.stringify(\n profiles.filter((p: { id: string }) => p.id !== activeId),\n ),\n );\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n }\n localStorage.removeItem(\"auth-storage\");\n}\n\nfunction clearAllAuthState(): void {\n // Get all profiles and remove their data\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles: Array<{ id: string }> = raw ? JSON.parse(raw) : [];\n for (const p of profiles) {\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-auth-storage`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-context`);\n }\n } catch {\n /* ignore */\n }\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(`${BF_PREFIX}-`) && key.endsWith(\"-auth-storage\")) {\n localStorage.removeItem(key);\n }\n }\n localStorage.removeItem(BF_PROFILES);\n localStorage.removeItem(\"auth-storage\");\n sessionStorage.removeItem(BF_ACTIVE);\n}\n\n// SSO cookie helpers (shared domain cookie for local dev fast-path)\nconst SSO_COOKIE_NAME = \"bf-sso-profile\";\nconst SHELL_SSO_COOKIE_NAME = \"bf-sso\";\nfunction setSSoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SSO_COOKIE_NAME}=${encodeURIComponent(cookieValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction setShellSsoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction clearSSoCookieBridge(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n }\n } catch {\n /* ignore */\n }\n}\n\nexport function SessionBridgePage() {\n const connectedParents = useRef<Map<Window, ConnectedParent>>(new Map());\n const readySentTo = useRef<Map<Window, string>>(new Map());\n const storageAccessGranted = useRef(false);\n\n // Request unpartitioned storage access (Storage Access API).\n // This is critical for cross-domain SSO in modern browsers that\n // partition third-party iframe localStorage.\n useEffect(() => {\n async function requestAccess() {\n try {\n // Check if Storage Access API is available\n if (!document.requestStorageAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Check if we already have access\n const hasAccess = await document.hasStorageAccess();\n if (hasAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Request unpartitioned storage access\n await document.requestStorageAccess();\n storageAccessGranted.current = true;\n } catch {\n // Storage Access API not available or denied — fall through\n // to cookie-based fallback. localStorage will be partitioned.\n storageAccessGranted.current = false;\n }\n }\n void requestAccess();\n }, []);\n\n useEffect(() => {\n function isOriginAllowed(origin: string): boolean {\n return isAllowedSsoOriginString(origin);\n }\n\n function broadcastToAll(\n message: BridgeMessage,\n excludeSource?: Window | null,\n ): void {\n connectedParents.current.forEach((connection, parent) => {\n if (parent !== excludeSource) {\n connection.source.postMessage(\n { ...message, source: BRIDGE_SOURCE, nonce: connection.nonce },\n connection.origin,\n );\n }\n });\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!isOriginAllowed(event.origin)) return;\n if (!isBridgeMessage(event.data) || event.data.source !== PARENT_SOURCE) {\n return;\n }\n const message = event.data;\n const parentSource = isWindowMessageSource(event.source)\n ? event.source\n : null;\n if (!parentSource) return;\n\n const incomingNonce =\n typeof message.nonce === \"string\" && message.nonce.length > 0\n ? message.nonce\n : null;\n const existingConnection = connectedParents.current.get(parentSource);\n\n if (message.type !== \"AUTH_INIT\") {\n if (!incomingNonce || existingConnection?.nonce !== incomingNonce) {\n return;\n }\n } else if (!incomingNonce) {\n return;\n }\n\n switch (message.type) {\n case \"AUTH_INIT\": {\n connectedParents.current.set(parentSource, {\n source: parentSource,\n origin: event.origin,\n nonce: incomingNonce,\n });\n\n // If the parent sends its current auth state in AUTH_INIT,\n // store it so other cross-domain shells can pick it up.\n if (message.payload && typeof message.payload === \"object\") {\n const initPayload = message.payload as Record<string, unknown>;\n if (initPayload.user && initPayload.tokens) {\n writeAuthState(initPayload);\n }\n }\n\n const state = readAuthState();\n const connection = connectedParents.current.get(parentSource);\n if (connection) {\n connection.source.postMessage(\n {\n type: \"AUTH_STATE\",\n payload: state,\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n if (readySentTo.current.get(parentSource) !== connection.nonce) {\n readySentTo.current.set(parentSource, connection.nonce);\n connection.source.postMessage(\n {\n type: \"AUTH_BRIDGE_READY\",\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n }\n }\n break;\n }\n case \"AUTH_LOGIN\": {\n if (message.payload) {\n writeAuthState(message.payload);\n // Set SSO cookie for cross-domain fast-path detection\n const loginPayload = message.payload as Record<string, unknown>;\n const loginProfileId =\n (loginPayload.activeProfileId as string) ??\n ((loginPayload.user as Record<string, unknown>)?.id as string);\n if (loginProfileId) {\n clearSSoCookieBridge();\n setSSoCookieBridge(loginProfileId, event.origin);\n setShellSsoCookieBridge(loginProfileId, event.origin);\n }\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"login\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_LOGOUT\": {\n clearAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_LOGOUT_ALL\": {\n clearAllAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout_all\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_TOKEN_REFRESH\": {\n if (message.payload?.tokens) {\n writeAuthState({ tokens: message.payload.tokens });\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"refresh\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_ACCOUNT_SWITCH\": {\n if (message.payload) {\n writeAuthState(message.payload);\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"account_switch\" },\n },\n parentSource,\n );\n }\n break;\n }\n }\n }\n\n function handleStorageChange(event: StorageEvent): void {\n if (event.key?.startsWith(BF_PREFIX) || event.key === BF_PROFILES) {\n broadcastToAll({\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"storage_sync\" },\n });\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n window.addEventListener(\"storage\", handleStorageChange);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n window.removeEventListener(\"storage\", handleStorageChange);\n // Don't clear connectedParents or readySentTo on cleanup to maintain state across StrictMode remounts\n };\n }, []);\n\n return (\n <div\n id=\"auth-session-bridge-root\"\n data-bridge=\"active\"\n style={{ display: \"none\" }}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/auth/utils/ssoOrigins.ts
|
|
2
|
+
var e = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,botlit.ai,bigconsole.com,gethealthybowl.com,headshotmarketing.com,timecampus.com,ggnomad.com,movethewheels.com,crewfoundry.com,planmagnet.com,eventfullymanaged.com,kadaikodi.com,assethandler.com,technospam.com,semanticfed.com,manufacturedops.com,theglobalfuel.com,housingvista.com,proserviceworld.com,mybodyshield.com,artistrybase.com,collabkin.com,brainyrich.com,theculturepost.com,comradecircle.com,cosmicintersection.com,govcitizenhub.com,intelwatchtower.com,ecoimpacthub.com,buildmyiq.com,fluidgrids.ai,atheletebridge.com,adaptercloud.com,coolandlovely.com,subscriberbot.com,labsofscience.com,hookmovies.com".split(","));
|
|
3
|
+
function t(t) {
|
|
4
|
+
if (t.hostname === "localhost" || t.hostname === "127.0.0.1" || t.hostname.endsWith(".local.burdenoff.com")) return !0;
|
|
5
|
+
if (t.protocol !== "https:") return !1;
|
|
6
|
+
for (let n of e) if (t.hostname === n || t.hostname === `app.${n}` || t.hostname === `alphaapp.${n}` || t.hostname === `admin.${n}` || t.hostname === `alphaadmin.${n}` || t.hostname === `www.${n}`) return !0;
|
|
7
|
+
return !1;
|
|
8
|
+
}
|
|
9
|
+
function n(e) {
|
|
10
|
+
try {
|
|
11
|
+
return t(new URL(e));
|
|
12
|
+
} catch {
|
|
13
|
+
return !1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { t as isAllowedSsoOrigin, n as isAllowedSsoOriginString };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=ssoOrigins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssoOrigins.js","names":[],"sources":["../../../src/auth/utils/ssoOrigins.ts"],"sourcesContent":["/**\n * Shared first-party SSO origin allow-list for the cross-domain auth pages\n * (SSOClearPage, SSODepositPage, SSORelayPage, SessionBridgePage).\n *\n * These four pages each used to hand-roll an identical copy of this domain set\n * plus its matching function, and they drifted independently — BOFF-4120 was\n * exactly that drift (SSOClearPage's copy was missing admin./alphaadmin., so an\n * admin logout returnTo was rejected on the bridge host and stranded the\n * operator on app.burdenoff.com). Keep the list + matching logic HERE only so\n * a new product domain or subdomain is added in one place. BOFF-4124.\n */\n\nconst ALLOWED_SSO_DOMAINS = new Set<string>([\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\n/**\n * True when `url` is a first-party Burdenoff shell origin permitted to take\n * part in the cross-domain SSO relay / clear / deposit / session-bridge flow:\n * - localhost / 127.0.0.1 (any port, local dev)\n * - *.local.burdenoff.com (local dev behind nginx)\n * - https://{\"\" | app. | alphaapp. | admin. | alphaadmin. | www.}<product-domain>\n * — note admin./alphaadmin. are included: the admin app shells\n * (admin.burdenoff.com / alphaadmin.burdenoff.com) are first-party origins\n * that relay through and return from the central bridge host.\n */\nexport function isAllowedSsoOrigin(url: URL): boolean {\n if (url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\") return true;\n if (url.hostname.endsWith(\".local.burdenoff.com\")) return true;\n if (url.protocol !== \"https:\") return false;\n for (const domain of ALLOWED_SSO_DOMAINS) {\n if (\n url.hostname === domain ||\n url.hostname === `app.${domain}` ||\n url.hostname === `alphaapp.${domain}` ||\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\n/**\n * String-origin variant for postMessage origin checks (SessionBridgePage),\n * which receives a raw origin string rather than a parsed URL. Returns false on\n * an unparseable origin.\n */\nexport function isAllowedSsoOriginString(origin: string): boolean {\n try {\n return isAllowedSsoOrigin(new URL(origin));\n } catch {\n return false;\n }\n}\n"],"mappings":";AAYA,IAAM,IAAsB,IAAI,IAAY,8qBAwC3C,CAAC;AAYF,SAAgB,EAAmB,GAAmB;AAEpD,KADI,EAAI,aAAa,eAAe,EAAI,aAAa,eACjD,EAAI,SAAS,SAAS,uBAAuB,CAAE,QAAO;AAC1D,KAAI,EAAI,aAAa,SAAU,QAAO;AACtC,MAAK,IAAM,KAAU,EACnB,KACE,EAAI,aAAa,KACjB,EAAI,aAAa,OAAO,OACxB,EAAI,aAAa,YAAY,OAC7B,EAAI,aAAa,SAAS,OAC1B,EAAI,aAAa,cAAc,OAC/B,EAAI,aAAa,OAAO,IAExB,QAAO;AAGX,QAAO;;AAQT,SAAgB,EAAyB,GAAyB;AAChE,KAAI;AACF,SAAO,EAAmB,IAAI,IAAI,EAAO,CAAC;SACpC;AACN,SAAO"}
|