@burdenoff/microfe-auth 2026.531.4 → 2026.601.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.
@@ -180,7 +180,7 @@ function B({ basePath: e = "", enableAdminRoutes: c = !1, bypassEnterprisePlanGa
180
180
  defaultValue: !0,
181
181
  children: /* @__PURE__ */ h(s, {
182
182
  fallback: /* @__PURE__ */ h(I, {}),
183
- children: /* @__PURE__ */ h(T, {})
183
+ children: /* @__PURE__ */ h(T, { enableAdminRoutes: c })
184
184
  })
185
185
  }) })
186
186
  }),
@@ -206,13 +206,6 @@ function B({ basePath: e = "", enableAdminRoutes: c = !1, bypassEnterprisePlanGa
206
206
  })
207
207
  }) })
208
208
  }),
209
- /* @__PURE__ */ h(f, {
210
- path: "api-keys",
211
- element: /* @__PURE__ */ h(z, { children: /* @__PURE__ */ h(d, {
212
- to: "/devportal/api-keys",
213
- replace: !0
214
- }) })
215
- }),
216
209
  c && u && /* @__PURE__ */ h(f, {
217
210
  path: "audit-log",
218
211
  element: /* @__PURE__ */ h(z, { children: /* @__PURE__ */ h(_, {
@@ -245,17 +238,6 @@ function B({ basePath: e = "", enableAdminRoutes: c = !1, bypassEnterprisePlanGa
245
238
  replace: !0
246
239
  })
247
240
  }),
248
- /* @__PURE__ */ h(f, {
249
- path: "apps",
250
- element: /* @__PURE__ */ h(z, { children: /* @__PURE__ */ h(_, {
251
- flag: "auth.apps",
252
- defaultValue: !1,
253
- children: /* @__PURE__ */ h(d, {
254
- to: "/devportal/apps",
255
- replace: !0
256
- })
257
- }) })
258
- }),
259
241
  /* @__PURE__ */ h(f, {
260
242
  path: "users",
261
243
  element: c ? /* @__PURE__ */ h(z, { children: /* @__PURE__ */ h(_, {
@@ -295,13 +277,6 @@ function B({ basePath: e = "", enableAdminRoutes: c = !1, bypassEnterprisePlanGa
295
277
  })
296
278
  }) })
297
279
  }),
298
- /* @__PURE__ */ h(f, {
299
- path: "delegations",
300
- element: /* @__PURE__ */ h(z, { children: /* @__PURE__ */ h(d, {
301
- to: "/devportal/delegations",
302
- replace: !0
303
- }) })
304
- }),
305
280
  /* @__PURE__ */ h(f, {
306
281
  index: !0,
307
282
  element: /* @__PURE__ */ h(d, {
@@ -1 +1 @@
1
- {"version":3,"file":"AuthRoutes.js","names":[],"sources":["../../src/auth/AuthRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect, useState } from \"react\";\nimport {\n useFeatureFlagDetails,\n FeatureGate,\n} from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { Navigate, Route, Routes } from \"react-router-dom\";\nimport { useAuthStore } from \"./store/authStore\";\n\n// Eager imports - these are the most commonly accessed routes\nimport { LoginPage } from \"./pages/LoginPage\";\nimport { SessionBridgePage } from \"./pages/SessionBridgePage\";\nimport { SSORelayPage } from \"./pages/SSORelayPage\";\nimport { SSORelayCallbackPage } from \"./pages/SSORelayCallbackPage\";\nimport { SSODepositPage } from \"./pages/SSODepositPage\";\nimport { SSOClearPage } from \"./pages/SSOClearPage\";\n\n// Lazy-loaded pages - loaded on demand when their routes are accessed\nconst RegisterPage = lazy(() =>\n import(\"./pages/RegisterPage\").then((m) => ({ default: m.RegisterPage })),\n);\nconst PasswordResetPage = lazy(() =>\n import(\"./pages/PasswordResetPage\").then((m) => ({\n default: m.PasswordResetPage,\n })),\n);\nconst EmailVerifyPage = lazy(() =>\n import(\"./pages/EmailVerifyPage\").then((m) => ({\n default: m.EmailVerifyPage,\n })),\n);\nconst MfaVerifyPage = lazy(() =>\n import(\"./pages/MfaVerifyPage\").then((m) => ({ default: m.MfaVerifyPage })),\n);\nconst MagicLinkPage = lazy(() =>\n import(\"./pages/MagicLinkPage\").then((m) => ({ default: m.MagicLinkPage })),\n);\nconst ReactivatePage = lazy(() =>\n import(\"./pages/ReactivatePage\").then((m) => ({ default: m.ReactivatePage })),\n);\nconst SecurityPage = lazy(() =>\n import(\"./pages/SecurityPage\").then((m) => ({ default: m.SecurityPage })),\n);\nconst SessionsPage = lazy(() =>\n import(\"./pages/SessionsPage\").then((m) => ({ default: m.SessionsPage })),\n);\nconst MfaSetupPage = lazy(() =>\n import(\"./pages/MfaSetupPage\").then((m) => ({ default: m.MfaSetupPage })),\n);\nconst AuditLogPage = lazy(() =>\n import(\"./pages/AuditLogPage\").then((m) => ({ default: m.AuditLogPage })),\n);\nconst SsoConfigPage = lazy(() =>\n import(\"./pages/SsoConfigPage\").then((m) => ({ default: m.SsoConfigPage })),\n);\nconst SsoLoginPage = lazy(() =>\n import(\"./pages/SsoLoginPage\").then((m) => ({ default: m.SsoLoginPage })),\n);\nconst SsoCallbackPage = lazy(() =>\n import(\"./pages/SsoCallbackPage\").then((m) => ({\n default: m.SsoCallbackPage,\n })),\n);\nconst OAuthCallbackPage = lazy(() =>\n import(\"./pages/OAuthCallbackPage\").then((m) => ({\n default: m.OAuthCallbackPage,\n })),\n);\nconst UsersPage = lazy(() =>\n import(\"./pages/UsersPage\").then((m) => ({ default: m.UsersPage })),\n);\nconst UserDetailsPage = lazy(() =>\n import(\"./pages/UserDetailsPage\").then((m) => ({\n default: m.UserDetailsPage,\n })),\n);\nconst UserProfilePage = lazy(() =>\n import(\"./pages/UserProfilePage\").then((m) => ({\n default: m.UserProfilePage,\n })),\n);\n/**\n * Simple loading fallback for lazy-loaded pages\n */\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"size-8 animate-spin rounded-full border-4 border-primary border-t-transparent\" />\n </div>\n );\n}\n\nfunction decodeJwtExpiry(token: string): number | null {\n try {\n const payload = JSON.parse(\n atob(token.split(\".\")[1].replace(/-/g, \"+\").replace(/_/g, \"/\")),\n ) as { exp?: number };\n\n return typeof payload.exp === \"number\" ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction getStoredSessionSnapshot(): {\n hasValidSession: boolean;\n expiresAt: number | null;\n} {\n try {\n const activeProfileId = sessionStorage.getItem(\"bf-active-profile\");\n if (!activeProfileId) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${activeProfileId}-tokens`);\n if (!tokensRaw) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokens = JSON.parse(tokensRaw) as {\n accessToken?: string;\n expiresAt?: number;\n };\n\n if (!tokens.accessToken) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const expiresAt =\n typeof tokens.expiresAt === \"number\"\n ? tokens.expiresAt\n : decodeJwtExpiry(tokens.accessToken);\n\n if (!expiresAt) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n return {\n hasValidSession: expiresAt > Date.now(),\n expiresAt,\n };\n } catch {\n return { hasValidSession: false, expiresAt: null };\n }\n}\n\n/**\n * Protected route wrapper\n */\nfunction ProtectedRoute({ children }: { children: React.ReactNode }) {\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const clearAuth = useAuthStore((state) => state.clearAuth);\n const [sessionCheckVersion, setSessionCheckVersion] = useState(0);\n const sessionSnapshot = getStoredSessionSnapshot();\n\n useEffect(() => {\n const handleSessionUpdate = () => {\n setSessionCheckVersion((value) => value + 1);\n };\n\n window.addEventListener(\"bf-profile-storage-change\", handleSessionUpdate);\n window.addEventListener(\"storage\", handleSessionUpdate);\n\n return () => {\n window.removeEventListener(\n \"bf-profile-storage-change\",\n handleSessionUpdate,\n );\n window.removeEventListener(\"storage\", handleSessionUpdate);\n };\n }, []);\n\n useEffect(() => {\n if (!sessionSnapshot.expiresAt) return;\n\n const timeoutMs = Math.max(sessionSnapshot.expiresAt - Date.now(), 0) + 50;\n const timer = window.setTimeout(() => {\n setSessionCheckVersion((value) => value + 1);\n }, timeoutMs);\n\n return () => window.clearTimeout(timer);\n }, [sessionSnapshot.expiresAt, sessionCheckVersion]);\n\n useEffect(() => {\n if (isAuthenticated && !sessionSnapshot.hasValidSession) {\n clearAuth();\n }\n }, [clearAuth, isAuthenticated, sessionSnapshot.hasValidSession]);\n\n if (!sessionSnapshot.hasValidSession) {\n return <Navigate to=\"/auth/login\" replace />;\n }\n\n return <>{children}</>;\n}\n\n/**\n * Internal router for the Auth microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * When used within a parent route (e.g., <Route path=\"/auth/*\">), all paths are relative:\n * - login → Login page (accessible at /auth/login)\n * - register → Registration page (accessible at /auth/register)\n * - reset-password → Password reset page (accessible at /auth/reset-password)\n * - verify-email → Email verification page (accessible at /auth/verify-email)\n * - mfa-verify → MFA verification page (accessible at /auth/mfa-verify)\n * - magic-link → Magic link authentication page (accessible at /auth/magic-link)\n * - sso/login → SSO login page (accessible at /auth/sso/login)\n * - sso/callback → SSO callback handler (accessible at /auth/sso/callback)\n * - oauth/callback → OAuth callback handler (accessible at /auth/oauth/callback)\n * - security → Security settings (protected, accessible at /auth/security)\n * - sessions → Active sessions (protected, accessible at /auth/sessions)\n * - mfa → MFA setup (protected, accessible at /auth/mfa)\n * - api-keys → API keys management (protected, accessible at /auth/api-keys)\n * - apps → Legacy redirect to Developer Portal app creation\n * - users → Users management (protected, admin only, accessible at /auth/users)\n * - profile → User profile page (protected, accessible at /auth/profile)\n * - audit-log → Security audit log (protected, accessible at /auth/audit-log)\n * - sso → SSO configuration (protected, admin only, accessible at /auth/sso)\n */\nexport function AuthRoutes({\n basePath: _basePath = \"\",\n // Admin routes (`/auth/users`, `/auth/users/:id`, `/auth/sso`,\n // `/auth/audit-log`) are opt-in: only the admin app shell should pass\n // `enableAdminRoutes={true}`. User-facing product hosts (vibecontrols,\n // workspaces, botlit, healthybowl, etc.) intentionally do not expose\n // these surfaces — the gateway's RBAC directives still gate the\n // underlying queries/mutations, but hiding the routes prevents\n // accidental UI discoverability and reduces the attack surface for\n // misconfigured permissions.\n enableAdminRoutes = false,\n bypassEnterprisePlanGate = false,\n}: {\n basePath?: string;\n enableAdminRoutes?: boolean;\n bypassEnterprisePlanGate?: boolean;\n}) {\n // Note: basePath is kept for backwards compatibility but not used in route paths\n // since React Router v7 uses relative paths when nested inside a parent <Route>\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n return (\n <Routes>\n {/* Public routes (no auth required) */}\n {/* LoginPage is eager-loaded as it's the most common entry point */}\n <Route path=\"login\" element={<LoginPage />} />\n <Route\n path=\"register\"\n element={\n <Suspense fallback={<PageLoader />}>\n <RegisterPage />\n </Suspense>\n }\n />\n <Route\n path=\"reset-password\"\n element={\n <Suspense fallback={<PageLoader />}>\n <PasswordResetPage />\n </Suspense>\n }\n />\n <Route\n path=\"verify-email\"\n element={\n <Suspense fallback={<PageLoader />}>\n <EmailVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"mfa-verify\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MfaVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"magic-link\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MagicLinkPage />\n </Suspense>\n }\n />\n\n {/* SSO routes (public) */}\n <Route\n path=\"sso/login\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoLoginPage />\n </Suspense>\n }\n />\n <Route\n path=\"sso/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoCallbackPage />\n </Suspense>\n }\n />\n\n {/* OAuth callback route (public) */}\n <Route\n path=\"oauth/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <OAuthCallbackPage />\n </Suspense>\n }\n />\n\n {/* Account reactivation (public) */}\n <Route\n path=\"reactivate\"\n element={\n <Suspense fallback={<PageLoader />}>\n <ReactivatePage />\n </Suspense>\n }\n />\n\n {/* Session bridge (public - embedded as hidden iframe by other app shells) */}\n <Route path=\"session-bridge\" element={<SessionBridgePage />} />\n\n {/* Cross-domain SSO relay (reads local tokens, redirects with them to target) */}\n <Route path=\"sso-relay\" element={<SSORelayPage />} />\n\n {/* Cross-domain SSO callback (receives tokens from relay, stores locally) */}\n <Route path=\"sso-callback\" element={<SSORelayCallbackPage />} />\n\n {/* Cross-domain SSO deposit (bridge host receives tokens on top-level\n redirect right after login on a product origin, writes first-party\n localStorage on the bridge origin so other products can discover\n the session). */}\n <Route path=\"sso-deposit\" element={<SSODepositPage />} />\n\n {/* Cross-domain SLO clear (bridge host clears its central session on\n top-level redirect from a product origin's logout). */}\n <Route path=\"sso-clear\" element={<SSOClearPage />} />\n\n {/* Protected routes (auth required) */}\n <Route\n path=\"security\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.security\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SecurityPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"sessions\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sessions\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SessionsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"mfa\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.mfa\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <MfaSetupPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"api-keys\"\n element={\n <ProtectedRoute>\n <Navigate to=\"/devportal/api-keys\" replace />\n </ProtectedRoute>\n }\n />\n {enableAdminRoutes && showAuditLog && (\n <Route\n path=\"audit-log\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.audit-log\" defaultValue={false}>\n <Suspense fallback={<PageLoader />}>\n <AuditLogPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n )}\n {!enableAdminRoutes && (\n <Route\n path=\"audit-log\"\n element={<Navigate to=\"/auth/login\" replace />}\n />\n )}\n <Route\n path=\"sso\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sso\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SsoConfigPage\n bypassEnterprisePlanGate={bypassEnterprisePlanGate}\n />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"apps\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.apps\" defaultValue={false}>\n <Navigate to=\"/devportal/apps\" replace />\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"users\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UsersPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users/:id\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserDetailsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"profile\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.profile\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserProfilePage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"delegations\"\n element={\n <ProtectedRoute>\n <Navigate to=\"/devportal/delegations\" replace />\n </ProtectedRoute>\n }\n />\n\n {/* Default redirect - when visiting /auth or /auth/ with no subpath */}\n <Route index element={<Navigate to=\"login\" replace />} />\n </Routes>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AuthRoutes.js","names":[],"sources":["../../src/auth/AuthRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect, useState } from \"react\";\nimport {\n useFeatureFlagDetails,\n FeatureGate,\n} from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { Navigate, Route, Routes } from \"react-router-dom\";\nimport { useAuthStore } from \"./store/authStore\";\n\n// Eager imports - these are the most commonly accessed routes\nimport { LoginPage } from \"./pages/LoginPage\";\nimport { SessionBridgePage } from \"./pages/SessionBridgePage\";\nimport { SSORelayPage } from \"./pages/SSORelayPage\";\nimport { SSORelayCallbackPage } from \"./pages/SSORelayCallbackPage\";\nimport { SSODepositPage } from \"./pages/SSODepositPage\";\nimport { SSOClearPage } from \"./pages/SSOClearPage\";\n\n// Lazy-loaded pages - loaded on demand when their routes are accessed\nconst RegisterPage = lazy(() =>\n import(\"./pages/RegisterPage\").then((m) => ({ default: m.RegisterPage })),\n);\nconst PasswordResetPage = lazy(() =>\n import(\"./pages/PasswordResetPage\").then((m) => ({\n default: m.PasswordResetPage,\n })),\n);\nconst EmailVerifyPage = lazy(() =>\n import(\"./pages/EmailVerifyPage\").then((m) => ({\n default: m.EmailVerifyPage,\n })),\n);\nconst MfaVerifyPage = lazy(() =>\n import(\"./pages/MfaVerifyPage\").then((m) => ({ default: m.MfaVerifyPage })),\n);\nconst MagicLinkPage = lazy(() =>\n import(\"./pages/MagicLinkPage\").then((m) => ({ default: m.MagicLinkPage })),\n);\nconst ReactivatePage = lazy(() =>\n import(\"./pages/ReactivatePage\").then((m) => ({ default: m.ReactivatePage })),\n);\nconst SecurityPage = lazy(() =>\n import(\"./pages/SecurityPage\").then((m) => ({ default: m.SecurityPage })),\n);\nconst SessionsPage = lazy(() =>\n import(\"./pages/SessionsPage\").then((m) => ({ default: m.SessionsPage })),\n);\nconst MfaSetupPage = lazy(() =>\n import(\"./pages/MfaSetupPage\").then((m) => ({ default: m.MfaSetupPage })),\n);\nconst AuditLogPage = lazy(() =>\n import(\"./pages/AuditLogPage\").then((m) => ({ default: m.AuditLogPage })),\n);\nconst SsoConfigPage = lazy(() =>\n import(\"./pages/SsoConfigPage\").then((m) => ({ default: m.SsoConfigPage })),\n);\nconst SsoLoginPage = lazy(() =>\n import(\"./pages/SsoLoginPage\").then((m) => ({ default: m.SsoLoginPage })),\n);\nconst SsoCallbackPage = lazy(() =>\n import(\"./pages/SsoCallbackPage\").then((m) => ({\n default: m.SsoCallbackPage,\n })),\n);\nconst OAuthCallbackPage = lazy(() =>\n import(\"./pages/OAuthCallbackPage\").then((m) => ({\n default: m.OAuthCallbackPage,\n })),\n);\nconst UsersPage = lazy(() =>\n import(\"./pages/UsersPage\").then((m) => ({ default: m.UsersPage })),\n);\nconst UserDetailsPage = lazy(() =>\n import(\"./pages/UserDetailsPage\").then((m) => ({\n default: m.UserDetailsPage,\n })),\n);\nconst UserProfilePage = lazy(() =>\n import(\"./pages/UserProfilePage\").then((m) => ({\n default: m.UserProfilePage,\n })),\n);\n/**\n * Simple loading fallback for lazy-loaded pages\n */\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"size-8 animate-spin rounded-full border-4 border-primary border-t-transparent\" />\n </div>\n );\n}\n\nfunction decodeJwtExpiry(token: string): number | null {\n try {\n const payload = JSON.parse(\n atob(token.split(\".\")[1].replace(/-/g, \"+\").replace(/_/g, \"/\")),\n ) as { exp?: number };\n\n return typeof payload.exp === \"number\" ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction getStoredSessionSnapshot(): {\n hasValidSession: boolean;\n expiresAt: number | null;\n} {\n try {\n const activeProfileId = sessionStorage.getItem(\"bf-active-profile\");\n if (!activeProfileId) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${activeProfileId}-tokens`);\n if (!tokensRaw) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokens = JSON.parse(tokensRaw) as {\n accessToken?: string;\n expiresAt?: number;\n };\n\n if (!tokens.accessToken) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const expiresAt =\n typeof tokens.expiresAt === \"number\"\n ? tokens.expiresAt\n : decodeJwtExpiry(tokens.accessToken);\n\n if (!expiresAt) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n return {\n hasValidSession: expiresAt > Date.now(),\n expiresAt,\n };\n } catch {\n return { hasValidSession: false, expiresAt: null };\n }\n}\n\n/**\n * Protected route wrapper\n */\nfunction ProtectedRoute({ children }: { children: React.ReactNode }) {\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const clearAuth = useAuthStore((state) => state.clearAuth);\n const [sessionCheckVersion, setSessionCheckVersion] = useState(0);\n const sessionSnapshot = getStoredSessionSnapshot();\n\n useEffect(() => {\n const handleSessionUpdate = () => {\n setSessionCheckVersion((value) => value + 1);\n };\n\n window.addEventListener(\"bf-profile-storage-change\", handleSessionUpdate);\n window.addEventListener(\"storage\", handleSessionUpdate);\n\n return () => {\n window.removeEventListener(\n \"bf-profile-storage-change\",\n handleSessionUpdate,\n );\n window.removeEventListener(\"storage\", handleSessionUpdate);\n };\n }, []);\n\n useEffect(() => {\n if (!sessionSnapshot.expiresAt) return;\n\n const timeoutMs = Math.max(sessionSnapshot.expiresAt - Date.now(), 0) + 50;\n const timer = window.setTimeout(() => {\n setSessionCheckVersion((value) => value + 1);\n }, timeoutMs);\n\n return () => window.clearTimeout(timer);\n }, [sessionSnapshot.expiresAt, sessionCheckVersion]);\n\n useEffect(() => {\n if (isAuthenticated && !sessionSnapshot.hasValidSession) {\n clearAuth();\n }\n }, [clearAuth, isAuthenticated, sessionSnapshot.hasValidSession]);\n\n if (!sessionSnapshot.hasValidSession) {\n return <Navigate to=\"/auth/login\" replace />;\n }\n\n return <>{children}</>;\n}\n\n/**\n * Internal router for the Auth microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * When used within a parent route (e.g., <Route path=\"/auth/*\">), all paths are relative:\n * - login → Login page (accessible at /auth/login)\n * - register → Registration page (accessible at /auth/register)\n * - reset-password → Password reset page (accessible at /auth/reset-password)\n * - verify-email → Email verification page (accessible at /auth/verify-email)\n * - mfa-verify → MFA verification page (accessible at /auth/mfa-verify)\n * - magic-link → Magic link authentication page (accessible at /auth/magic-link)\n * - sso/login → SSO login page (accessible at /auth/sso/login)\n * - sso/callback → SSO callback handler (accessible at /auth/sso/callback)\n * - oauth/callback → OAuth callback handler (accessible at /auth/oauth/callback)\n * - security → Security settings (protected, accessible at /auth/security)\n * - sessions → Active sessions (protected, accessible at /auth/sessions)\n * - mfa → MFA setup (protected, accessible at /auth/mfa)\n * - users → Users management (protected, admin only, accessible at /auth/users)\n * - profile → User profile page (protected, accessible at /auth/profile)\n * - audit-log → Security audit log (protected, accessible at /auth/audit-log)\n * - sso → SSO configuration (protected, admin only, accessible at /auth/sso)\n */\nexport function AuthRoutes({\n basePath: _basePath = \"\",\n // Admin routes (`/auth/users`, `/auth/users/:id`, `/auth/sso`,\n // `/auth/audit-log`) are opt-in: only the admin app shell should pass\n // `enableAdminRoutes={true}`. User-facing product hosts (vibecontrols,\n // workspaces, botlit, healthybowl, etc.) intentionally do not expose\n // these surfaces — the gateway's RBAC directives still gate the\n // underlying queries/mutations, but hiding the routes prevents\n // accidental UI discoverability and reduces the attack surface for\n // misconfigured permissions.\n enableAdminRoutes = false,\n bypassEnterprisePlanGate = false,\n}: {\n basePath?: string;\n enableAdminRoutes?: boolean;\n bypassEnterprisePlanGate?: boolean;\n}) {\n // Note: basePath is kept for backwards compatibility but not used in route paths\n // since React Router v7 uses relative paths when nested inside a parent <Route>\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n return (\n <Routes>\n {/* Public routes (no auth required) */}\n {/* LoginPage is eager-loaded as it's the most common entry point */}\n <Route path=\"login\" element={<LoginPage />} />\n <Route\n path=\"register\"\n element={\n <Suspense fallback={<PageLoader />}>\n <RegisterPage />\n </Suspense>\n }\n />\n <Route\n path=\"reset-password\"\n element={\n <Suspense fallback={<PageLoader />}>\n <PasswordResetPage />\n </Suspense>\n }\n />\n <Route\n path=\"verify-email\"\n element={\n <Suspense fallback={<PageLoader />}>\n <EmailVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"mfa-verify\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MfaVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"magic-link\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MagicLinkPage />\n </Suspense>\n }\n />\n\n {/* SSO routes (public) */}\n <Route\n path=\"sso/login\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoLoginPage />\n </Suspense>\n }\n />\n <Route\n path=\"sso/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoCallbackPage />\n </Suspense>\n }\n />\n\n {/* OAuth callback route (public) */}\n <Route\n path=\"oauth/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <OAuthCallbackPage />\n </Suspense>\n }\n />\n\n {/* Account reactivation (public) */}\n <Route\n path=\"reactivate\"\n element={\n <Suspense fallback={<PageLoader />}>\n <ReactivatePage />\n </Suspense>\n }\n />\n\n {/* Session bridge (public - embedded as hidden iframe by other app shells) */}\n <Route path=\"session-bridge\" element={<SessionBridgePage />} />\n\n {/* Cross-domain SSO relay (reads local tokens, redirects with them to target) */}\n <Route path=\"sso-relay\" element={<SSORelayPage />} />\n\n {/* Cross-domain SSO callback (receives tokens from relay, stores locally) */}\n <Route path=\"sso-callback\" element={<SSORelayCallbackPage />} />\n\n {/* Cross-domain SSO deposit (bridge host receives tokens on top-level\n redirect right after login on a product origin, writes first-party\n localStorage on the bridge origin so other products can discover\n the session). */}\n <Route path=\"sso-deposit\" element={<SSODepositPage />} />\n\n {/* Cross-domain SLO clear (bridge host clears its central session on\n top-level redirect from a product origin's logout). */}\n <Route path=\"sso-clear\" element={<SSOClearPage />} />\n\n {/* Protected routes (auth required) */}\n <Route\n path=\"security\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.security\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SecurityPage enableAdminRoutes={enableAdminRoutes} />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"sessions\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sessions\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SessionsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"mfa\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.mfa\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <MfaSetupPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n {enableAdminRoutes && showAuditLog && (\n <Route\n path=\"audit-log\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.audit-log\" defaultValue={false}>\n <Suspense fallback={<PageLoader />}>\n <AuditLogPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n )}\n {!enableAdminRoutes && (\n <Route\n path=\"audit-log\"\n element={<Navigate to=\"/auth/login\" replace />}\n />\n )}\n <Route\n path=\"sso\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sso\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SsoConfigPage\n bypassEnterprisePlanGate={bypassEnterprisePlanGate}\n />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UsersPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users/:id\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserDetailsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"profile\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.profile\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserProfilePage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n\n {/* Default redirect - when visiting /auth or /auth/ with no subpath */}\n <Route index element={<Navigate to=\"login\" replace />} />\n </Routes>\n );\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ var o = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,b
9
9
  function s(e) {
10
10
  if (e.hostname === "localhost" || e.hostname === "127.0.0.1" || e.hostname.endsWith(".local.burdenoff.com")) return !0;
11
11
  if (e.protocol !== "https:") return !1;
12
- for (let t of o) if (e.hostname === t || e.hostname === `app.${t}` || e.hostname === `alphaapp.${t}` || e.hostname === `www.${t}`) return !0;
12
+ for (let t of o) 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
13
  return !1;
14
14
  }
15
15
  function c(e) {
@@ -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 { 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\";\n\nconst ALLOWED_RETURN_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]);\n\nfunction isAllowedReturnUrl(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_RETURN_DOMAINS) {\n if (\n url.hostname === domain ||\n url.hostname === `app.${domain}` ||\n url.hostname === `alphaapp.${domain}` ||\n url.hostname === `www.${domain}`\n ) {\n return true;\n }\n }\n return false;\n}\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 || isAllowedReturnUrl(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 (\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":";;;;;;;AA6BA,IAAM,IAAyB,IAAI,IAAI,qkBAkCtC,CAAC;AAEF,SAAS,EAAmB,GAAmB;AAE7C,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,OAAO,IAExB,QAAO;AAGX,QAAO;;AAGT,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,EAGhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,2FAA4F,CAAA;EACvG,CAAA"}
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 { 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\";\n\nconst ALLOWED_RETURN_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]);\n\nfunction isAllowedReturnUrl(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_RETURN_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 // valid post-deposit return targets. Without these, a login that lands on\n // the bridge host falls back to \"/\" instead of returning to the admin app.\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 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 || isAllowedReturnUrl(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 (\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":";;;;;;;AA6BA,IAAM,IAAyB,IAAI,IAAI,qkBAkCtC,CAAC;AAEF,SAAS,EAAmB,GAAmB;AAE7C,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,OAI7B,EAAI,aAAa,SAAS,OAC1B,EAAI,aAAa,cAAc,OAC/B,EAAI,aAAa,OAAO,IAExB,QAAO;AAGX,QAAO;;AAGT,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,EAGhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,2FAA4F,CAAA;EACvG,CAAA"}
@@ -9,7 +9,7 @@ var r = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,b
9
9
  function c(e) {
10
10
  if (e.hostname === "localhost" || e.hostname === "127.0.0.1" || e.hostname.endsWith(".local.burdenoff.com")) return !0;
11
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 === `www.${t}`) return !0;
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
13
  return !1;
14
14
  }
15
15
  function l() {
@@ -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]);\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 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 return URL — just show a blank page\n return;\n }\n\n // Read tokens from localStorage (this origin has them). Prefer the shared\n // SSO cookie profile because a relay often runs in a new tab with empty\n // sessionStorage and stale historical profiles in localStorage.\n const profileId = resolveRelayProfileId();\n if (!profileId) {\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n const profilesRaw = readProfilesRaw();\n\n if (!tokensRaw || !userRaw) {\n // No tokens — redirect back without them\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n // Encode the auth state as a base64 URL fragment\n const payload = {\n profileId,\n tokens: tokensRaw,\n user: userRaw,\n profiles: profilesRaw,\n };\n const encoded = btoa(JSON.stringify(payload));\n\n // Build the callback URL on the target domain\n // Parse returnTo to get the target origin\n let callbackUrl: string;\n try {\n const targetUrl = new URL(returnTo);\n if (!isAllowedRelayTarget(targetUrl)) {\n window.location.replace(\"/\");\n return;\n }\n const returnPath = targetUrl.pathname + targetUrl.search;\n callbackUrl = `${targetUrl.origin}/auth/sso-callback?returnTo=${encodeURIComponent(returnPath)}#sso=${encoded}`;\n } catch {\n // returnTo is a relative path — can't do cross-domain relay\n window.location.replace(returnTo);\n return;\n }\n\n // Redirect to the callback\n window.location.replace(callbackUrl);\n }, [searchParams]);\n\n return (\n <div className=\"flex items-center justify-center min-h-screen bg-bg-canvas\">\n <div className=\"animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n"],"mappings":";;;;AAoBA,IAAM,IAAwB,IAAI,IAAI,qkBAkCrC,CAAC,EAEI,IAAoB,cACpB,IAAqB,cACrB,IAAkB,IAAI,IAAI,CAAC,aAAa,YAAY,CAAC,EAErD,IAAsB;CAC1B,UAAU;CACV,kBAAkB;CACnB;AAED,SAAS,EAAqB,GAAmB;AAI/C,KAHI,EAAI,aAAa,eAAe,EAAI,aAAa,eAGjD,EAAI,SAAS,SAAS,uBAAuB,CAC/C,QAAO;AAET,KAAI,EAAI,aAAa,SACnB,QAAO;AAET,MAAK,IAAM,KAAU,EACnB,KACE,EAAI,aAAa,KACjB,EAAI,aAAa,OAAO,OACxB,EAAI,aAAa,YAAY,OAC7B,EAAI,aAAa,OAAO,IAExB,QAAO;AAGX,QAAO;;AAGT,SAAS,IAA0B;AACjC,KAAI;EACF,IAAM,IAAW,OAAO,SAAS,UAC3B,IAAS,EAAS,SAAS,uBAAuB,GACpD,yBACA,KAAA;AACJ,OAAK,IAAM,KAAc,CAAC,UAAU,iBAAiB,CAKnD,CAJA,SAAS,SAAS,GAAG,EAAW,uBAC5B,MACF,SAAS,SAAS,GAAG,EAAW,oBAAoB,EAAO,eAEzD,KAAY,CAAC,EAAgB,IAAI,EAAS,KAC5C,SAAS,SAAS,GAAG,EAAW,oBAAoB,EAAS;SAG3D;;AAKV,SAAS,IAA0B;AACjC,KAAI;AACF,UACE,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAAC,IAAI,EAAkB,IAAI,IAErE,MAAM,IAAI,CACV,QAAQ,MAAW,EAAO,SAAS,EAAE;SAClC;AACN,SAAO,EAAE;;;AAIb,SAAS,EAAkB,GAA0B;AACnD,KAAI;EACF,IAAM,IAAY,IAAI,IAAI,GAAU,OAAO,SAAS,OAAO;AAQ3D,SANE,EAAU,WAAW,OAAO,SAAS,UACrC,CAAC,EAAqB,EAAU,GAEzB,iBAET,EAAU,aAAa,IAAI,GAAoB,IAAI,EAC5C,EAAU;SACX;AACN,SAAO;;;AAIX,SAAS,IAAwC;AAC/C,QAAO,GAAmB,EAAE,MAAM;;AAGpC,SAAS,IAGA;AACP,KAAI;AACF,OAAK,IAAM,KAAc,CAAC,UAAU,iBAAiB,EAAW;GAC9D,IAAM,IAAQ,SAAS,OAAO,MAAM,EAAoB,GAAY;AACpE,OAAI,CAAC,EAAO;GAEZ,IAAM,IAAS,KAAK,MAAM,mBAAmB,EAAM,GAAG,CAAC;AAGvD,OAAI,OAAO,KAAW,SAAU,QAAO;IAAE,IAAI;IAAQ,QAAQ;IAAM;AACnE,OAAI,OAAO,EAAO,MAAO,YAAY,EAAO,GAAG,SAAS,EACtD,QAAO;IACL,IAAI,EAAO;IACX,QAAQ,OAAO,EAAO,UAAW,WAAW,EAAO,SAAS;IAC7D;;SAGC;AACN,SAAO;;AAET,QAAO;;AAGT,SAAS,EAA4B,GAA2B;AAC9D,KAAI;EACF,IAAM,IAAY,GAAmB;AACrC,MAAI,CAAC,GAAW,OAAQ,QAAO;EAE/B,IAAM,IAAW,IAAI,IAAI,EAAU,OAAO;AAE1C,MADI,CAAC,EAAqB,EAAS,IAC/B,EAAS,WAAW,OAAO,SAAS,OAAQ,QAAO;EAEvD,IAAM,IAAa,GAAe;AAClC,MAAI,EAAW,SAAS,EAAS,OAAO,IAAI,EAAW,UAAU,EAC/D,QAAO;EAGT,IAAM,IAAe,IAAI,IAAI,mBAAmB,EAAS,OAAO;AAQhE,SAPA,EAAa,aAAa,IAAI,YAAY,EAAS,EACnD,EAAa,aAAa,IACxB,GACA,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,GAAY,OAAO,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CACvE,EAED,OAAO,SAAS,QAAQ,EAAa,KAAK,EACnC;SACD;AACN,SAAO;;;AAIX,SAAS,IAAiC;AACxC,QAAO,aAAa,QAAQ,cAAc;;AAG5C,SAAS,IAA2B;AAClC,KAAI;EACF,IAAM,IAAc,GAAiB;AAIrC,UAHiB,IACZ,KAAK,MAAM,EAAY,GACxB,EAAE,EAEH,KAAK,MAAY,EAAQ,GAAG,CAC5B,QAAQ,MAAqB,OAAO,KAAO,YAAY,EAAG,SAAS,EAAE;SAClE;AACN,SAAO,EAAE;;;AAIb,SAAS,IAAuC;CAC9C,IAAM,IAAa;EACjB,GAAwB;EACxB,eAAe,QAAQ,oBAAoB;EAC3C,GAAG,GAAgB;EACpB,CAAC,QAAQ,MAAqB,OAAO,KAAO,YAAY,EAAG,SAAS,EAAE;AAEvE,MAAK,IAAM,KAAa,MAAM,KAAK,IAAI,IAAI,EAAW,CAAC,EAAE;EACvD,IAAM,IAAY,aAAa,QAAQ,QAAQ,EAAU,SAAS,EAC5D,IAAU,aAAa,QAAQ,QAAQ,EAAU,OAAO;AAC9D,MAAI,KAAa,EAEf,QADA,eAAe,QAAQ,qBAAqB,EAAU,EAC/C;;AAIX,QAAO;;AAGT,SAAgB,IAAe;CAC7B,IAAM,CAAC,KAAgB,GAAiB;AA8DxC,QA5DA,QAAgB;EACd,IAAM,IAAW,EAAa,IAAI,WAAW;AAC7C,MAAI,CAAC,EAEH;EAMF,IAAM,IAAY,GAAuB;AACzC,MAAI,CAAC,GAAW;AACd,OAAI,EAA4B,EAAS,CAAE;AAE3C,GADA,GAAmB,EACnB,OAAO,SAAS,QAAQ,EAAkB,EAAS,CAAC;AACpD;;EAGF,IAAM,IAAY,aAAa,QAAQ,QAAQ,EAAU,SAAS,EAC5D,IAAU,aAAa,QAAQ,QAAQ,EAAU,OAAO,EACxD,IAAc,GAAiB;AAErC,MAAI,CAAC,KAAa,CAAC,GAAS;AAE1B,OAAI,EAA4B,EAAS,CAAE;AAE3C,GADA,GAAmB,EACnB,OAAO,SAAS,QAAQ,EAAkB,EAAS,CAAC;AACpD;;EAUF,IAAM,IAAU,KAAK,KAAK,UANV;GACd;GACA,QAAQ;GACR,MAAM;GACN,UAAU;GACX,CAC2C,CAAC,EAIzC;AACJ,MAAI;GACF,IAAM,IAAY,IAAI,IAAI,EAAS;AACnC,OAAI,CAAC,EAAqB,EAAU,EAAE;AACpC,WAAO,SAAS,QAAQ,IAAI;AAC5B;;GAEF,IAAM,IAAa,EAAU,WAAW,EAAU;AAClD,OAAc,GAAG,EAAU,OAAO,8BAA8B,mBAAmB,EAAW,CAAC,OAAO;UAChG;AAEN,UAAO,SAAS,QAAQ,EAAS;AACjC;;AAIF,SAAO,SAAS,QAAQ,EAAY;IACnC,CAAC,EAAa,CAAC,EAGhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,2FAA4F,CAAA;EACvG,CAAA"}
1
+ {"version":3,"file":"SSORelayPage.js","names":[],"sources":["../../../src/auth/pages/SSORelayPage.tsx"],"sourcesContent":["/**\n * SSORelayPage — Cross-domain SSO token relay.\n *\n * This page is loaded on the ORIGIN where the user is logged in.\n * It reads tokens from localStorage and redirects to the TARGET domain's\n * /auth/sso-callback with the tokens in a URL fragment (not query param\n * for security — fragments are not sent to servers).\n *\n * Flow:\n * 1. User visits productA.com → not logged in → detects SSO cookie\n * 2. Redirect to productB.com/auth/sso-relay?returnTo=productA.com/dashboard\n * (productB is where the user IS logged in — identified by cookie)\n * 3. This page reads tokens from productB's localStorage\n * 4. Redirects to productA.com/auth/sso-callback#token=<base64-encoded-tokens>\n * 5. SSORelayCallbackPage on productA decodes and stores tokens, redirects to /dashboard\n */\n\nimport { useEffect } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\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]);\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 return URL — just show a blank page\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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"}