@burdenoff/microfe-auth 2026.1031.1 → 2026.1101.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.
Files changed (135) hide show
  1. package/dist/auth/AuthRoot.js +14 -11
  2. package/dist/auth/AuthRoot.js.map +1 -1
  3. package/dist/auth/AuthRoutes.js +1 -2
  4. package/dist/auth/AuthRoutes.js.map +1 -1
  5. package/dist/auth/components/AuthIcons.js +1 -2
  6. package/dist/auth/components/AuthIcons.js.map +1 -1
  7. package/dist/auth/components/AuthLayout.js +1 -2
  8. package/dist/auth/components/AuthLayout.js.map +1 -1
  9. package/dist/auth/components/CaptchaV2.js +9 -10
  10. package/dist/auth/components/CaptchaV2.js.map +1 -1
  11. package/dist/auth/components/ConfirmActionDialog.js +1 -2
  12. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  13. package/dist/auth/components/profile/UserProfileMenu.js +7 -6
  14. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  15. package/dist/auth/components/workspace/WorkspaceSelector.js +1 -2
  16. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  17. package/dist/auth/constants/permissions.js +1 -2
  18. package/dist/auth/constants/permissions.js.map +1 -1
  19. package/dist/auth/constants/sessionKeys.js +1 -2
  20. package/dist/auth/constants/sessionKeys.js.map +1 -1
  21. package/dist/auth/hooks/contextDeepLink.js +1 -2
  22. package/dist/auth/hooks/contextDeepLink.js.map +1 -1
  23. package/dist/auth/hooks/index.js +12 -11
  24. package/dist/auth/hooks/useAccountOperations.js +1 -2
  25. package/dist/auth/hooks/useAccountOperations.js.map +1 -1
  26. package/dist/auth/hooks/useActiveContextBootstrapMirror.js +1 -2
  27. package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
  28. package/dist/auth/hooks/useAuthOperations.js +37 -37
  29. package/dist/auth/hooks/useAuthOperations.js.map +1 -1
  30. package/dist/auth/hooks/useAuthPermissions.js +1 -2
  31. package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
  32. package/dist/auth/hooks/useCaptchaV3.js +1 -2
  33. package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
  34. package/dist/auth/hooks/useContextSwitcherData.js +86 -79
  35. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  36. package/dist/auth/hooks/useMfaOperations.js +4 -5
  37. package/dist/auth/hooks/useMfaOperations.js.map +1 -1
  38. package/dist/auth/hooks/useMyWorkspaces.js +4 -5
  39. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  40. package/dist/auth/hooks/useOAuthOperations.js +11 -12
  41. package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
  42. package/dist/auth/hooks/usePasswordOperations.js +1 -2
  43. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  44. package/dist/auth/hooks/useSessionOperations.js +3 -4
  45. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  46. package/dist/auth/hooks/useSignupCaptcha.js +1 -2
  47. package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
  48. package/dist/auth/hooks/useSsoOperations.js +80 -81
  49. package/dist/auth/hooks/useSsoOperations.js.map +1 -1
  50. package/dist/auth/hooks/useTokenRefresh.js +8 -12
  51. package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
  52. package/dist/auth/hooks/useUserOperations.js +1 -2
  53. package/dist/auth/hooks/useUserOperations.js.map +1 -1
  54. package/dist/auth/hooks/useWorkspaceOperations.js +1 -2
  55. package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
  56. package/dist/auth/hooks/useWorkspaceToken.js +1 -2
  57. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  58. package/dist/auth/index.js +19 -18
  59. package/dist/auth/pages/AuditLogPage.js +21 -22
  60. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  61. package/dist/auth/pages/EmailVerifyPage.js +15 -17
  62. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  63. package/dist/auth/pages/LoginPage.js +89 -105
  64. package/dist/auth/pages/LoginPage.js.map +1 -1
  65. package/dist/auth/pages/MagicLinkPage.js +30 -31
  66. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  67. package/dist/auth/pages/MfaSetupPage.js +18 -20
  68. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  69. package/dist/auth/pages/MfaVerifyPage.js +31 -35
  70. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  71. package/dist/auth/pages/OAuthCallbackPage.js +4 -11
  72. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  73. package/dist/auth/pages/PasswordResetPage.js +20 -23
  74. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  75. package/dist/auth/pages/ReactivatePage.js +14 -18
  76. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  77. package/dist/auth/pages/RegisterPage.js +180 -181
  78. package/dist/auth/pages/RegisterPage.js.map +1 -1
  79. package/dist/auth/pages/SSOClearPage.js +9 -10
  80. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  81. package/dist/auth/pages/SSODepositPage.js +9 -13
  82. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  83. package/dist/auth/pages/SSORelayCallbackPage.js +3 -7
  84. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  85. package/dist/auth/pages/SSORelayPage.js +11 -10
  86. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  87. package/dist/auth/pages/SecurityPage.js +19 -21
  88. package/dist/auth/pages/SecurityPage.js.map +1 -1
  89. package/dist/auth/pages/SessionBridgePage.js +9 -16
  90. package/dist/auth/pages/SessionBridgePage.js.map +1 -1
  91. package/dist/auth/pages/SessionsPage.js +17 -18
  92. package/dist/auth/pages/SessionsPage.js.map +1 -1
  93. package/dist/auth/pages/SsoCallbackPage.js +17 -22
  94. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  95. package/dist/auth/pages/SsoConfigPage.js +69 -69
  96. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  97. package/dist/auth/pages/SsoLoginPage.js +9 -10
  98. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  99. package/dist/auth/pages/UserDetailsPage.js +16 -23
  100. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  101. package/dist/auth/pages/UserProfilePage.js +140 -141
  102. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  103. package/dist/auth/pages/UsersPage.js +446 -453
  104. package/dist/auth/pages/UsersPage.js.map +1 -1
  105. package/dist/auth/providers/AuthProvider.js +1 -2
  106. package/dist/auth/providers/AuthProvider.js.map +1 -1
  107. package/dist/auth/providers/CaptchaProvider.js +1 -2
  108. package/dist/auth/providers/CaptchaProvider.js.map +1 -1
  109. package/dist/auth/store/authStore.js +5 -12
  110. package/dist/auth/store/authStore.js.map +1 -1
  111. package/dist/auth/utils/captchaConfig.js +5 -8
  112. package/dist/auth/utils/captchaConfig.js.map +1 -1
  113. package/dist/auth/utils/formatters.js +1 -2
  114. package/dist/auth/utils/formatters.js.map +1 -1
  115. package/dist/auth/utils/gatewayBaseUrls.js +1 -2
  116. package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
  117. package/dist/auth/utils/legalUrls.js +2 -3
  118. package/dist/auth/utils/legalUrls.js.map +1 -1
  119. package/dist/auth/utils/redirect.js +1 -2
  120. package/dist/auth/utils/redirect.js.map +1 -1
  121. package/dist/auth/utils/ssoOrigins.js +2 -3
  122. package/dist/auth/utils/ssoOrigins.js.map +1 -1
  123. package/dist/auth/utils/validation.js +34 -36
  124. package/dist/auth/utils/validation.js.map +1 -1
  125. package/dist/generated/global-operations.js +2 -3
  126. package/dist/generated/global-operations.js.map +1 -1
  127. package/dist/generated/wspace-operations.js +2 -3
  128. package/dist/generated/wspace-operations.js.map +1 -1
  129. package/dist/microfe-auth.css +1 -1
  130. package/dist/shared/hooks/useTr.js +1 -2
  131. package/dist/shared/hooks/useTr.js.map +1 -1
  132. package/dist/translations/en.js +1 -2
  133. package/dist/translations/en.js.map +1 -1
  134. package/dist/utils/nativeBridge.js +1 -2
  135. package/package.json +7 -3
