@burdenoff/microfe-auth 2026.718.1 → 2026.718.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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":"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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-auth",
3
- "version": "2026.718.1",
3
+ "version": "2026.718.3",
4
4
  "description": "Auth microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "imports": {
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@apollo/client": "^4.0.9",
47
- "@burdenoff/fe-libs": "2026.704.2",
47
+ "@burdenoff/fe-libs": "2026.718.1",
48
48
  "@types/qrcode.react": "^3.0.0",
49
49
  "clsx": "^2.1.1",
50
50
  "graphql": "^16.12.0",