@@ -1 +1 @@
1
- {"version":3,"file":"authStore.js","names":[],"sources":["../../../src/auth/store/authStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { ContextLinkDenial } from \"../hooks/contextDeepLink\";\nimport { createProfileScopedStorage } from \"@burdenoff/fe-libs/shared/utils\";\nimport type {\n User,\n Session,\n MfaDevice,\n OAuthAccount,\n TrustedDevice,\n SecurityAudit,\n ApiKey,\n App,\n AppUserDelegation,\n Workspace,\n Organization,\n Project,\n} from \"../types\";\n\n/**\n * Sync auth state to profile-scoped storage (ProfileStorageManager keys).\n * The active profile ID comes from sessionStorage (\"bf-active-profile\").\n * Data is stored under localStorage keys: bf-p-{profileId}-user, bf-p-{profileId}-tokens, etc.\n */\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\nconst AUTH_STORAGE_KEY = \"auth-storage\";\nconst AUTH_STATE_CLEARED_EVENT = \"bf-auth-state-cleared\";\n\nfunction getActiveProfileForSync(): string | null {\n return sessionStorage.getItem(BF_ACTIVE);\n}\n\nfunction pfKey(profileId: string, suffix: string): string {\n return `${BF_PREFIX}-${profileId}-${suffix}`;\n}\n\nfunction clearPersistedAuthStorage(profileId?: string | null): void {\n localStorage.removeItem(AUTH_STORAGE_KEY);\n if (profileId) {\n localStorage.removeItem(pfKey(profileId, AUTH_STORAGE_KEY));\n }\n for (const key of Object.keys(localStorage)) {\n if (\n key.startsWith(`${BF_PREFIX}-`) &&\n key.endsWith(`-${AUTH_STORAGE_KEY}`)\n ) {\n localStorage.removeItem(key);\n }\n }\n}\n\ninterface ProfileMeta {\n id: string;\n email: string;\n name: string;\n avatar?: string;\n lastLogin: string;\n}\n\n/** Dispatch custom event to notify fe-libs AuthProvider of storage changes (same-tab). */\nfunction notifyProfileStorageChange(): void {\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(new Event(\"bf-profile-storage-change\"));\n }\n}\n\nfunction syncToProfileStorage(data: {\n user?: User | null;\n accessToken?: string | null;\n refreshToken?: string | null;\n workspaceToken?: string | null;\n clear?: boolean;\n}) {\n if (typeof window === \"undefined\") return;\n\n const profileId = getActiveProfileForSync();\n if (!profileId && !data.user && !data.clear) return;\n\n // For login/signup: prefer the user's own ID as the target profile.\n // This prevents overwriting an existing active profile when a NEW user\n // signs up or logs in (multi-account scenario).\n const targetId = data.user?.id ?? profileId;\n if (!targetId && !data.clear) return;\n\n if (data.clear) {\n clearPersistedAuthStorage(targetId);\n if (!targetId) {\n localStorage.removeItem(BF_PROFILES);\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n // Remove profile data keys\n localStorage.removeItem(pfKey(targetId, \"user\"));\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n // Remove from profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const filtered = profiles.filter((p) => p.id !== targetId);\n localStorage.setItem(BF_PROFILES, JSON.stringify(filtered));\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n\n if (!targetId) return;\n\n // An account whose email is not verified cannot sign in (the backend rejects\n // it with EMAIL_NOT_VERIFIED), so it must never become a switchable profile.\n // signUp() keeps the pending user in this store for display only (BOFF-7215).\n if (data.user && data.user.emailVerified === false) return;\n\n if (data.user) {\n localStorage.setItem(pfKey(targetId, \"user\"), JSON.stringify(data.user));\n // Update profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const existing = profiles.filter((p) => p.id !== targetId);\n existing.push({\n id: targetId,\n email: data.user.email ?? \"\",\n name:\n data.user.name ??\n `${data.user.firstName ?? \"\"} ${data.user.lastName ?? \"\"}`.trim(),\n avatar: data.user.avatar,\n lastLogin: new Date().toISOString(),\n });\n localStorage.setItem(BF_PROFILES, JSON.stringify(existing));\n } catch {\n /* ignore */\n }\n // Set as active profile in this tab\n sessionStorage.setItem(BF_ACTIVE, targetId);\n }\n\n if (data.accessToken !== undefined || data.refreshToken !== undefined) {\n const existingRaw = localStorage.getItem(pfKey(targetId, \"tokens\"));\n const existing = existingRaw ? JSON.parse(existingRaw) : {};\n // Compute expiresAt from the actual JWT exp claim if a new access token is provided\n let expiresAt = existing.expiresAt ?? Date.now() + 60 * 60 * 1000;\n const tokenToCheck = data.accessToken ?? existing.accessToken;\n if (tokenToCheck) {\n try {\n const raw = tokenToCheck\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n if (typeof payload.exp === \"number\") {\n expiresAt = payload.exp * 1000; // JWT exp is in seconds → ms\n }\n } catch {\n // Fallback: keep existing or default 1h\n }\n }\n const merged = {\n ...existing,\n ...(data.accessToken !== undefined\n ? { accessToken: data.accessToken }\n : {}),\n ...(data.refreshToken !== undefined\n ? { refreshToken: data.refreshToken }\n : {}),\n expiresAt,\n };\n if (merged.accessToken) {\n localStorage.setItem(pfKey(targetId, \"tokens\"), JSON.stringify(merged));\n } else {\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n }\n }\n\n if (data.workspaceToken !== undefined) {\n if (data.workspaceToken) {\n localStorage.setItem(\n pfKey(targetId, \"workspace-token\"),\n data.workspaceToken,\n );\n } else {\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n }\n }\n\n // Notify fe-libs AuthProvider of the change (same-tab custom event)\n notifyProfileStorageChange();\n\n // Set SSO cookie for cross-domain session sharing (works in all envs)\n // Stores profile ID + the origin where the user logged in.\n // Other shells use the origin to redirect to /auth/sso-relay for token relay.\n try {\n const hostname = window.location.hostname;\n // For local dev, set cookie on shared .local.burdenoff.com domain\n // For production, set cookie on current domain only (relay handles cross-domain)\n const isSharedDomain = hostname.endsWith(\".local.burdenoff.com\");\n const domain = isSharedDomain ? \".local.burdenoff.com\" : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieOpts = `${domainPart}; path=/; samesite=lax${securePart}`;\n\n if (data.accessToken && targetId) {\n // Store profile ID + login origin for SSO relay\n const ssoValue = JSON.stringify({\n id: targetId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n }\n\n if (data.clear) {\n document.cookie = \"bf-sso=; path=/; max-age=0\";\n document.cookie = \"bf-sso-profile=; path=/; max-age=0\";\n if (domain) {\n document.cookie = `bf-sso=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `bf-sso=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* ignore */\n }\n}\n\ninterface AuthState {\n // Auth state\n user: User | null;\n accessToken: string | null;\n refreshToken: string | null;\n isAuthenticated: boolean;\n\n // Organization state\n organizations: Organization[];\n currentOrganizationId: string | null;\n isLoadingOrganizations: boolean;\n\n // Workspace state\n workspaces: Workspace[];\n currentWorkspaceId: string | null;\n workspaceToken: string | null;\n isLoadingWorkspaces: boolean;\n\n // Project state\n projects: Project[];\n currentProjectId: string | null;\n isLoadingProjects: boolean;\n\n /**\n * A deep link (`?workspace=` / `?org=`) named a workspace or organization\n * the caller cannot open. The context fell back to one the caller can open;\n * the shell shows this instead of the page until it is dismissed. Never\n * persisted: it describes this page load only.\n */\n contextLinkDenial: ContextLinkDenial | null;\n\n // MFA state\n mfaRequired: boolean;\n mfaChallengeId: string | null;\n mfaDevices: MfaDevice[];\n\n // Session state\n activeSessions: Session[];\n currentSessionId: string | null;\n\n // OAuth state\n linkedAccounts: OAuthAccount[];\n\n // Security state\n trustedDevices: TrustedDevice[];\n securityEvents: SecurityAudit[];\n\n // API Keys state\n apiKeys: ApiKey[];\n\n // Apps state\n apps: App[];\n currentApp: App | null;\n\n // Delegations state\n delegations: AppUserDelegation[];\n grantedDelegations: AppUserDelegation[];\n\n // UI state\n isLoading: boolean;\n error: string | null;\n errorCode: string | null;\n\n // Actions - Auth\n setUser: (user: User | null) => void;\n setAccessToken: (token: string | null) => void;\n setAuthTokens: (accessToken: string, refreshToken: string) => void;\n setAuthenticated: (isAuthenticated: boolean) => void;\n clearAuth: () => void;\n\n // Actions - Organization\n setOrganizations: (organizations: Organization[]) => void;\n setCurrentOrganizationId: (orgId: string | null) => void;\n setLoadingOrganizations: (loading: boolean) => void;\n\n // Actions - Workspace\n setWorkspaces: (workspaces: Workspace[]) => void;\n setCurrentWorkspaceId: (workspaceId: string | null) => void;\n setWorkspaceToken: (token: string | null) => void;\n setLoadingWorkspaces: (loading: boolean) => void;\n clearWorkspace: () => void;\n\n // Actions - Project\n setProjects: (projects: Project[]) => void;\n setCurrentProjectId: (projectId: string | null) => void;\n setLoadingProjects: (loading: boolean) => void;\n clearProject: () => void;\n\n // Actions - Deep links\n setContextLinkDenial: (denial: ContextLinkDenial | null) => void;\n\n // Actions - Context (clears org, workspace, project)\n clearContext: () => void;\n\n // Actions - Profile Switch (load from profile storage without syncing back)\n loadFromProfile: (profileId: string) => void;\n\n // Actions - MFA\n setMfaRequired: (required: boolean, challengeId?: string) => void;\n setMfaDevices: (devices: MfaDevice[]) => void;\n removeMfaDevice: (deviceId: string) => void;\n\n // Actions - Sessions\n setActiveSessions: (sessions: Session[]) => void;\n removeSession: (sessionId: string) => void;\n\n // Actions - OAuth\n setLinkedAccounts: (accounts: OAuthAccount[]) => void;\n removeLinkedAccount: (accountId: string) => void;\n\n // Actions - Security\n setTrustedDevices: (devices: TrustedDevice[]) => void;\n removeTrustedDevice: (deviceId: string) => void;\n setSecurityEvents: (events: SecurityAudit[]) => void;\n\n // Actions - API Keys\n setApiKeys: (keys: ApiKey[]) => void;\n removeApiKey: (keyId: string) => void;\n\n // Actions - Apps\n setApps: (apps: App[]) => void;\n setCurrentApp: (app: App | null) => void;\n addApp: (app: App) => void;\n updateApp: (app: App) => void;\n removeApp: (appId: string) => void;\n\n // Actions - Delegations\n setDelegations: (delegations: AppUserDelegation[]) => void;\n setGrantedDelegations: (delegations: AppUserDelegation[]) => void;\n addDelegation: (delegation: AppUserDelegation) => void;\n removeDelegation: (delegationId: string) => void;\n\n // Actions - UI\n setLoading: (loading: boolean) => void;\n setError: (error: string | null, errorCode?: string | null) => void;\n}\n\n/**\n * Auth Zustand store — browser-tab-local state.\n *\n * SAFE for in-memory (no Redis needed):\n * - accessToken / refreshToken / user: per-tab, backed by profileScopedStorage\n * (localStorage key bf-p-{profileId}-*). Tabs sharing the same profile ID\n * stay in sync via the \"bf-profile-storage-change\" custom event + \"storage\"\n * cross-tab event.\n * - workspaceToken: same pattern as above.\n * - mfaRequired / mfaChallengeId: transient login-flow state; not needed\n * cross-tab (each login has its own challenge).\n * - organizations / workspaces / projects lists: cached context, re-fetched\n * after account switch; no cross-tab sharing needed.\n * - UI state (isLoading, error): purely ephemeral.\n *\n * NOT safe for in-memory (needs Redis in future if required server-side):\n * - Nothing currently — all server-side session tracking is done by the\n * backend (global-auth-svc) using its own DB. The frontend only holds\n * short-lived JWT access tokens that expire independently.\n */\nexport const useAuthStore = create<AuthState>()(\n persist(\n (set) => ({\n // Initial state\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n contextLinkDenial: null,\n mfaRequired: false,\n mfaChallengeId: null,\n mfaDevices: [],\n activeSessions: [],\n currentSessionId: null,\n linkedAccounts: [],\n trustedDevices: [],\n securityEvents: [],\n apiKeys: [],\n apps: [],\n currentApp: null,\n delegations: [],\n grantedDelegations: [],\n isLoading: false,\n error: null,\n errorCode: null,\n\n // Actions - Auth (with profile storage sync)\n setUser: (user) => {\n set({ user });\n syncToProfileStorage({ user });\n },\n\n setAccessToken: (token) => {\n set({ accessToken: token });\n syncToProfileStorage({ accessToken: token });\n },\n\n setAuthTokens: (accessToken, refreshToken) => {\n // Only set tokens here. Do NOT overwrite user — setUser() handles that\n // with the full user object from the GraphQL response (firstName, lastName, etc.).\n // Fall back to JWT-decoded user ONLY if no user is currently in the store.\n const currentState = useAuthStore.getState();\n if (currentState.user) {\n // User already set (from setUser call) — just update tokens\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n } else {\n // No user yet — decode JWT as fallback\n try {\n const b64 = accessToken\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n const user: User = {\n id: payload.sub,\n email: payload.email,\n username: payload.username,\n role: payload.role,\n status: \"ACTIVE\",\n emailVerified: true,\n mfaEnabled: false,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n set({ accessToken, refreshToken, user });\n syncToProfileStorage({ accessToken, refreshToken, user });\n } catch (error) {\n console.error(\"[AuthStore] Failed to decode JWT:\", error);\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n }\n }\n },\n\n setAuthenticated: (isAuthenticated) => set({ isAuthenticated }),\n\n clearAuth: () => {\n set({\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n mfaRequired: false,\n mfaChallengeId: null,\n activeSessions: [],\n currentSessionId: null,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n workspaceToken: null,\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n contextLinkDenial: null,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n });\n syncToProfileStorage({ clear: true });\n },\n\n // Actions - Organization\n setOrganizations: (organizations) => set({ organizations }),\n\n setCurrentOrganizationId: (orgId) =>\n set((state) =>\n state.currentOrganizationId === orgId\n ? state\n : { currentOrganizationId: orgId },\n ),\n\n setLoadingOrganizations: (loading) =>\n set({ isLoadingOrganizations: loading }),\n\n // Actions - Workspace\n setWorkspaces: (workspaces) => set({ workspaces }),\n\n setCurrentWorkspaceId: (workspaceId) =>\n set((state) =>\n state.currentWorkspaceId === workspaceId\n ? state\n : { currentWorkspaceId: workspaceId },\n ),\n\n setWorkspaceToken: (token) => {\n set({ workspaceToken: token });\n syncToProfileStorage({ workspaceToken: token });\n },\n\n setLoadingWorkspaces: (loading) => set({ isLoadingWorkspaces: loading }),\n\n clearWorkspace: () =>\n set({ currentWorkspaceId: null, workspaceToken: null, workspaces: [] }),\n\n // Actions - Project\n setProjects: (projects) => set({ projects }),\n\n setCurrentProjectId: (projectId) =>\n set((state) =>\n state.currentProjectId === projectId\n ? state\n : { currentProjectId: projectId },\n ),\n\n setLoadingProjects: (loading) => set({ isLoadingProjects: loading }),\n\n clearProject: () => set({ currentProjectId: null, projects: [] }),\n\n // Actions - Context (clears org, workspace, project)\n setContextLinkDenial: (denial) => set({ contextLinkDenial: denial }),\n\n clearContext: () =>\n set({\n contextLinkDenial: null,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n }),\n\n // Actions - Profile Switch\n // Loads user/tokens from profile-scoped localStorage WITHOUT syncing back.\n // Used during account switching to align zustand with the new active profile.\n loadFromProfile: (profileId: string) => {\n let user: User | null = null;\n let accessToken: string | null = null;\n let refreshToken: string | null = null;\n let workspaceToken: string | null = null;\n\n try {\n const userRaw = localStorage.getItem(pfKey(profileId, \"user\"));\n if (userRaw) user = JSON.parse(userRaw);\n } catch {\n /* ignore */\n }\n\n try {\n const tokensRaw = localStorage.getItem(pfKey(profileId, \"tokens\"));\n if (tokensRaw) {\n const tokens = JSON.parse(tokensRaw);\n accessToken = tokens.accessToken ?? null;\n refreshToken = tokens.refreshToken ?? null;\n }\n } catch {\n /* ignore */\n }\n\n workspaceToken = localStorage.getItem(\n pfKey(profileId, \"workspace-token\"),\n );\n\n // Set everything in one batch — clears context data so it will be re-fetched\n set({\n user,\n accessToken,\n refreshToken,\n isAuthenticated: !!accessToken && !!user,\n workspaceToken,\n // Clear all context — will be re-fetched for the new user\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n // Clear MFA state\n mfaRequired: false,\n mfaChallengeId: null,\n });\n\n // Notify listeners (e.g., AuthTokenProvider) without writing back to storage\n notifyProfileStorageChange();\n },\n\n // Actions - MFA\n setMfaRequired: (required, challengeId) =>\n set({ mfaRequired: required, mfaChallengeId: challengeId || null }),\n\n setMfaDevices: (devices) => set({ mfaDevices: devices }),\n\n removeMfaDevice: (deviceId) =>\n set((state) => ({\n mfaDevices: state.mfaDevices.filter((d) => d.id !== deviceId),\n })),\n\n // Actions - Sessions\n setActiveSessions: (sessions) => set({ activeSessions: sessions }),\n\n removeSession: (sessionId) =>\n set((state) => ({\n activeSessions: state.activeSessions.filter(\n (s) => s.id !== sessionId,\n ),\n })),\n\n // Actions - OAuth\n setLinkedAccounts: (accounts) => set({ linkedAccounts: accounts }),\n\n removeLinkedAccount: (accountId) =>\n set((state) => ({\n linkedAccounts: state.linkedAccounts.filter(\n (a) => a.id !== accountId,\n ),\n })),\n\n // Actions - Security\n setTrustedDevices: (devices) => set({ trustedDevices: devices }),\n\n removeTrustedDevice: (deviceId) =>\n set((state) => ({\n trustedDevices: state.trustedDevices.filter((d) => d.id !== deviceId),\n })),\n\n setSecurityEvents: (events) => set({ securityEvents: events }),\n\n // Actions - API Keys\n setApiKeys: (keys) => set({ apiKeys: keys }),\n\n removeApiKey: (keyId) =>\n set((state) => ({\n apiKeys: state.apiKeys.filter((k) => k.id !== keyId),\n })),\n\n // Actions - Apps\n setApps: (apps) => set({ apps }),\n\n setCurrentApp: (app) => set({ currentApp: app }),\n\n addApp: (app) =>\n set((state) => ({\n apps: [...state.apps, app],\n })),\n\n updateApp: (app) =>\n set((state) => ({\n apps: state.apps.map((a) => (a.id === app.id ? app : a)),\n currentApp: state.currentApp?.id === app.id ? app : state.currentApp,\n })),\n\n removeApp: (appId) =>\n set((state) => ({\n apps: state.apps.filter((a) => a.id !== appId),\n currentApp: state.currentApp?.id === appId ? null : state.currentApp,\n })),\n\n // Actions - Delegations\n setDelegations: (delegations) => set({ delegations }),\n\n setGrantedDelegations: (delegations) =>\n set({ grantedDelegations: delegations }),\n\n addDelegation: (delegation) =>\n set((state) => ({\n delegations: [...state.delegations, delegation],\n })),\n\n removeDelegation: (delegationId) =>\n set((state) => ({\n delegations: state.delegations.filter((d) => d.id !== delegationId),\n grantedDelegations: state.grantedDelegations.filter(\n (d) => d.id !== delegationId,\n ),\n })),\n\n // Actions - UI\n setLoading: (loading) => set({ isLoading: loading }),\n\n setError: (error, errorCode) =>\n set({ error, errorCode: errorCode ?? null }),\n }),\n {\n name: \"auth-storage\",\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n accessToken: state.accessToken,\n refreshToken: state.refreshToken,\n user: state.user,\n isAuthenticated: state.isAuthenticated,\n // Context state\n currentOrganizationId: state.currentOrganizationId,\n currentWorkspaceId: state.currentWorkspaceId,\n currentProjectId: state.currentProjectId,\n workspaceToken: state.workspaceToken,\n // MFA state must be persisted for login verification flow\n mfaRequired: state.mfaRequired,\n mfaChallengeId: state.mfaChallengeId,\n }),\n },\n ),\n);\n\ntype AuthClearWindow = Window & { __bfAuthClearListenerInstalled?: boolean };\n\nif (typeof window !== \"undefined\") {\n const authWindow = window as AuthClearWindow;\n if (!authWindow.__bfAuthClearListenerInstalled) {\n authWindow.__bfAuthClearListenerInstalled = true;\n window.addEventListener(AUTH_STATE_CLEARED_EVENT, () => {\n const state = useAuthStore.getState();\n if (\n state.isAuthenticated ||\n state.accessToken ||\n state.refreshToken ||\n state.user\n ) {\n state.clearAuth();\n return;\n }\n clearPersistedAuthStorage(sessionStorage.getItem(BF_ACTIVE));\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"authStore.js","names":["BF_PREFIX","BF_PROFILES","BF_ACTIVE","AUTH_STORAGE_KEY","AUTH_STATE_CLEARED_EVENT","getActiveProfileForSync","pfKey","profileId","suffix","clearPersistedAuthStorage","key","notifyProfileStorageChange","syncToProfileStorage","data","targetId","profilesRaw","filtered","p","existing","existingRaw","expiresAt","tokenToCheck","raw","padded","payload","merged","hostname","domain","cookieOpts","ssoValue","useAuthStore","create","persist","set","token","b64","accessToken","user","error","state","orgId","loading","workspaceId","projectId","denial","refreshToken","workspaceToken","userRaw","tokensRaw","tokens","required","challengeId","devices","d","deviceId","sessions","s","sessionId","accounts","a","accountId","events","keys","k","keyId","app","appId","delegations","delegation","delegationId","errorCode","createProfileScopedStorage","authWindow"],"sources":["../../../src/auth/store/authStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { ContextLinkDenial } from \"../hooks/contextDeepLink\";\nimport { createProfileScopedStorage } from \"@burdenoff/fe-libs/shared/utils\";\nimport type {\n User,\n Session,\n MfaDevice,\n OAuthAccount,\n TrustedDevice,\n SecurityAudit,\n ApiKey,\n App,\n AppUserDelegation,\n Workspace,\n Organization,\n Project,\n} from \"../types\";\n\n/**\n * Sync auth state to profile-scoped storage (ProfileStorageManager keys).\n * The active profile ID comes from sessionStorage (\"bf-active-profile\").\n * Data is stored under localStorage keys: bf-p-{profileId}-user, bf-p-{profileId}-tokens, etc.\n */\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\nconst AUTH_STORAGE_KEY = \"auth-storage\";\nconst AUTH_STATE_CLEARED_EVENT = \"bf-auth-state-cleared\";\n\nfunction getActiveProfileForSync(): string | null {\n return sessionStorage.getItem(BF_ACTIVE);\n}\n\nfunction pfKey(profileId: string, suffix: string): string {\n return `${BF_PREFIX}-${profileId}-${suffix}`;\n}\n\nfunction clearPersistedAuthStorage(profileId?: string | null): void {\n localStorage.removeItem(AUTH_STORAGE_KEY);\n if (profileId) {\n localStorage.removeItem(pfKey(profileId, AUTH_STORAGE_KEY));\n }\n for (const key of Object.keys(localStorage)) {\n if (\n key.startsWith(`${BF_PREFIX}-`) &&\n key.endsWith(`-${AUTH_STORAGE_KEY}`)\n ) {\n localStorage.removeItem(key);\n }\n }\n}\n\ninterface ProfileMeta {\n id: string;\n email: string;\n name: string;\n avatar?: string;\n lastLogin: string;\n}\n\n/** Dispatch custom event to notify fe-libs AuthProvider of storage changes (same-tab). */\nfunction notifyProfileStorageChange(): void {\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(new Event(\"bf-profile-storage-change\"));\n }\n}\n\nfunction syncToProfileStorage(data: {\n user?: User | null;\n accessToken?: string | null;\n refreshToken?: string | null;\n workspaceToken?: string | null;\n clear?: boolean;\n}) {\n if (typeof window === \"undefined\") return;\n\n const profileId = getActiveProfileForSync();\n if (!profileId && !data.user && !data.clear) return;\n\n // For login/signup: prefer the user's own ID as the target profile.\n // This prevents overwriting an existing active profile when a NEW user\n // signs up or logs in (multi-account scenario).\n const targetId = data.user?.id ?? profileId;\n if (!targetId && !data.clear) return;\n\n if (data.clear) {\n clearPersistedAuthStorage(targetId);\n if (!targetId) {\n localStorage.removeItem(BF_PROFILES);\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n // Remove profile data keys\n localStorage.removeItem(pfKey(targetId, \"user\"));\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n // Remove from profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const filtered = profiles.filter((p) => p.id !== targetId);\n localStorage.setItem(BF_PROFILES, JSON.stringify(filtered));\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n return;\n }\n\n if (!targetId) return;\n\n // An account whose email is not verified cannot sign in (the backend rejects\n // it with EMAIL_NOT_VERIFIED), so it must never become a switchable profile.\n // signUp() keeps the pending user in this store for display only (BOFF-7215).\n if (data.user && data.user.emailVerified === false) return;\n\n if (data.user) {\n localStorage.setItem(pfKey(targetId, \"user\"), JSON.stringify(data.user));\n // Update profiles list\n try {\n const profilesRaw = localStorage.getItem(BF_PROFILES);\n const profiles: ProfileMeta[] = profilesRaw\n ? JSON.parse(profilesRaw)\n : [];\n const existing = profiles.filter((p) => p.id !== targetId);\n existing.push({\n id: targetId,\n email: data.user.email ?? \"\",\n name:\n data.user.name ??\n `${data.user.firstName ?? \"\"} ${data.user.lastName ?? \"\"}`.trim(),\n avatar: data.user.avatar,\n lastLogin: new Date().toISOString(),\n });\n localStorage.setItem(BF_PROFILES, JSON.stringify(existing));\n } catch {\n /* ignore */\n }\n // Set as active profile in this tab\n sessionStorage.setItem(BF_ACTIVE, targetId);\n }\n\n if (data.accessToken !== undefined || data.refreshToken !== undefined) {\n const existingRaw = localStorage.getItem(pfKey(targetId, \"tokens\"));\n const existing = existingRaw ? JSON.parse(existingRaw) : {};\n // Compute expiresAt from the actual JWT exp claim if a new access token is provided\n let expiresAt = existing.expiresAt ?? Date.now() + 60 * 60 * 1000;\n const tokenToCheck = data.accessToken ?? existing.accessToken;\n if (tokenToCheck) {\n try {\n const raw = tokenToCheck\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n if (typeof payload.exp === \"number\") {\n expiresAt = payload.exp * 1000; // JWT exp is in seconds → ms\n }\n } catch {\n // Fallback: keep existing or default 1h\n }\n }\n const merged = {\n ...existing,\n ...(data.accessToken !== undefined\n ? { accessToken: data.accessToken }\n : {}),\n ...(data.refreshToken !== undefined\n ? { refreshToken: data.refreshToken }\n : {}),\n expiresAt,\n };\n if (merged.accessToken) {\n localStorage.setItem(pfKey(targetId, \"tokens\"), JSON.stringify(merged));\n } else {\n localStorage.removeItem(pfKey(targetId, \"tokens\"));\n }\n }\n\n if (data.workspaceToken !== undefined) {\n if (data.workspaceToken) {\n localStorage.setItem(\n pfKey(targetId, \"workspace-token\"),\n data.workspaceToken,\n );\n } else {\n localStorage.removeItem(pfKey(targetId, \"workspace-token\"));\n }\n }\n\n // Notify fe-libs AuthProvider of the change (same-tab custom event)\n notifyProfileStorageChange();\n\n // Set SSO cookie for cross-domain session sharing (works in all envs)\n // Stores profile ID + the origin where the user logged in.\n // Other shells use the origin to redirect to /auth/sso-relay for token relay.\n try {\n const hostname = window.location.hostname;\n // For local dev, set cookie on shared .local.burdenoff.com domain\n // For production, set cookie on current domain only (relay handles cross-domain)\n const isSharedDomain = hostname.endsWith(\".local.burdenoff.com\");\n const domain = isSharedDomain ? \".local.burdenoff.com\" : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieOpts = `${domainPart}; path=/; samesite=lax${securePart}`;\n\n if (data.accessToken && targetId) {\n // Store profile ID + login origin for SSO relay\n const ssoValue = JSON.stringify({\n id: targetId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${cookieOpts}; max-age=86400`;\n }\n\n if (data.clear) {\n document.cookie = \"bf-sso=; path=/; max-age=0\";\n document.cookie = \"bf-sso-profile=; path=/; max-age=0\";\n if (domain) {\n document.cookie = `bf-sso=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `bf-sso=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `bf-sso-profile=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* ignore */\n }\n}\n\ninterface AuthState {\n // Auth state\n user: User | null;\n accessToken: string | null;\n refreshToken: string | null;\n isAuthenticated: boolean;\n\n // Organization state\n organizations: Organization[];\n currentOrganizationId: string | null;\n isLoadingOrganizations: boolean;\n\n // Workspace state\n workspaces: Workspace[];\n currentWorkspaceId: string | null;\n workspaceToken: string | null;\n isLoadingWorkspaces: boolean;\n\n // Project state\n projects: Project[];\n currentProjectId: string | null;\n isLoadingProjects: boolean;\n\n /**\n * A deep link (`?workspace=` / `?org=`) named a workspace or organization\n * the caller cannot open. The context fell back to one the caller can open;\n * the shell shows this instead of the page until it is dismissed. Never\n * persisted: it describes this page load only.\n */\n contextLinkDenial: ContextLinkDenial | null;\n\n // MFA state\n mfaRequired: boolean;\n mfaChallengeId: string | null;\n mfaDevices: MfaDevice[];\n\n // Session state\n activeSessions: Session[];\n currentSessionId: string | null;\n\n // OAuth state\n linkedAccounts: OAuthAccount[];\n\n // Security state\n trustedDevices: TrustedDevice[];\n securityEvents: SecurityAudit[];\n\n // API Keys state\n apiKeys: ApiKey[];\n\n // Apps state\n apps: App[];\n currentApp: App | null;\n\n // Delegations state\n delegations: AppUserDelegation[];\n grantedDelegations: AppUserDelegation[];\n\n // UI state\n isLoading: boolean;\n error: string | null;\n errorCode: string | null;\n\n // Actions - Auth\n setUser: (user: User | null) => void;\n setAccessToken: (token: string | null) => void;\n setAuthTokens: (accessToken: string, refreshToken: string) => void;\n setAuthenticated: (isAuthenticated: boolean) => void;\n clearAuth: () => void;\n\n // Actions - Organization\n setOrganizations: (organizations: Organization[]) => void;\n setCurrentOrganizationId: (orgId: string | null) => void;\n setLoadingOrganizations: (loading: boolean) => void;\n\n // Actions - Workspace\n setWorkspaces: (workspaces: Workspace[]) => void;\n setCurrentWorkspaceId: (workspaceId: string | null) => void;\n setWorkspaceToken: (token: string | null) => void;\n setLoadingWorkspaces: (loading: boolean) => void;\n clearWorkspace: () => void;\n\n // Actions - Project\n setProjects: (projects: Project[]) => void;\n setCurrentProjectId: (projectId: string | null) => void;\n setLoadingProjects: (loading: boolean) => void;\n clearProject: () => void;\n\n // Actions - Deep links\n setContextLinkDenial: (denial: ContextLinkDenial | null) => void;\n\n // Actions - Context (clears org, workspace, project)\n clearContext: () => void;\n\n // Actions - Profile Switch (load from profile storage without syncing back)\n loadFromProfile: (profileId: string) => void;\n\n // Actions - MFA\n setMfaRequired: (required: boolean, challengeId?: string) => void;\n setMfaDevices: (devices: MfaDevice[]) => void;\n removeMfaDevice: (deviceId: string) => void;\n\n // Actions - Sessions\n setActiveSessions: (sessions: Session[]) => void;\n removeSession: (sessionId: string) => void;\n\n // Actions - OAuth\n setLinkedAccounts: (accounts: OAuthAccount[]) => void;\n removeLinkedAccount: (accountId: string) => void;\n\n // Actions - Security\n setTrustedDevices: (devices: TrustedDevice[]) => void;\n removeTrustedDevice: (deviceId: string) => void;\n setSecurityEvents: (events: SecurityAudit[]) => void;\n\n // Actions - API Keys\n setApiKeys: (keys: ApiKey[]) => void;\n removeApiKey: (keyId: string) => void;\n\n // Actions - Apps\n setApps: (apps: App[]) => void;\n setCurrentApp: (app: App | null) => void;\n addApp: (app: App) => void;\n updateApp: (app: App) => void;\n removeApp: (appId: string) => void;\n\n // Actions - Delegations\n setDelegations: (delegations: AppUserDelegation[]) => void;\n setGrantedDelegations: (delegations: AppUserDelegation[]) => void;\n addDelegation: (delegation: AppUserDelegation) => void;\n removeDelegation: (delegationId: string) => void;\n\n // Actions - UI\n setLoading: (loading: boolean) => void;\n setError: (error: string | null, errorCode?: string | null) => void;\n}\n\n/**\n * Auth Zustand store — browser-tab-local state.\n *\n * SAFE for in-memory (no Redis needed):\n * - accessToken / refreshToken / user: per-tab, backed by profileScopedStorage\n * (localStorage key bf-p-{profileId}-*). Tabs sharing the same profile ID\n * stay in sync via the \"bf-profile-storage-change\" custom event + \"storage\"\n * cross-tab event.\n * - workspaceToken: same pattern as above.\n * - mfaRequired / mfaChallengeId: transient login-flow state; not needed\n * cross-tab (each login has its own challenge).\n * - organizations / workspaces / projects lists: cached context, re-fetched\n * after account switch; no cross-tab sharing needed.\n * - UI state (isLoading, error): purely ephemeral.\n *\n * NOT safe for in-memory (needs Redis in future if required server-side):\n * - Nothing currently — all server-side session tracking is done by the\n * backend (global-auth-svc) using its own DB. The frontend only holds\n * short-lived JWT access tokens that expire independently.\n */\nexport const useAuthStore = create<AuthState>()(\n persist(\n (set) => ({\n // Initial state\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n contextLinkDenial: null,\n mfaRequired: false,\n mfaChallengeId: null,\n mfaDevices: [],\n activeSessions: [],\n currentSessionId: null,\n linkedAccounts: [],\n trustedDevices: [],\n securityEvents: [],\n apiKeys: [],\n apps: [],\n currentApp: null,\n delegations: [],\n grantedDelegations: [],\n isLoading: false,\n error: null,\n errorCode: null,\n\n // Actions - Auth (with profile storage sync)\n setUser: (user) => {\n set({ user });\n syncToProfileStorage({ user });\n },\n\n setAccessToken: (token) => {\n set({ accessToken: token });\n syncToProfileStorage({ accessToken: token });\n },\n\n setAuthTokens: (accessToken, refreshToken) => {\n // Only set tokens here. Do NOT overwrite user — setUser() handles that\n // with the full user object from the GraphQL response (firstName, lastName, etc.).\n // Fall back to JWT-decoded user ONLY if no user is currently in the store.\n const currentState = useAuthStore.getState();\n if (currentState.user) {\n // User already set (from setUser call) — just update tokens\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n } else {\n // No user yet — decode JWT as fallback\n try {\n const b64 = accessToken\n .split(\".\")[1]\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\");\n const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded));\n const user: User = {\n id: payload.sub,\n email: payload.email,\n username: payload.username,\n role: payload.role,\n status: \"ACTIVE\",\n emailVerified: true,\n mfaEnabled: false,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n set({ accessToken, refreshToken, user });\n syncToProfileStorage({ accessToken, refreshToken, user });\n } catch (error) {\n console.error(\"[AuthStore] Failed to decode JWT:\", error);\n set({ accessToken, refreshToken });\n syncToProfileStorage({ accessToken, refreshToken });\n }\n }\n },\n\n setAuthenticated: (isAuthenticated) => set({ isAuthenticated }),\n\n clearAuth: () => {\n set({\n user: null,\n accessToken: null,\n refreshToken: null,\n isAuthenticated: false,\n mfaRequired: false,\n mfaChallengeId: null,\n activeSessions: [],\n currentSessionId: null,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n workspaceToken: null,\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n contextLinkDenial: null,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n });\n syncToProfileStorage({ clear: true });\n },\n\n // Actions - Organization\n setOrganizations: (organizations) => set({ organizations }),\n\n setCurrentOrganizationId: (orgId) =>\n set((state) =>\n state.currentOrganizationId === orgId\n ? state\n : { currentOrganizationId: orgId },\n ),\n\n setLoadingOrganizations: (loading) =>\n set({ isLoadingOrganizations: loading }),\n\n // Actions - Workspace\n setWorkspaces: (workspaces) => set({ workspaces }),\n\n setCurrentWorkspaceId: (workspaceId) =>\n set((state) =>\n state.currentWorkspaceId === workspaceId\n ? state\n : { currentWorkspaceId: workspaceId },\n ),\n\n setWorkspaceToken: (token) => {\n set({ workspaceToken: token });\n syncToProfileStorage({ workspaceToken: token });\n },\n\n setLoadingWorkspaces: (loading) => set({ isLoadingWorkspaces: loading }),\n\n clearWorkspace: () =>\n set({ currentWorkspaceId: null, workspaceToken: null, workspaces: [] }),\n\n // Actions - Project\n setProjects: (projects) => set({ projects }),\n\n setCurrentProjectId: (projectId) =>\n set((state) =>\n state.currentProjectId === projectId\n ? state\n : { currentProjectId: projectId },\n ),\n\n setLoadingProjects: (loading) => set({ isLoadingProjects: loading }),\n\n clearProject: () => set({ currentProjectId: null, projects: [] }),\n\n // Actions - Context (clears org, workspace, project)\n setContextLinkDenial: (denial) => set({ contextLinkDenial: denial }),\n\n clearContext: () =>\n set({\n contextLinkDenial: null,\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n workspaceToken: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n }),\n\n // Actions - Profile Switch\n // Loads user/tokens from profile-scoped localStorage WITHOUT syncing back.\n // Used during account switching to align zustand with the new active profile.\n loadFromProfile: (profileId: string) => {\n let user: User | null = null;\n let accessToken: string | null = null;\n let refreshToken: string | null = null;\n let workspaceToken: string | null = null;\n\n try {\n const userRaw = localStorage.getItem(pfKey(profileId, \"user\"));\n if (userRaw) user = JSON.parse(userRaw);\n } catch {\n /* ignore */\n }\n\n try {\n const tokensRaw = localStorage.getItem(pfKey(profileId, \"tokens\"));\n if (tokensRaw) {\n const tokens = JSON.parse(tokensRaw);\n accessToken = tokens.accessToken ?? null;\n refreshToken = tokens.refreshToken ?? null;\n }\n } catch {\n /* ignore */\n }\n\n workspaceToken = localStorage.getItem(\n pfKey(profileId, \"workspace-token\"),\n );\n\n // Set everything in one batch — clears context data so it will be re-fetched\n set({\n user,\n accessToken,\n refreshToken,\n isAuthenticated: !!accessToken && !!user,\n workspaceToken,\n // Clear all context — will be re-fetched for the new user\n organizations: [],\n currentOrganizationId: null,\n isLoadingOrganizations: false,\n workspaces: [],\n currentWorkspaceId: null,\n isLoadingWorkspaces: false,\n projects: [],\n currentProjectId: null,\n isLoadingProjects: false,\n // Clear MFA state\n mfaRequired: false,\n mfaChallengeId: null,\n });\n\n // Notify listeners (e.g., AuthTokenProvider) without writing back to storage\n notifyProfileStorageChange();\n },\n\n // Actions - MFA\n setMfaRequired: (required, challengeId) =>\n set({ mfaRequired: required, mfaChallengeId: challengeId || null }),\n\n setMfaDevices: (devices) => set({ mfaDevices: devices }),\n\n removeMfaDevice: (deviceId) =>\n set((state) => ({\n mfaDevices: state.mfaDevices.filter((d) => d.id !== deviceId),\n })),\n\n // Actions - Sessions\n setActiveSessions: (sessions) => set({ activeSessions: sessions }),\n\n removeSession: (sessionId) =>\n set((state) => ({\n activeSessions: state.activeSessions.filter(\n (s) => s.id !== sessionId,\n ),\n })),\n\n // Actions - OAuth\n setLinkedAccounts: (accounts) => set({ linkedAccounts: accounts }),\n\n removeLinkedAccount: (accountId) =>\n set((state) => ({\n linkedAccounts: state.linkedAccounts.filter(\n (a) => a.id !== accountId,\n ),\n })),\n\n // Actions - Security\n setTrustedDevices: (devices) => set({ trustedDevices: devices }),\n\n removeTrustedDevice: (deviceId) =>\n set((state) => ({\n trustedDevices: state.trustedDevices.filter((d) => d.id !== deviceId),\n })),\n\n setSecurityEvents: (events) => set({ securityEvents: events }),\n\n // Actions - API Keys\n setApiKeys: (keys) => set({ apiKeys: keys }),\n\n removeApiKey: (keyId) =>\n set((state) => ({\n apiKeys: state.apiKeys.filter((k) => k.id !== keyId),\n })),\n\n // Actions - Apps\n setApps: (apps) => set({ apps }),\n\n setCurrentApp: (app) => set({ currentApp: app }),\n\n addApp: (app) =>\n set((state) => ({\n apps: [...state.apps, app],\n })),\n\n updateApp: (app) =>\n set((state) => ({\n apps: state.apps.map((a) => (a.id === app.id ? app : a)),\n currentApp: state.currentApp?.id === app.id ? app : state.currentApp,\n })),\n\n removeApp: (appId) =>\n set((state) => ({\n apps: state.apps.filter((a) => a.id !== appId),\n currentApp: state.currentApp?.id === appId ? null : state.currentApp,\n })),\n\n // Actions - Delegations\n setDelegations: (delegations) => set({ delegations }),\n\n setGrantedDelegations: (delegations) =>\n set({ grantedDelegations: delegations }),\n\n addDelegation: (delegation) =>\n set((state) => ({\n delegations: [...state.delegations, delegation],\n })),\n\n removeDelegation: (delegationId) =>\n set((state) => ({\n delegations: state.delegations.filter((d) => d.id !== delegationId),\n grantedDelegations: state.grantedDelegations.filter(\n (d) => d.id !== delegationId,\n ),\n })),\n\n // Actions - UI\n setLoading: (loading) => set({ isLoading: loading }),\n\n setError: (error, errorCode) =>\n set({ error, errorCode: errorCode ?? null }),\n }),\n {\n name: \"auth-storage\",\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n accessToken: state.accessToken,\n refreshToken: state.refreshToken,\n user: state.user,\n isAuthenticated: state.isAuthenticated,\n // Context state\n currentOrganizationId: state.currentOrganizationId,\n currentWorkspaceId: state.currentWorkspaceId,\n currentProjectId: state.currentProjectId,\n workspaceToken: state.workspaceToken,\n // MFA state must be persisted for login verification flow\n mfaRequired: state.mfaRequired,\n mfaChallengeId: state.mfaChallengeId,\n }),\n },\n ),\n);\n\ntype AuthClearWindow = Window & { __bfAuthClearListenerInstalled?: boolean };\n\nif (typeof window !== \"undefined\") {\n const authWindow = window as AuthClearWindow;\n if (!authWindow.__bfAuthClearListenerInstalled) {\n authWindow.__bfAuthClearListenerInstalled = true;\n window.addEventListener(AUTH_STATE_CLEARED_EVENT, () => {\n const state = useAuthStore.getState();\n if (\n state.isAuthenticated ||\n state.accessToken ||\n state.refreshToken ||\n state.user\n ) {\n state.clearAuth();\n return;\n }\n clearPersistedAuthStorage(sessionStorage.getItem(BF_ACTIVE));\n });\n }\n}\n"],"mappings":"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"}
@@ -1,13 +1,10 @@
1
- //#region src/auth/utils/captchaConfig.ts
2
- var e = !1;
3
- function t() {
4
- return e;
1
+ function e() {
2
+ return !1;
5
3
  }
6
- function n() {}
7
- function r() {
4
+ function t() {}
5
+ function n() {
8
6
  throw Error("reCAPTCHA site key is not configured");
9
7
  }
10
- //#endregion
11
- export { n as getRecaptchaSiteKey, t as isCaptchaEnabled, r as requireRecaptchaSiteKey };
8
+ export { t as getRecaptchaSiteKey, e as isCaptchaEnabled, n as requireRecaptchaSiteKey };
12
9
 
13
10
  //# sourceMappingURL=captchaConfig.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"captchaConfig.js","names":[],"sources":["../../../src/auth/utils/captchaConfig.ts"],"sourcesContent":["const CAPTCHA_ENABLED = import.meta.env.VITE_CAPTCHA_ENABLED === \"true\";\n\nexport function isCaptchaEnabled(): boolean {\n return CAPTCHA_ENABLED;\n}\n\nexport function getRecaptchaSiteKey(): string | undefined {\n const siteKey = import.meta.env.VITE_RECAPTCHA_SITE_KEY;\n return typeof siteKey === \"string\" && siteKey.trim().length > 0\n ? siteKey.trim()\n : undefined;\n}\n\nexport function requireRecaptchaSiteKey(): string {\n const siteKey = getRecaptchaSiteKey();\n if (!siteKey) {\n throw new Error(\"reCAPTCHA site key is not configured\");\n }\n return siteKey;\n}\n"],"mappings":";AAAA,IAAM,IAAkB;AAExB,SAAgB,IAA4B;CAC1C,OAAO;AACT;AAEA,SAAgB,IAA0C,CAK1D;AAEA,SAAgB,IAAkC;CAG9C,MAAU,MAAM,sCAAsC;AAG1D"}
1
+ {"version":3,"file":"captchaConfig.js","names":["isCaptchaEnabled","getRecaptchaSiteKey","requireRecaptchaSiteKey"],"sources":["../../../src/auth/utils/captchaConfig.ts"],"sourcesContent":["const CAPTCHA_ENABLED = import.meta.env.VITE_CAPTCHA_ENABLED === \"true\";\n\nexport function isCaptchaEnabled(): boolean {\n return CAPTCHA_ENABLED;\n}\n\nexport function getRecaptchaSiteKey(): string | undefined {\n const siteKey = import.meta.env.VITE_RECAPTCHA_SITE_KEY;\n return typeof siteKey === \"string\" && siteKey.trim().length > 0\n ? siteKey.trim()\n : undefined;\n}\n\nexport function requireRecaptchaSiteKey(): string {\n const siteKey = getRecaptchaSiteKey();\n if (!siteKey) {\n throw new Error(\"reCAPTCHA site key is not configured\");\n }\n return siteKey;\n}\n"],"mappings":"AAEA,SAAgBA,IAA4B;CAC1C,OAAO;AACT;AAEA,SAAgBC,IAA0C,CAK1D;AAEA,SAAgBC,IAAkC;CAG9C,MAAU,MAAM,sCAAsC;AAG1D"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/utils/formatters.ts
1
+ // src/auth/utils/formatters.ts
2
2
  function e(e) {
3
3
  let t = new Date(e), n = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), r = Math.floor(n / 1e3), i = Math.floor(r / 60), a = Math.floor(i / 60), o = Math.floor(a / 24);
4
4
  if (r < 60) return "just now";
@@ -42,7 +42,6 @@ function i(e) {
42
42
  let t = e.toLowerCase();
43
43
  return t.includes("edg") ? "Edge" : t.includes("chrome") ? "Chrome" : t.includes("firefox") ? "Firefox" : t.includes("safari") && !t.includes("chrome") ? "Safari" : t.includes("opera") ? "Opera" : "Unknown Browser";
44
44
  }
45
- //#endregion
46
45
  export { t as formatDate, n as formatDateTime, e as formatRelativeTime, i as parseBrowserName, r as parseDeviceName };
47
46
 
48
47
  //# sourceMappingURL=formatters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.js","names":[],"sources":["../../../src/auth/utils/formatters.ts"],"sourcesContent":["/**\n * Format date relative to now (e.g., \"2 minutes ago\", \"3 days ago\")\n */\nexport function formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSecs = Math.floor(diffMs / 1000);\n const diffMins = Math.floor(diffSecs / 60);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffSecs < 60) return \"just now\";\n if (diffMins < 60)\n return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n if (diffHours < 24)\n return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? \"s\" : \"\"} ago`;\n if (diffDays < 30) {\n const weeks = Math.floor(diffDays / 7);\n return `${weeks} week${weeks !== 1 ? \"s\" : \"\"} ago`;\n }\n if (diffDays < 365) {\n const months = Math.floor(diffDays / 30);\n return `${months} month${months !== 1 ? \"s\" : \"\"} ago`;\n }\n const years = Math.floor(diffDays / 365);\n return `${years} year${years !== 1 ? \"s\" : \"\"} ago`;\n}\n\n/**\n * Format date to human-readable string\n */\nexport function formatDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\n/**\n * Format date and time\n */\nexport function formatDateTime(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/**\n * Mask API key (show only prefix and last 4 characters)\n */\nexport function maskApiKey(key: string, prefix: string): string {\n if (key.length <= 12) return key;\n const last4 = key.slice(-4);\n return `${prefix}_${\"•\".repeat(20)}${last4}`;\n}\n\n/**\n * Format IP address with location\n */\nexport function formatLocationString(\n ipAddress: string,\n location: string | null,\n): string {\n if (!location) return ipAddress;\n return `${ipAddress} · ${location}`;\n}\n\n/**\n * Get device icon based on user agent\n */\nexport function getDeviceIcon(userAgent: string): string {\n const ua = userAgent.toLowerCase();\n\n if (\n ua.includes(\"mobile\") ||\n ua.includes(\"android\") ||\n ua.includes(\"iphone\")\n ) {\n return \"📱\";\n }\n\n if (ua.includes(\"ipad\") || ua.includes(\"tablet\")) {\n return \"📱\";\n }\n\n if (ua.includes(\"mac\")) {\n return \"💻\";\n }\n\n if (ua.includes(\"windows\")) {\n return \"💻\";\n }\n\n if (ua.includes(\"linux\")) {\n return \"💻\";\n }\n\n return \"🖥️\";\n}\n\n/**\n * Parse device name from user agent\n */\nexport function parseDeviceName(userAgent: string | null | undefined): string {\n if (!userAgent) return \"Unknown Device\";\n const ua = userAgent.toLowerCase();\n\n // Mobile devices\n if (ua.includes(\"iphone\")) return \"iPhone\";\n if (ua.includes(\"ipad\")) return \"iPad\";\n if (ua.includes(\"android\")) return \"Android Device\";\n\n // Desktop OS\n if (ua.includes(\"mac\")) return \"Mac\";\n if (ua.includes(\"windows\")) return \"Windows PC\";\n if (ua.includes(\"linux\")) return \"Linux\";\n\n return \"Unknown Device\";\n}\n\n/**\n * Parse browser name from user agent\n */\nexport function parseBrowserName(userAgent: string | null | undefined): string {\n if (!userAgent) return \"Unknown Browser\";\n const ua = userAgent.toLowerCase();\n\n if (ua.includes(\"edg\")) return \"Edge\";\n if (ua.includes(\"chrome\")) return \"Chrome\";\n if (ua.includes(\"firefox\")) return \"Firefox\";\n if (ua.includes(\"safari\") && !ua.includes(\"chrome\")) return \"Safari\";\n if (ua.includes(\"opera\")) return \"Opera\";\n\n return \"Unknown Browser\";\n}\n\n/**\n * Format OAuth provider name\n */\nexport function formatOAuthProvider(provider: string): string {\n const providers: Record<string, string> = {\n google: \"Google\",\n github: \"GitHub\",\n microsoft: \"Microsoft\",\n apple: \"Apple\",\n linkedin: \"LinkedIn\",\n facebook: \"Facebook\",\n };\n\n return providers[provider.toLowerCase()] || provider;\n}\n"],"mappings":";AAGA,SAAgB,EAAmB,GAA4B;CAC7D,IAAM,IAAO,IAAI,KAAK,CAAU,GAE1B,qBAAS,IADC,KACD,GAAI,QAAQ,IAAI,EAAK,QAAQ,GACtC,IAAW,KAAK,MAAM,IAAS,GAAI,GACnC,IAAW,KAAK,MAAM,IAAW,EAAE,GACnC,IAAY,KAAK,MAAM,IAAW,EAAE,GACpC,IAAW,KAAK,MAAM,IAAY,EAAE;CAE1C,IAAI,IAAW,IAAI,OAAO;CAC1B,IAAI,IAAW,IACb,OAAO,GAAG,EAAS,SAAS,MAAa,IAAU,KAAN,IAAS;CACxD,IAAI,IAAY,IACd,OAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;CACxD,IAAI,IAAW,GAAG,OAAO,GAAG,EAAS,MAAM,MAAa,IAAU,KAAN,IAAS;CACrE,IAAI,IAAW,IAAI;EACjB,IAAM,IAAQ,KAAK,MAAM,IAAW,CAAC;EACrC,OAAO,GAAG,EAAM,OAAO,MAAU,IAAU,KAAN,IAAS;CAChD;CACA,IAAI,IAAW,KAAK;EAClB,IAAM,IAAS,KAAK,MAAM,IAAW,EAAE;EACvC,OAAO,GAAG,EAAO,QAAQ,MAAW,IAAU,KAAN,IAAS;CACnD;CACA,IAAM,IAAQ,KAAK,MAAM,IAAW,GAAG;CACvC,OAAO,GAAG,EAAM,OAAO,MAAU,IAAU,KAAN,IAAS;AAChD;AAKA,SAAgB,EAAW,GAA4B;CAErD,OAAO,IADU,KAAK,CACf,EAAK,mBAAmB,SAAS;EACtC,MAAM;EACN,OAAO;EACP,KAAK;CACP,CAAC;AACH;AAKA,SAAgB,EAAe,GAA4B;CAEzD,OAAO,IADU,KAAK,CACf,EAAK,eAAe,SAAS;EAClC,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV,CAAC;AACH;AA0DA,SAAgB,EAAgB,GAA8C;CAC5E,IAAI,CAAC,GAAW,OAAO;CACvB,IAAM,IAAK,EAAU,YAAY;CAYjC,OATI,EAAG,SAAS,QAAQ,IAAU,WAC9B,EAAG,SAAS,MAAM,IAAU,SAC5B,EAAG,SAAS,SAAS,IAAU,mBAG/B,EAAG,SAAS,KAAK,IAAU,QAC3B,EAAG,SAAS,SAAS,IAAU,eAC/B,EAAG,SAAS,OAAO,IAAU,UAE1B;AACT;AAKA,SAAgB,EAAiB,GAA8C;CAC7E,IAAI,CAAC,GAAW,OAAO;CACvB,IAAM,IAAK,EAAU,YAAY;CAQjC,OANI,EAAG,SAAS,KAAK,IAAU,SAC3B,EAAG,SAAS,QAAQ,IAAU,WAC9B,EAAG,SAAS,SAAS,IAAU,YAC/B,EAAG,SAAS,QAAQ,KAAK,CAAC,EAAG,SAAS,QAAQ,IAAU,WACxD,EAAG,SAAS,OAAO,IAAU,UAE1B;AACT"}
1
+ {"version":3,"file":"formatters.js","names":["formatRelativeTime","date","dateString","diffMs","diffSecs","diffMins","diffHours","diffDays","weeks","months","years","formatDate","formatDateTime","parseDeviceName","userAgent","ua","parseBrowserName"],"sources":["../../../src/auth/utils/formatters.ts"],"sourcesContent":["/**\n * Format date relative to now (e.g., \"2 minutes ago\", \"3 days ago\")\n */\nexport function formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSecs = Math.floor(diffMs / 1000);\n const diffMins = Math.floor(diffSecs / 60);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffSecs < 60) return \"just now\";\n if (diffMins < 60)\n return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n if (diffHours < 24)\n return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? \"s\" : \"\"} ago`;\n if (diffDays < 30) {\n const weeks = Math.floor(diffDays / 7);\n return `${weeks} week${weeks !== 1 ? \"s\" : \"\"} ago`;\n }\n if (diffDays < 365) {\n const months = Math.floor(diffDays / 30);\n return `${months} month${months !== 1 ? \"s\" : \"\"} ago`;\n }\n const years = Math.floor(diffDays / 365);\n return `${years} year${years !== 1 ? \"s\" : \"\"} ago`;\n}\n\n/**\n * Format date to human-readable string\n */\nexport function formatDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\n/**\n * Format date and time\n */\nexport function formatDateTime(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/**\n * Mask API key (show only prefix and last 4 characters)\n */\nexport function maskApiKey(key: string, prefix: string): string {\n if (key.length <= 12) return key;\n const last4 = key.slice(-4);\n return `${prefix}_${\"•\".repeat(20)}${last4}`;\n}\n\n/**\n * Format IP address with location\n */\nexport function formatLocationString(\n ipAddress: string,\n location: string | null,\n): string {\n if (!location) return ipAddress;\n return `${ipAddress} · ${location}`;\n}\n\n/**\n * Get device icon based on user agent\n */\nexport function getDeviceIcon(userAgent: string): string {\n const ua = userAgent.toLowerCase();\n\n if (\n ua.includes(\"mobile\") ||\n ua.includes(\"android\") ||\n ua.includes(\"iphone\")\n ) {\n return \"📱\";\n }\n\n if (ua.includes(\"ipad\") || ua.includes(\"tablet\")) {\n return \"📱\";\n }\n\n if (ua.includes(\"mac\")) {\n return \"💻\";\n }\n\n if (ua.includes(\"windows\")) {\n return \"💻\";\n }\n\n if (ua.includes(\"linux\")) {\n return \"💻\";\n }\n\n return \"🖥️\";\n}\n\n/**\n * Parse device name from user agent\n */\nexport function parseDeviceName(userAgent: string | null | undefined): string {\n if (!userAgent) return \"Unknown Device\";\n const ua = userAgent.toLowerCase();\n\n // Mobile devices\n if (ua.includes(\"iphone\")) return \"iPhone\";\n if (ua.includes(\"ipad\")) return \"iPad\";\n if (ua.includes(\"android\")) return \"Android Device\";\n\n // Desktop OS\n if (ua.includes(\"mac\")) return \"Mac\";\n if (ua.includes(\"windows\")) return \"Windows PC\";\n if (ua.includes(\"linux\")) return \"Linux\";\n\n return \"Unknown Device\";\n}\n\n/**\n * Parse browser name from user agent\n */\nexport function parseBrowserName(userAgent: string | null | undefined): string {\n if (!userAgent) return \"Unknown Browser\";\n const ua = userAgent.toLowerCase();\n\n if (ua.includes(\"edg\")) return \"Edge\";\n if (ua.includes(\"chrome\")) return \"Chrome\";\n if (ua.includes(\"firefox\")) return \"Firefox\";\n if (ua.includes(\"safari\") && !ua.includes(\"chrome\")) return \"Safari\";\n if (ua.includes(\"opera\")) return \"Opera\";\n\n return \"Unknown Browser\";\n}\n\n/**\n * Format OAuth provider name\n */\nexport function formatOAuthProvider(provider: string): string {\n const providers: Record<string, string> = {\n google: \"Google\",\n github: \"GitHub\",\n microsoft: \"Microsoft\",\n apple: \"Apple\",\n linkedin: \"LinkedIn\",\n facebook: \"Facebook\",\n };\n\n return providers[provider.toLowerCase()] || provider;\n}\n"],"mappings":";AAGA,SAAgBA,EAAmB,GAA4B;CAC7D,IAAMC,IAAO,IAAI,KAAKC,CAAU,GAE1BC,qBAAS,IADC,KACD,EAAA,CAAI,QAAQ,IAAIF,EAAK,QAAQ,GACtCG,IAAW,KAAK,MAAMD,IAAS,GAAI,GACnCE,IAAW,KAAK,MAAMD,IAAW,EAAE,GACnCE,IAAY,KAAK,MAAMD,IAAW,EAAE,GACpCE,IAAW,KAAK,MAAMD,IAAY,EAAE;CAE1C,IAAIF,IAAW,IAAI,OAAO;CAC1B,IAAIC,IAAW,IACb,OAAO,GAAGA,EAAS,SAASA,MAAa,IAAU,KAAN,IAAS;CACxD,IAAIC,IAAY,IACd,OAAO,GAAGA,EAAU,OAAOA,MAAc,IAAU,KAAN,IAAS;CACxD,IAAIC,IAAW,GAAG,OAAO,GAAGA,EAAS,MAAMA,MAAa,IAAU,KAAN,IAAS;CACrE,IAAIA,IAAW,IAAI;EACjB,IAAMC,IAAQ,KAAK,MAAMD,IAAW,CAAC;EACrC,OAAO,GAAGC,EAAM,OAAOA,MAAU,IAAU,KAAN,IAAS;CAChD;CACA,IAAID,IAAW,KAAK;EAClB,IAAME,IAAS,KAAK,MAAMF,IAAW,EAAE;EACvC,OAAO,GAAGE,EAAO,QAAQA,MAAW,IAAU,KAAN,IAAS;CACnD;CACA,IAAMC,IAAQ,KAAK,MAAMH,IAAW,GAAG;CACvC,OAAO,GAAGG,EAAM,OAAOA,MAAU,IAAU,KAAN,IAAS;AAChD;AAKA,SAAgBC,EAAW,GAA4B;CAErD,OAAO,IADU,KAAKT,CACf,CAAA,CAAK,mBAAmB,SAAS;EACtC,MAAM;EACN,OAAO;EACP,KAAK;CACP,CAAC;AACH;AAKA,SAAgBU,EAAe,GAA4B;CAEzD,OAAO,IADU,KAAKV,CACf,CAAA,CAAK,eAAe,SAAS;EAClC,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV,CAAC;AACH;AA0DA,SAAgBW,EAAgB,GAA8C;CAC5E,IAAI,CAACC,GAAW,OAAO;CACvB,IAAMC,IAAKD,EAAU,YAAY;CAYjC,OATIC,EAAG,SAAS,QAAQ,IAAU,WAC9BA,EAAG,SAAS,MAAM,IAAU,SAC5BA,EAAG,SAAS,SAAS,IAAU,mBAG/BA,EAAG,SAAS,KAAK,IAAU,QAC3BA,EAAG,SAAS,SAAS,IAAU,eAC/BA,EAAG,SAAS,OAAO,IAAU,UAE1B;AACT;AAKA,SAAgBC,EAAiB,GAA8C;CAC7E,IAAI,CAACF,GAAW,OAAO;CACvB,IAAMC,IAAKD,EAAU,YAAY;CAQjC,OANIC,EAAG,SAAS,KAAK,IAAU,SAC3BA,EAAG,SAAS,QAAQ,IAAU,WAC9BA,EAAG,SAAS,SAAS,IAAU,YAC/BA,EAAG,SAAS,QAAQ,KAAK,CAACA,EAAG,SAAS,QAAQ,IAAU,WACxDA,EAAG,SAAS,OAAO,IAAU,UAE1B;AACT"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/utils/gatewayBaseUrls.ts
1
+ // src/auth/utils/gatewayBaseUrls.ts
2
2
  function e() {
3
3
  let e = typeof window < "u" ? window.location.hostname.toLowerCase() : "";
4
4
  return e === "app.burdenoff.com" ? {
@@ -12,7 +12,6 @@ function e() {
12
12
  workspaceBaseUrl: "https://gw-wspace.local.burdenoff.com"
13
13
  };
14
14
  }
15
- //#endregion
16
15
  export { e as resolveGatewayBaseUrls };
17
16
 
18
17
  //# sourceMappingURL=gatewayBaseUrls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gatewayBaseUrls.js","names":[],"sources":["../../../src/auth/utils/gatewayBaseUrls.ts"],"sourcesContent":["export interface GatewayBaseUrls {\n globalBaseUrl: string;\n workspaceBaseUrl: string;\n}\n\nexport function resolveGatewayBaseUrls(): GatewayBaseUrls {\n const hostname =\n typeof window !== \"undefined\" ? window.location.hostname.toLowerCase() : \"\";\n\n // Production\n if (hostname === \"app.burdenoff.com\") {\n return {\n globalBaseUrl: \"https://graphql.burdenoff.com\",\n workspaceBaseUrl: \"https://graphqlworkspaces.burdenoff.com\",\n };\n }\n\n // Alpha / staging\n if (hostname === \"alphaapp.burdenoff.com\") {\n return {\n globalBaseUrl: \"https://alphagraphql.burdenoff.com\",\n workspaceBaseUrl: \"https://alphagraphqlworkspaces.burdenoff.com\",\n };\n }\n\n // Everything else is local development (localhost, 127.0.0.1, app.local.burdenoff.com, etc.)\n return {\n globalBaseUrl: \"https://gw-global.local.burdenoff.com\",\n workspaceBaseUrl: \"https://gw-wspace.local.burdenoff.com\",\n };\n}\n"],"mappings":";AAKA,SAAgB,IAA0C;CACxD,IAAM,IACJ,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS,YAAY,IAAI;CAmB3E,OAhBI,MAAa,sBACR;EACL,eAAe;EACf,kBAAkB;CACpB,IAIE,MAAa,2BACR;EACL,eAAe;EACf,kBAAkB;CACpB,IAIK;EACL,eAAe;EACf,kBAAkB;CACpB;AACF"}
1
+ {"version":3,"file":"gatewayBaseUrls.js","names":["resolveGatewayBaseUrls","hostname"],"sources":["../../../src/auth/utils/gatewayBaseUrls.ts"],"sourcesContent":["export interface GatewayBaseUrls {\n globalBaseUrl: string;\n workspaceBaseUrl: string;\n}\n\nexport function resolveGatewayBaseUrls(): GatewayBaseUrls {\n const hostname =\n typeof window !== \"undefined\" ? window.location.hostname.toLowerCase() : \"\";\n\n // Production\n if (hostname === \"app.burdenoff.com\") {\n return {\n globalBaseUrl: \"https://graphql.burdenoff.com\",\n workspaceBaseUrl: \"https://graphqlworkspaces.burdenoff.com\",\n };\n }\n\n // Alpha / staging\n if (hostname === \"alphaapp.burdenoff.com\") {\n return {\n globalBaseUrl: \"https://alphagraphql.burdenoff.com\",\n workspaceBaseUrl: \"https://alphagraphqlworkspaces.burdenoff.com\",\n };\n }\n\n // Everything else is local development (localhost, 127.0.0.1, app.local.burdenoff.com, etc.)\n return {\n globalBaseUrl: \"https://gw-global.local.burdenoff.com\",\n workspaceBaseUrl: \"https://gw-wspace.local.burdenoff.com\",\n };\n}\n"],"mappings":";AAKA,SAAgBA,IAA0C;CACxD,IAAMC,IACJ,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS,YAAY,IAAI;CAmB3E,OAhBIA,MAAa,sBACR;EACL,eAAe;EACf,kBAAkB;CACpB,IAIEA,MAAa,2BACR;EACL,eAAe;EACf,kBAAkB;CACpB,IAIK;EACL,eAAe;EACf,kBAAkB;CACpB;AACF"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/utils/legalUrls.ts
1
+ // src/auth/utils/legalUrls.ts
2
2
  var e = [
3
3
  "app.",
4
4
  "alphaapp.",
@@ -20,7 +20,6 @@ function n(e) {
20
20
  let n = t();
21
21
  return n ? `${n}${e}` : e;
22
22
  }
23
- //#endregion
24
- export { n as resolveLegalUrl };
23
+ export { n as resolveLegalUrl, t as resolveMarketingOrigin };
25
24
 
26
25
  //# sourceMappingURL=legalUrls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"legalUrls.js","names":[],"sources":["../../../src/auth/utils/legalUrls.ts"],"sourcesContent":["/**\n * Public legal-document URLs for the signup consent copy.\n *\n * The signup form is shown to SIGNED-OUT visitors, so its \"Terms of Service\" and\n * \"Privacy Policy\" links must resolve to the public marketing site. They used to\n * be written as the app-relative paths `/legal/terms` and `/legal/privacy`, which\n * resolved against the app origin (e.g. `https://app.fluidgrids.ai/legal/terms`).\n * That route lives *inside* the shell's authenticated block, so a visitor who\n * clicked it was bounced to `/auth/login?returnTo=%2Flegal%2Fterms` and the shell\n * footer fired two FORBIDDEN queries on the way out:\n *\n * [Burdenoff backend error] You do not have permission to do that.\n * {code: FORBIDDEN, operationName: GetFooterContentPages}\n * {code: FORBIDDEN, operationName: GetFooterProduct}\n *\n * i.e. the consent links were unreadable for exactly the people being asked to\n * consent. The marketing site is also the authoritative home of the policy text\n * (rendered from the dated drafts in `fluidgrids-website/src/content/legal/`),\n * so linking there avoids maintaining a second copy that can drift.\n *\n * microfe-auth is mounted by every Burdenoff product shell, so the origin is\n * DERIVED from the current host rather than hardcoded — hardcoding one product's\n * domain would send every other product's users to the wrong company's terms.\n * Every product serves its marketing site at `www.<product-domain>` using the\n * same `/terms` and `/privacy` route names (verified against fluidgrids.ai,\n * burdenoff.com, bigconsole.com, planmagnet.com, adaptercloud.com, botmanor.com and\n * timecampus.com).\n */\n\n/**\n * Shell subdomains that front a product's app surface. Stripping one of these\n * leaves the bare product domain whose `www.` host serves the marketing site.\n */\nconst APP_HOST_PREFIXES = [\n \"app.\",\n \"alphaapp.\",\n \"admin.\",\n \"alphaadmin.\",\n \"www.\",\n];\n\n/**\n * Resolve the public marketing origin for the product currently being served,\n * or `null` when it cannot be determined (local development, or a host with no\n * registrable domain). Exported for unit testing; prefer `resolveLegalUrl`.\n */\nexport function resolveMarketingOrigin(hostname?: string): string | null {\n const host = (\n hostname ?? (typeof window !== \"undefined\" ? window.location.hostname : \"\")\n )\n .toLowerCase()\n .trim();\n\n if (!host) return null;\n\n // Local development has no marketing site to link to. `0.0.0.0` and IPv6\n // literals (which `location.hostname` reports bracketed, e.g. \"[::1]\") are\n // included because a bare dotted-quad would otherwise survive the \"has a dot\"\n // check below and produce a nonsense origin like `https://www.0.0.0.0`.\n if (\n host === \"localhost\" ||\n host === \"127.0.0.1\" ||\n host === \"0.0.0.0\" ||\n host.startsWith(\"[\") ||\n host.endsWith(\".local.burdenoff.com\")\n ) {\n return null;\n }\n\n // Any other IPv4 literal is likewise not a registrable domain.\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host)) return null;\n\n let domain = host;\n for (const prefix of APP_HOST_PREFIXES) {\n if (domain.startsWith(prefix)) {\n domain = domain.slice(prefix.length);\n break;\n }\n }\n\n // A bare hostname with no dot is not a registrable domain we can build on.\n if (!domain.includes(\".\")) return null;\n\n return `https://www.${domain}`;\n}\n\n/** Public legal pages, by the route name every product marketing site uses. */\nexport type LegalDocumentPath = \"/terms\" | \"/privacy\";\n\n/**\n * Absolute URL of a public legal document for the current product.\n *\n * Falls back to the bare path when the marketing origin cannot be resolved\n * (local development), which keeps the link valid-looking in dev without\n * inventing a production domain.\n */\nexport function resolveLegalUrl(path: LegalDocumentPath): string {\n const origin = resolveMarketingOrigin();\n return origin ? `${origin}${path}` : path;\n}\n"],"mappings":";AAiCA,IAAM,IAAoB;CACxB;CACA;CACA;CACA;CACA;AACF;AAOA,SAAgB,EAAuB,GAAkC;CACvE,IAAM,KACJ,MAAa,OAAO,SAAW,MAAc,OAAO,SAAS,WAAW,KAEvE,YAAY,EACZ,KAAK;CAmBR,IAjBI,CAAC,KAOH,MAAS,eACT,MAAS,eACT,MAAS,aACT,EAAK,WAAW,GAAG,KACnB,EAAK,SAAS,sBAAsB,KAMlC,uBAAuB,KAAK,CAAI,GAAG,OAAO;CAE9C,IAAI,IAAS;CACb,KAAK,IAAM,KAAU,GACnB,IAAI,EAAO,WAAW,CAAM,GAAG;EAC7B,IAAS,EAAO,MAAM,EAAO,MAAM;EACnC;CACF;CAMF,OAFK,EAAO,SAAS,GAAG,IAEjB,eAAe,MAFY;AAGpC;AAYA,SAAgB,EAAgB,GAAiC;CAC/D,IAAM,IAAS,EAAuB;CACtC,OAAO,IAAS,GAAG,IAAS,MAAS;AACvC"}
1
+ {"version":3,"file":"legalUrls.js","names":["APP_HOST_PREFIXES","resolveMarketingOrigin","host","hostname","domain","prefix","resolveLegalUrl","origin","path"],"sources":["../../../src/auth/utils/legalUrls.ts"],"sourcesContent":["/**\n * Public legal-document URLs for the signup consent copy.\n *\n * The signup form is shown to SIGNED-OUT visitors, so its \"Terms of Service\" and\n * \"Privacy Policy\" links must resolve to the public marketing site. They used to\n * be written as the app-relative paths `/legal/terms` and `/legal/privacy`, which\n * resolved against the app origin (e.g. `https://app.fluidgrids.ai/legal/terms`).\n * That route lives *inside* the shell's authenticated block, so a visitor who\n * clicked it was bounced to `/auth/login?returnTo=%2Flegal%2Fterms` and the shell\n * footer fired two FORBIDDEN queries on the way out:\n *\n * [Burdenoff backend error] You do not have permission to do that.\n * {code: FORBIDDEN, operationName: GetFooterContentPages}\n * {code: FORBIDDEN, operationName: GetFooterProduct}\n *\n * i.e. the consent links were unreadable for exactly the people being asked to\n * consent. The marketing site is also the authoritative home of the policy text\n * (rendered from the dated drafts in `fluidgrids-website/src/content/legal/`),\n * so linking there avoids maintaining a second copy that can drift.\n *\n * microfe-auth is mounted by every Burdenoff product shell, so the origin is\n * DERIVED from the current host rather than hardcoded — hardcoding one product's\n * domain would send every other product's users to the wrong company's terms.\n * Every product serves its marketing site at `www.<product-domain>` using the\n * same `/terms` and `/privacy` route names (verified against fluidgrids.ai,\n * burdenoff.com, bigconsole.com, planmagnet.com, adaptercloud.com, botmanor.com and\n * timecampus.com).\n */\n\n/**\n * Shell subdomains that front a product's app surface. Stripping one of these\n * leaves the bare product domain whose `www.` host serves the marketing site.\n */\nconst APP_HOST_PREFIXES = [\n \"app.\",\n \"alphaapp.\",\n \"admin.\",\n \"alphaadmin.\",\n \"www.\",\n];\n\n/**\n * Resolve the public marketing origin for the product currently being served,\n * or `null` when it cannot be determined (local development, or a host with no\n * registrable domain). Exported for unit testing; prefer `resolveLegalUrl`.\n */\nexport function resolveMarketingOrigin(hostname?: string): string | null {\n const host = (\n hostname ?? (typeof window !== \"undefined\" ? window.location.hostname : \"\")\n )\n .toLowerCase()\n .trim();\n\n if (!host) return null;\n\n // Local development has no marketing site to link to. `0.0.0.0` and IPv6\n // literals (which `location.hostname` reports bracketed, e.g. \"[::1]\") are\n // included because a bare dotted-quad would otherwise survive the \"has a dot\"\n // check below and produce a nonsense origin like `https://www.0.0.0.0`.\n if (\n host === \"localhost\" ||\n host === \"127.0.0.1\" ||\n host === \"0.0.0.0\" ||\n host.startsWith(\"[\") ||\n host.endsWith(\".local.burdenoff.com\")\n ) {\n return null;\n }\n\n // Any other IPv4 literal is likewise not a registrable domain.\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host)) return null;\n\n let domain = host;\n for (const prefix of APP_HOST_PREFIXES) {\n if (domain.startsWith(prefix)) {\n domain = domain.slice(prefix.length);\n break;\n }\n }\n\n // A bare hostname with no dot is not a registrable domain we can build on.\n if (!domain.includes(\".\")) return null;\n\n return `https://www.${domain}`;\n}\n\n/** Public legal pages, by the route name every product marketing site uses. */\nexport type LegalDocumentPath = \"/terms\" | \"/privacy\";\n\n/**\n * Absolute URL of a public legal document for the current product.\n *\n * Falls back to the bare path when the marketing origin cannot be resolved\n * (local development), which keeps the link valid-looking in dev without\n * inventing a production domain.\n */\nexport function resolveLegalUrl(path: LegalDocumentPath): string {\n const origin = resolveMarketingOrigin();\n return origin ? `${origin}${path}` : path;\n}\n"],"mappings":";AAiCA,IAAMA,IAAoB;CACxB;CACA;CACA;CACA;CACA;AACF;AAOA,SAAgBC,EAAuB,GAAkC;CACvE,IAAMC,KACJC,MAAa,OAAO,SAAW,MAAc,OAAO,SAAS,WAAW,IAAA,CAEvE,YAAY,CAAC,CACb,KAAK;CAmBR,IAjBI,CAACD,KAOHA,MAAS,eACTA,MAAS,eACTA,MAAS,aACTA,EAAK,WAAW,GAAG,KACnBA,EAAK,SAAS,sBAAsB,KAMlC,uBAAuB,KAAKA,CAAI,GAAG,OAAO;CAE9C,IAAIE,IAASF;CACb,KAAK,IAAMG,KAAUL,GACnB,IAAII,EAAO,WAAWC,CAAM,GAAG;EAC7B,IAASD,EAAO,MAAMC,EAAO,MAAM;EACnC;CACF;CAMF,OAFKD,EAAO,SAAS,GAAG,IAEjB,eAAeA,MAFY;AAGpC;AAYA,SAAgBE,EAAgB,GAAiC;CAC/D,IAAMC,IAASN,EAAuB;CACtC,OAAOM,IAAS,GAAGA,IAASC,MAASA;AACvC"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/utils/redirect.ts
1
+ // src/auth/utils/redirect.ts
2
2
  function e(e) {
3
3
  if (typeof window > "u") return !1;
4
4
  try {
@@ -10,7 +10,6 @@ function e(e) {
10
10
  function t(t) {
11
11
  return typeof window > "u" ? t : !t || t === "/" || e(t) ? "/dashboard" : t;
12
12
  }
13
- //#endregion
14
13
  export { t as sanitizePostLoginPath };
15
14
 
16
15
  //# sourceMappingURL=redirect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.js","names":[],"sources":["../../../src/auth/utils/redirect.ts"],"sourcesContent":["/**\n * Redirect helpers for the post-login flow.\n *\n * Keeps redirect-sanitization logic pure and unit-testable so the login page\n * cannot accidentally redirect back into an auth route (BOFF-6807).\n */\n\nfunction isAuthRoutePath(path: string): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const url = new URL(path, window.location.origin);\n return url.pathname.startsWith(\"/auth\");\n } catch {\n return false;\n }\n}\n\n/**\n * Ensure the post-login destination never lands back on an auth route.\n * Falls back to `/dashboard` when the supplied path is empty, \"/\", or an\n * `/auth/*` path.\n */\nexport function sanitizePostLoginPath(path: string): string {\n if (typeof window === \"undefined\") return path;\n if (!path || path === \"/\" || isAuthRoutePath(path)) {\n return \"/dashboard\";\n }\n return path;\n}\n"],"mappings":";AAOA,SAAS,EAAgB,GAAuB;CAC9C,IAAI,OAAO,SAAW,KAAa,OAAO;CAC1C,IAAI;EAEF,OAAO,IADS,IAAI,GAAM,OAAO,SAAS,MACnC,EAAI,SAAS,WAAW,OAAO;CACxC,QAAQ;EACN,OAAO;CACT;AACF;AAOA,SAAgB,EAAsB,GAAsB;CAK1D,OAJI,OAAO,SAAW,MAAoB,IACtC,CAAC,KAAQ,MAAS,OAAO,EAAgB,CAAI,IACxC,eAEF;AACT"}
1
+ {"version":3,"file":"redirect.js","names":["isAuthRoutePath","path","sanitizePostLoginPath"],"sources":["../../../src/auth/utils/redirect.ts"],"sourcesContent":["/**\n * Redirect helpers for the post-login flow.\n *\n * Keeps redirect-sanitization logic pure and unit-testable so the login page\n * cannot accidentally redirect back into an auth route (BOFF-6807).\n */\n\nfunction isAuthRoutePath(path: string): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const url = new URL(path, window.location.origin);\n return url.pathname.startsWith(\"/auth\");\n } catch {\n return false;\n }\n}\n\n/**\n * Ensure the post-login destination never lands back on an auth route.\n * Falls back to `/dashboard` when the supplied path is empty, \"/\", or an\n * `/auth/*` path.\n */\nexport function sanitizePostLoginPath(path: string): string {\n if (typeof window === \"undefined\") return path;\n if (!path || path === \"/\" || isAuthRoutePath(path)) {\n return \"/dashboard\";\n }\n return path;\n}\n"],"mappings":";AAOA,SAASA,EAAgB,GAAuB;CAC9C,IAAI,OAAO,SAAW,KAAa,OAAO;CAC1C,IAAI;EAEF,OAAO,IADS,IAAIC,GAAM,OAAO,SAAS,MACnC,CAAA,CAAI,SAAS,WAAW,OAAO;CACxC,QAAQ;EACN,OAAO;CACT;AACF;AAOA,SAAgBC,EAAsB,GAAsB;CAK1D,OAJI,OAAO,SAAW,MAAoBD,IACtC,CAACA,KAAQA,MAAS,OAAOD,EAAgBC,CAAI,IACxC,eAEFA;AACT"}
@@ -1,5 +1,5 @@
1
- //#region src/auth/utils/ssoOrigins.ts
2
- var e = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,botlit.ai,botmanor.com,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(","));
1
+ // src/auth/utils/ssoOrigins.ts
2
+ var e = /* @__PURE__ */ new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,botlit.ai,botmanor.com,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
3
  function t(t) {
4
4
  if (t.hostname === "localhost" || t.hostname === "127.0.0.1" || t.hostname.endsWith(".local.burdenoff.com")) return !0;
5
5
  if (t.protocol !== "https:") return !1;
@@ -13,7 +13,6 @@ function n(e) {
13
13
  return !1;
14
14
  }
15
15
  }
16
- //#endregion
17
16
  export { t as isAllowedSsoOrigin, n as isAllowedSsoOriginString };
18
17
 
19
18
  //# sourceMappingURL=ssoOrigins.js.map
@@ -1 +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 // BotManor (formerly Botlit): botmanor.com is the primary domain; botlit.ai\n // stays trusted while it redirects there, so in-flight relays still return.\n \"botlit.ai\",\n \"botmanor.com\",\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,0rBA2C5C,CAAC;AAYD,SAAgB,EAAmB,GAAmB;CAEpD,IADI,EAAI,aAAa,eAAe,EAAI,aAAa,eACjD,EAAI,SAAS,SAAS,sBAAsB,GAAG,OAAO;CAC1D,IAAI,EAAI,aAAa,UAAU,OAAO;CACtC,KAAK,IAAM,KAAU,GACnB,IACE,EAAI,aAAa,KACjB,EAAI,aAAa,OAAO,OACxB,EAAI,aAAa,YAAY,OAC7B,EAAI,aAAa,SAAS,OAC1B,EAAI,aAAa,cAAc,OAC/B,EAAI,aAAa,OAAO,KAExB,OAAO;CAGX,OAAO;AACT;AAOA,SAAgB,EAAyB,GAAyB;CAChE,IAAI;EACF,OAAO,EAAmB,IAAI,IAAI,CAAM,CAAC;CAC3C,QAAQ;EACN,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"ssoOrigins.js","names":["ALLOWED_SSO_DOMAINS","isAllowedSsoOrigin","url","domain","isAllowedSsoOriginString","origin"],"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 // BotManor (formerly Botlit): botmanor.com is the primary domain; botlit.ai\n // stays trusted while it redirects there, so in-flight relays still return.\n \"botlit.ai\",\n \"botmanor.com\",\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,IAAMA,oBAAsB,IAAI,IAAY,0rBA2C5C,CAAC;AAYD,SAAgBC,EAAmB,GAAmB;CAEpD,IADIC,EAAI,aAAa,eAAeA,EAAI,aAAa,eACjDA,EAAI,SAAS,SAAS,sBAAsB,GAAG,OAAO;CAC1D,IAAIA,EAAI,aAAa,UAAU,OAAO;CACtC,KAAK,IAAMC,KAAUH,GACnB,IACEE,EAAI,aAAaC,KACjBD,EAAI,aAAa,OAAOC,OACxBD,EAAI,aAAa,YAAYC,OAC7BD,EAAI,aAAa,SAASC,OAC1BD,EAAI,aAAa,cAAcC,OAC/BD,EAAI,aAAa,OAAOC,KAExB,OAAO;CAGX,OAAO;AACT;AAOA,SAAgBC,EAAyB,GAAyB;CAChE,IAAI;EACF,OAAOH,EAAmB,IAAI,IAAII,CAAM,CAAC;CAC3C,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -1,4 +1,4 @@
1
- //#region src/auth/utils/validation.ts
1
+ // src/auth/utils/validation.ts
2
2
  function e(e) {
3
3
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);
4
4
  }
@@ -9,22 +9,21 @@ function t(e) {
9
9
  errors: t
10
10
  };
11
11
  }
12
- var n = 1, r = 100;
13
- function i(e) {
12
+ function n(e) {
14
13
  return [...e].length;
15
14
  }
16
- var a = /^[\p{L}\p{M} '.\-\u00B7\u30FB\u200C\u200D]*\p{L}[\p{L}\p{M} '.\-\u00B7\u30FB\u200C\u200D]*$/u, o = /[\u2018\u2019\u02BC]/g, s = /[\u2010\u2011]/g;
17
- function c(e) {
18
- let t = (e ?? "").normalize("NFC").replace(o, "'").replace(s, "-").trim();
19
- return i(t) < n ? {
15
+ var r = /^[\p{L}\p{M} '.\-\u00B7\u30FB\u200C\u200D]*\p{L}[\p{L}\p{M} '.\-\u00B7\u30FB\u200C\u200D]*$/u, i = /[\u2018\u2019\u02BC]/g, a = /[\u2010\u2011]/g;
16
+ function o(e) {
17
+ let t = (e ?? "").normalize("NFC").replace(i, "'").replace(a, "-").trim();
18
+ return n(t) < 1 ? {
20
19
  valid: !1,
21
20
  code: "required",
22
21
  message: "Name is required"
23
- } : i(t) > r ? {
22
+ } : n(t) > 100 ? {
24
23
  valid: !1,
25
24
  code: "too_long",
26
- message: `Name must be ${r} characters or fewer`
27
- } : a.test(t) ? {
25
+ message: "Name must be 100 characters or fewer"
26
+ } : r.test(t) ? {
28
27
  valid: !0,
29
28
  value: t
30
29
  } : /\p{L}/u.test(t) ? {
@@ -37,47 +36,47 @@ function c(e) {
37
36
  message: "Name must contain at least one letter"
38
37
  };
39
38
  }
40
- var l = /^[a-zA-Z0-9_-]+$/, u = 3, d = 30;
41
- function f(e) {
42
- let t = [], n = (e ?? "").trim();
43
- return i(n) === 0 ? t.push({
39
+ var s = /^[a-zA-Z0-9_-]+$/;
40
+ function c(e) {
41
+ let t = [], r = (e ?? "").trim();
42
+ return n(r) === 0 ? t.push({
44
43
  code: "required",
45
44
  message: "Username is required"
46
- }) : (i(n) < u && t.push({
45
+ }) : (n(r) < 3 && t.push({
47
46
  code: "too_short",
48
- message: `Username must be at least ${u} characters`
49
- }), i(n) > d && t.push({
47
+ message: "Username must be at least 3 characters"
48
+ }), n(r) > 30 && t.push({
50
49
  code: "too_long",
51
- message: `Username must be ${d} characters or fewer`
52
- }), l.test(n) || t.push({
50
+ message: "Username must be 30 characters or fewer"
51
+ }), s.test(r) || t.push({
53
52
  code: "invalid_chars",
54
53
  message: "Username can only contain letters, numbers, underscores, and hyphens"
55
- }), i(n) >= u && !/[a-zA-Z]/.test(n) && t.push({
54
+ }), n(r) >= 3 && !/[a-zA-Z]/.test(r) && t.push({
56
55
  code: "no_letter",
57
56
  message: "Username must contain at least one letter"
58
57
  })), t.length === 0 ? {
59
58
  valid: !0,
60
- value: n
59
+ value: r
61
60
  } : {
62
61
  valid: !1,
63
62
  errors: t
64
63
  };
65
64
  }
66
- function p(e, t = {}) {
67
- let n = t.requireAll ?? !0, r = [], i = (e) => !e || e.trim().length === 0, a = !i(e.firstName) || n ? c(e.firstName) : null;
65
+ function l(e, t = {}) {
66
+ let n = t.requireAll ?? !0, r = [], i = (e) => !e || e.trim().length === 0, a = !i(e.firstName) || n ? o(e.firstName) : null;
68
67
  a && !a.valid && r.push({
69
68
  field: "firstName",
70
69
  code: a.code,
71
70
  message: a.message
72
71
  });
73
- let o = !i(e.lastName) || n ? c(e.lastName) : null;
74
- o && !o.valid && r.push({
72
+ let s = !i(e.lastName) || n ? o(e.lastName) : null;
73
+ s && !s.valid && r.push({
75
74
  field: "lastName",
76
- code: o.code,
77
- message: o.message
75
+ code: s.code,
76
+ message: s.message
78
77
  });
79
- let s = !i(e.username) || n ? f(e.username) : null;
80
- if (s && !s.valid) for (let e of s.errors) r.push({
78
+ let l = !i(e.username) || n ? c(e.username) : null;
79
+ if (l && !l.valid) for (let e of l.errors) r.push({
81
80
  field: "username",
82
81
  code: e.code,
83
82
  message: e.message
@@ -86,17 +85,17 @@ function p(e, t = {}) {
86
85
  valid: !1,
87
86
  errors: r
88
87
  };
89
- let l = (e, t) => e ? e.value : (t ?? "").trim();
88
+ let u = (e, t) => e ? e.value : (t ?? "").trim();
90
89
  return {
91
90
  valid: !0,
92
91
  values: {
93
- firstName: l(a, e.firstName),
94
- lastName: l(o, e.lastName),
95
- username: l(s, e.username)
92
+ firstName: u(a, e.firstName),
93
+ lastName: u(s, e.lastName),
94
+ username: u(l, e.username)
96
95
  }
97
96
  };
98
97
  }
99
- function m(e) {
98
+ function u(e) {
100
99
  let t = e.trim();
101
100
  if (t.length === 0) return { valid: !0 };
102
101
  if (!/^[0-9+\-() ]+$/.test(t)) return {
@@ -115,7 +114,6 @@ function m(e) {
115
114
  error: "Phone number must start with +, a digit, or ("
116
115
  };
117
116
  }
118
- //#endregion
119
- export { e as validateEmail, t as validatePassword, m as validatePhoneNumber, p as validateUserFields };
117
+ export { e as validateEmail, o as validateName, t as validatePassword, u as validatePhoneNumber, l as validateUserFields, c as validateUsername };
120
118
 
121
119
  //# sourceMappingURL=validation.js.map