@burdenoff/microfe-auth 2026.1030.1 → 2026.1101.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/AuthRoot.js +14 -11
- package/dist/auth/AuthRoot.js.map +1 -1
- package/dist/auth/AuthRoutes.js +1 -2
- package/dist/auth/AuthRoutes.js.map +1 -1
- package/dist/auth/components/AuthIcons.js +1 -2
- package/dist/auth/components/AuthIcons.js.map +1 -1
- package/dist/auth/components/AuthLayout.js +1 -2
- package/dist/auth/components/AuthLayout.js.map +1 -1
- package/dist/auth/components/CaptchaV2.js +9 -10
- package/dist/auth/components/CaptchaV2.js.map +1 -1
- package/dist/auth/components/ConfirmActionDialog.js +1 -2
- package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
- package/dist/auth/components/profile/UserProfileMenu.js +7 -6
- package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
- package/dist/auth/components/workspace/WorkspaceSelector.js +1 -2
- package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
- package/dist/auth/constants/permissions.js +1 -2
- package/dist/auth/constants/permissions.js.map +1 -1
- package/dist/auth/constants/sessionKeys.js +1 -2
- package/dist/auth/constants/sessionKeys.js.map +1 -1
- package/dist/auth/hooks/contextDeepLink.js +1 -2
- package/dist/auth/hooks/contextDeepLink.js.map +1 -1
- package/dist/auth/hooks/index.js +12 -11
- package/dist/auth/hooks/useAccountOperations.js +1 -2
- package/dist/auth/hooks/useAccountOperations.js.map +1 -1
- package/dist/auth/hooks/useActiveContextBootstrapMirror.js +1 -2
- package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
- package/dist/auth/hooks/useAuthOperations.js +37 -37
- package/dist/auth/hooks/useAuthOperations.js.map +1 -1
- package/dist/auth/hooks/useAuthPermissions.js +1 -2
- package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
- package/dist/auth/hooks/useCaptchaV3.js +1 -2
- package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
- package/dist/auth/hooks/useContextSwitcherData.js +81 -78
- package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
- package/dist/auth/hooks/useMfaOperations.js +4 -5
- package/dist/auth/hooks/useMfaOperations.js.map +1 -1
- package/dist/auth/hooks/useMyWorkspaces.js +4 -5
- package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
- package/dist/auth/hooks/useOAuthOperations.js +11 -12
- package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
- package/dist/auth/hooks/usePasswordOperations.js +1 -2
- package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
- package/dist/auth/hooks/useSessionOperations.js +3 -4
- package/dist/auth/hooks/useSessionOperations.js.map +1 -1
- package/dist/auth/hooks/useSignupCaptcha.js +1 -2
- package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
- package/dist/auth/hooks/useSsoOperations.js +80 -81
- package/dist/auth/hooks/useSsoOperations.js.map +1 -1
- package/dist/auth/hooks/useTokenRefresh.js +8 -12
- package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
- package/dist/auth/hooks/useUserOperations.js +1 -2
- package/dist/auth/hooks/useUserOperations.js.map +1 -1
- package/dist/auth/hooks/useWorkspaceOperations.js +1 -2
- package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
- package/dist/auth/hooks/useWorkspaceToken.js +1 -2
- package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
- package/dist/auth/index.js +19 -18
- package/dist/auth/pages/AuditLogPage.js +21 -22
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/EmailVerifyPage.js +70 -64
- package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
- package/dist/auth/pages/LoginPage.js +89 -105
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +30 -31
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js +18 -20
- package/dist/auth/pages/MfaSetupPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +31 -35
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/OAuthCallbackPage.js +4 -11
- package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +20 -23
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +14 -18
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +180 -181
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SSOClearPage.js +9 -10
- package/dist/auth/pages/SSOClearPage.js.map +1 -1
- package/dist/auth/pages/SSODepositPage.js +9 -13
- package/dist/auth/pages/SSODepositPage.js.map +1 -1
- package/dist/auth/pages/SSORelayCallbackPage.js +3 -7
- package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
- package/dist/auth/pages/SSORelayPage.js +11 -10
- package/dist/auth/pages/SSORelayPage.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js +19 -21
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SessionBridgePage.js +9 -16
- package/dist/auth/pages/SessionBridgePage.js.map +1 -1
- package/dist/auth/pages/SessionsPage.js +17 -18
- package/dist/auth/pages/SessionsPage.js.map +1 -1
- package/dist/auth/pages/SsoCallbackPage.js +17 -22
- package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js +69 -69
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +9 -10
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js +16 -23
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js +140 -141
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js +446 -453
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/dist/auth/providers/AuthProvider.js +1 -2
- package/dist/auth/providers/AuthProvider.js.map +1 -1
- package/dist/auth/providers/CaptchaProvider.js +1 -2
- package/dist/auth/providers/CaptchaProvider.js.map +1 -1
- package/dist/auth/store/authStore.js +5 -12
- package/dist/auth/store/authStore.js.map +1 -1
- package/dist/auth/utils/captchaConfig.js +5 -8
- package/dist/auth/utils/captchaConfig.js.map +1 -1
- package/dist/auth/utils/formatters.js +1 -2
- package/dist/auth/utils/formatters.js.map +1 -1
- package/dist/auth/utils/gatewayBaseUrls.js +1 -2
- package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
- package/dist/auth/utils/legalUrls.js +2 -3
- package/dist/auth/utils/legalUrls.js.map +1 -1
- package/dist/auth/utils/redirect.js +1 -2
- package/dist/auth/utils/redirect.js.map +1 -1
- package/dist/auth/utils/ssoOrigins.js +2 -3
- package/dist/auth/utils/ssoOrigins.js.map +1 -1
- package/dist/auth/utils/validation.js +34 -36
- package/dist/auth/utils/validation.js.map +1 -1
- package/dist/generated/global-operations.js +2 -3
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/wspace-operations.js +2 -3
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/microfe-auth.css +1 -1
- package/dist/shared/hooks/useTr.js +1 -2
- package/dist/shared/hooks/useTr.js.map +1 -1
- package/dist/translations/en.js +1 -2
- package/dist/translations/en.js.map +1 -1
- package/dist/utils/nativeBridge.js +1 -2
- package/package.json +7 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useAuthStore as e } from "../store/authStore.js";
|
|
2
2
|
import { useCallback as t, useState as n } from "react";
|
|
3
3
|
import { graphqlFetch as r } from "@burdenoff/fe-libs/shared/graphql/fetch";
|
|
4
|
-
|
|
4
|
+
// src/auth/hooks/useWorkspaceToken.ts
|
|
5
5
|
function i(i = {}) {
|
|
6
6
|
let [a, o] = n(!1), [s, c] = n(null), l = t(async (t, n, i, a) => {
|
|
7
7
|
o(!0), c(null);
|
|
@@ -88,7 +88,6 @@ function i(i = {}) {
|
|
|
88
88
|
error: s
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
//#endregion
|
|
92
91
|
export { i as useWorkspaceToken };
|
|
93
92
|
|
|
94
93
|
//# sourceMappingURL=useWorkspaceToken.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWorkspaceToken.js","names":[],"sources":["../../../src/auth/hooks/useWorkspaceToken.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { graphqlFetch } from \"@burdenoff/fe-libs/shared/graphql/fetch\";\nimport { useAuthStore } from \"../store/authStore\";\n\n/**\n * Workspace token management hook\n *\n * Provides functions to issue, refresh, and verify workspace tokens.\n * Automatically updates the auth store with new tokens.\n *\n * API Flow:\n * - Calls workspace gateway (https://gw-wspace.local.burdenoff.com/workspaces/graphql)\n * - Gateway federates wspace-auth-svc which handles token operations\n * - Auth token (from global-auth-svc) goes in Authorization header\n *\n * Usage:\n * ```tsx\n * const { issueToken, refreshToken, verifyToken, isLoading, error } = useWorkspaceToken({\n * workspaceGatewayUrl: 'https://gw-wspace.local.burdenoff.com/workspaces/graphql'\n * });\n *\n * // After login and organization/workspace selection\n * await issueToken(organizationId, workspaceId);\n *\n * // Refresh token before expiry (uses stored org/workspace IDs)\n * await refreshToken();\n * ```\n */\nexport interface UseWorkspaceTokenOptions {\n /**\n * URL of workspace gateway GraphQL endpoint\n * Default: https://gw-wspace.local.burdenoff.com/workspaces/graphql\n *\n * Note: This should point to the workspace gateway, NOT directly to wspace-auth-svc.\n * The gateway federates wspace-auth-svc and handles routing.\n */\n workspaceGatewayUrl?: string;\n}\n\nexport interface WorkspaceTokenPayload {\n sub: string; // User ID\n workspaceId: string;\n tenantId?: string;\n email?: string;\n username?: string;\n role?: string;\n scopes: string[];\n iat: number;\n exp: number;\n}\n\nexport function useWorkspaceToken(_options: UseWorkspaceTokenOptions = {}) {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n /**\n * Issue a new workspace token for the given workspace\n *\n * @param organizationId - Organization ID (required)\n * @param workspaceId - Workspace ID (optional, auto-resolves to first accessible)\n * @param projectId - Project ID (optional, auto-resolves to first accessible)\n * @param scopes - Requested scopes (optional, defaults to full access)\n */\n const issueToken = useCallback(\n async (\n organizationId: string,\n workspaceId?: string,\n projectId?: string,\n scopes?: string[],\n ): Promise<string> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const accessToken = useAuthStore.getState().accessToken;\n\n if (!accessToken) {\n throw new Error(\"No access token available. Please login first.\");\n }\n\n const response = await graphqlFetch<{\n issueWorkspaceToken: {\n success: boolean;\n token: string;\n expiresAt: string;\n organizationId: string;\n workspaceId: string;\n projectId?: string;\n scopes: string[];\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {\n issueWorkspaceToken(input: $input) {\n success\n token\n expiresAt\n organizationId\n workspaceId\n projectId\n scopes\n error {\n code\n message\n }\n }\n }\n `,\n variables: {\n input: {\n organizationId,\n ...(workspaceId && { workspaceId }),\n ...(projectId && { projectId }),\n ...(scopes && { scopes }),\n },\n },\n authToken: accessToken,\n });\n\n const result = response.data?.issueWorkspaceToken;\n\n if (!result?.success || !result?.token) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Failed to issue workspace token\";\n throw new Error(errorMsg);\n }\n\n useAuthStore.getState().setWorkspaceToken(result.token);\n useAuthStore.getState().setCurrentWorkspaceId(result.workspaceId);\n\n setIsLoading(false);\n return result.token;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\"[useWorkspaceToken] Error issuing token:\", errorMessage);\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Verify a workspace token\n *\n * @param token - Optional token to verify (uses stored token if not provided)\n */\n const verifyToken = useCallback(\n async (token?: string): Promise<WorkspaceTokenPayload> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToVerify = token || workspaceToken;\n\n if (!tokenToVerify) {\n throw new Error(\"No workspace token available to verify\");\n }\n\n const response = await graphqlFetch<{\n verifyWorkspaceToken: {\n valid: boolean;\n payload?: WorkspaceTokenPayload;\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n query VerifyWorkspaceToken($token: String!) {\n verifyWorkspaceToken(token: $token) {\n valid\n payload {\n sub\n workspaceId\n tenantId\n email\n username\n role\n scopes\n iat\n exp\n }\n error {\n code\n message\n }\n }\n }\n `,\n variables: { token: tokenToVerify },\n });\n\n const result = response.data?.verifyWorkspaceToken;\n\n if (!result?.valid) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Token verification failed\";\n throw new Error(errorMsg);\n }\n\n setIsLoading(false);\n return result.payload!;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\n \"[useWorkspaceToken] Error verifying token:\",\n errorMessage,\n );\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Refresh the workspace token\n */\n const refreshToken = useCallback(async (): Promise<string> => {\n const { currentOrganizationId, currentWorkspaceId } =\n useAuthStore.getState();\n if (!currentOrganizationId) {\n throw new Error(\"No organization ID in store. Please login first.\");\n }\n return issueToken(currentOrganizationId, currentWorkspaceId ?? undefined);\n }, [issueToken]);\n\n /**\n * Decode a JWT token to get its payload (client-side only, no verification)\n */\n const decodeToken = useCallback((token: string): WorkspaceTokenPayload => {\n try {\n const base64Url = token.split(\".\")[1];\n const base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split(\"\")\n .map((c) => \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2))\n .join(\"\"),\n );\n return JSON.parse(jsonPayload);\n } catch {\n throw new Error(\"Failed to decode token\");\n }\n }, []);\n\n /**\n * Check if a token is expired (client-side check)\n */\n const isTokenExpired = useCallback(\n (token?: string): boolean => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return true;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n\n return payload.exp < now;\n } catch {\n return true;\n }\n },\n [decodeToken],\n );\n\n /**\n * Get time until token expiry in milliseconds\n */\n const getTimeUntilExpiry = useCallback(\n (token?: string): number => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return 0;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n const timeUntilExpiry = (payload.exp - now) * 1000; // Convert to ms\n\n return Math.max(0, timeUntilExpiry);\n } catch {\n return 0;\n }\n },\n [decodeToken],\n );\n\n return {\n issueToken,\n refreshToken,\n verifyToken,\n decodeToken,\n isTokenExpired,\n getTimeUntilExpiry,\n isLoading,\n error,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useWorkspaceToken.js","names":["useWorkspaceToken","isLoading","setIsLoading","useState","error","setError","issueToken","useCallback","accessToken","useAuthStore","result","graphqlFetch","workspaceId","projectId","scopes","errorMsg","err","errorMessage","verifyToken","tokenToVerify","token","workspaceToken","refreshToken","currentOrganizationId","currentWorkspaceId","decodeToken","base64","jsonPayload","c","tokenToCheck","payload","now","timeUntilExpiry"],"sources":["../../../src/auth/hooks/useWorkspaceToken.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { graphqlFetch } from \"@burdenoff/fe-libs/shared/graphql/fetch\";\nimport { useAuthStore } from \"../store/authStore\";\n\n/**\n * Workspace token management hook\n *\n * Provides functions to issue, refresh, and verify workspace tokens.\n * Automatically updates the auth store with new tokens.\n *\n * API Flow:\n * - Calls workspace gateway (https://gw-wspace.local.burdenoff.com/workspaces/graphql)\n * - Gateway federates wspace-auth-svc which handles token operations\n * - Auth token (from global-auth-svc) goes in Authorization header\n *\n * Usage:\n * ```tsx\n * const { issueToken, refreshToken, verifyToken, isLoading, error } = useWorkspaceToken({\n * workspaceGatewayUrl: 'https://gw-wspace.local.burdenoff.com/workspaces/graphql'\n * });\n *\n * // After login and organization/workspace selection\n * await issueToken(organizationId, workspaceId);\n *\n * // Refresh token before expiry (uses stored org/workspace IDs)\n * await refreshToken();\n * ```\n */\nexport interface UseWorkspaceTokenOptions {\n /**\n * URL of workspace gateway GraphQL endpoint\n * Default: https://gw-wspace.local.burdenoff.com/workspaces/graphql\n *\n * Note: This should point to the workspace gateway, NOT directly to wspace-auth-svc.\n * The gateway federates wspace-auth-svc and handles routing.\n */\n workspaceGatewayUrl?: string;\n}\n\nexport interface WorkspaceTokenPayload {\n sub: string; // User ID\n workspaceId: string;\n tenantId?: string;\n email?: string;\n username?: string;\n role?: string;\n scopes: string[];\n iat: number;\n exp: number;\n}\n\nexport function useWorkspaceToken(_options: UseWorkspaceTokenOptions = {}) {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n /**\n * Issue a new workspace token for the given workspace\n *\n * @param organizationId - Organization ID (required)\n * @param workspaceId - Workspace ID (optional, auto-resolves to first accessible)\n * @param projectId - Project ID (optional, auto-resolves to first accessible)\n * @param scopes - Requested scopes (optional, defaults to full access)\n */\n const issueToken = useCallback(\n async (\n organizationId: string,\n workspaceId?: string,\n projectId?: string,\n scopes?: string[],\n ): Promise<string> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const accessToken = useAuthStore.getState().accessToken;\n\n if (!accessToken) {\n throw new Error(\"No access token available. Please login first.\");\n }\n\n const response = await graphqlFetch<{\n issueWorkspaceToken: {\n success: boolean;\n token: string;\n expiresAt: string;\n organizationId: string;\n workspaceId: string;\n projectId?: string;\n scopes: string[];\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {\n issueWorkspaceToken(input: $input) {\n success\n token\n expiresAt\n organizationId\n workspaceId\n projectId\n scopes\n error {\n code\n message\n }\n }\n }\n `,\n variables: {\n input: {\n organizationId,\n ...(workspaceId && { workspaceId }),\n ...(projectId && { projectId }),\n ...(scopes && { scopes }),\n },\n },\n authToken: accessToken,\n });\n\n const result = response.data?.issueWorkspaceToken;\n\n if (!result?.success || !result?.token) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Failed to issue workspace token\";\n throw new Error(errorMsg);\n }\n\n useAuthStore.getState().setWorkspaceToken(result.token);\n useAuthStore.getState().setCurrentWorkspaceId(result.workspaceId);\n\n setIsLoading(false);\n return result.token;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\"[useWorkspaceToken] Error issuing token:\", errorMessage);\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Verify a workspace token\n *\n * @param token - Optional token to verify (uses stored token if not provided)\n */\n const verifyToken = useCallback(\n async (token?: string): Promise<WorkspaceTokenPayload> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToVerify = token || workspaceToken;\n\n if (!tokenToVerify) {\n throw new Error(\"No workspace token available to verify\");\n }\n\n const response = await graphqlFetch<{\n verifyWorkspaceToken: {\n valid: boolean;\n payload?: WorkspaceTokenPayload;\n error?: { code?: string; message?: string };\n };\n }>({\n gateway: \"workspace\",\n query: `\n query VerifyWorkspaceToken($token: String!) {\n verifyWorkspaceToken(token: $token) {\n valid\n payload {\n sub\n workspaceId\n tenantId\n email\n username\n role\n scopes\n iat\n exp\n }\n error {\n code\n message\n }\n }\n }\n `,\n variables: { token: tokenToVerify },\n });\n\n const result = response.data?.verifyWorkspaceToken;\n\n if (!result?.valid) {\n const errorMsg =\n typeof result?.error?.message === \"string\"\n ? result.error.message\n : typeof result?.error === \"string\"\n ? result.error\n : \"Token verification failed\";\n throw new Error(errorMsg);\n }\n\n setIsLoading(false);\n return result.payload!;\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Unknown error occurred\";\n console.error(\n \"[useWorkspaceToken] Error verifying token:\",\n errorMessage,\n );\n setError(errorMessage);\n setIsLoading(false);\n throw err;\n }\n },\n [],\n );\n\n /**\n * Refresh the workspace token\n */\n const refreshToken = useCallback(async (): Promise<string> => {\n const { currentOrganizationId, currentWorkspaceId } =\n useAuthStore.getState();\n if (!currentOrganizationId) {\n throw new Error(\"No organization ID in store. Please login first.\");\n }\n return issueToken(currentOrganizationId, currentWorkspaceId ?? undefined);\n }, [issueToken]);\n\n /**\n * Decode a JWT token to get its payload (client-side only, no verification)\n */\n const decodeToken = useCallback((token: string): WorkspaceTokenPayload => {\n try {\n const base64Url = token.split(\".\")[1];\n const base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split(\"\")\n .map((c) => \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2))\n .join(\"\"),\n );\n return JSON.parse(jsonPayload);\n } catch {\n throw new Error(\"Failed to decode token\");\n }\n }, []);\n\n /**\n * Check if a token is expired (client-side check)\n */\n const isTokenExpired = useCallback(\n (token?: string): boolean => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return true;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n\n return payload.exp < now;\n } catch {\n return true;\n }\n },\n [decodeToken],\n );\n\n /**\n * Get time until token expiry in milliseconds\n */\n const getTimeUntilExpiry = useCallback(\n (token?: string): number => {\n try {\n const { workspaceToken } = useAuthStore.getState();\n const tokenToCheck = token || workspaceToken;\n\n if (!tokenToCheck) return 0;\n\n const payload = decodeToken(tokenToCheck);\n const now = Math.floor(Date.now() / 1000);\n const timeUntilExpiry = (payload.exp - now) * 1000; // Convert to ms\n\n return Math.max(0, timeUntilExpiry);\n } catch {\n return 0;\n }\n },\n [decodeToken],\n );\n\n return {\n issueToken,\n refreshToken,\n verifyToken,\n decodeToken,\n isTokenExpired,\n getTimeUntilExpiry,\n isLoading,\n error,\n };\n}\n"],"mappings":"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"}
|
package/dist/auth/index.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import "./store/authStore.js";
|
|
3
|
-
import "./hooks/useAuthOperations.js";
|
|
4
|
-
import "./hooks/useMyWorkspaces.js";
|
|
5
|
-
import "./
|
|
6
|
-
import "./pages/SSORelayCallbackPage.js";
|
|
7
|
-
import "./AuthRoot.js";
|
|
8
|
-
import "./hooks/useMfaOperations.js";
|
|
9
|
-
import "./hooks/usePasswordOperations.js";
|
|
10
|
-
import "./hooks/useSessionOperations.js";
|
|
11
|
-
import "./hooks/useWorkspaceOperations.js";
|
|
12
|
-
import "./hooks/useContextSwitcherData.js";
|
|
13
|
-
import "./hooks/useActiveContextBootstrapMirror.js";
|
|
14
|
-
import "./hooks/useUserOperations.js";
|
|
1
|
+
import { AuthRoot as e } from "./AuthRoot.js";
|
|
2
|
+
import { useAuthStore as t } from "./store/authStore.js";
|
|
3
|
+
import { useAuthOperations as n } from "./hooks/useAuthOperations.js";
|
|
4
|
+
import { useMyWorkspaces as r } from "./hooks/useMyWorkspaces.js";
|
|
5
|
+
import { useAuth as i } from "./providers/AuthProvider.js";
|
|
15
6
|
import "./hooks/index.js";
|
|
16
|
-
import "./hooks/useWorkspaceToken.js";
|
|
17
|
-
import "./hooks/
|
|
18
|
-
import "./
|
|
19
|
-
import "./
|
|
7
|
+
import { useWorkspaceToken as a } from "./hooks/useWorkspaceToken.js";
|
|
8
|
+
import { useMfaOperations as o } from "./hooks/useMfaOperations.js";
|
|
9
|
+
import { usePasswordOperations as s } from "./hooks/usePasswordOperations.js";
|
|
10
|
+
import { useSessionOperations as c } from "./hooks/useSessionOperations.js";
|
|
11
|
+
import { useWorkspaceOperations as l } from "./hooks/useWorkspaceOperations.js";
|
|
12
|
+
import { useContextSwitcherData as u, useProjectSyncEffect as d, useWorkspaceSyncEffect as f } from "./hooks/useContextSwitcherData.js";
|
|
13
|
+
import { useActiveContextBootstrapMirror as p } from "./hooks/useActiveContextBootstrapMirror.js";
|
|
14
|
+
import { useUserOperations as m } from "./hooks/useUserOperations.js";
|
|
15
|
+
import { useTokenRefresh as h } from "./hooks/useTokenRefresh.js";
|
|
16
|
+
import { WorkspaceSelector as g } from "./components/workspace/WorkspaceSelector.js";
|
|
17
|
+
import { UserProfileMenu as _ } from "./components/profile/UserProfileMenu.js";
|
|
18
|
+
import { SSORelayPage as v } from "./pages/SSORelayPage.js";
|
|
19
|
+
import { SSORelayCallbackPage as y } from "./pages/SSORelayCallbackPage.js";
|
|
20
|
+
export { e as AuthRoot, y as SSORelayCallbackPage, v as SSORelayPage, _ as UserProfileMenu, g as WorkspaceSelector, p as useActiveContextBootstrapMirror, i as useAuth, n as useAuthOperations, t as useAuthStore, u as useContextSwitcherData, o as useMfaOperations, r as useMyWorkspaces, s as usePasswordOperations, d as useProjectSyncEffect, c as useSessionOperations, h as useTokenRefresh, m as useUserOperations, l as useWorkspaceOperations, f as useWorkspaceSyncEffect, a as useWorkspaceToken };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
-
import {
|
|
2
|
+
import { formatDateTime as t } from "../utils/formatters.js";
|
|
3
3
|
import { AlertIcon as n, SpinnerIcon as r } from "../components/AuthIcons.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { ConfirmActionDialog as i } from "../components/ConfirmActionDialog.js";
|
|
5
|
+
import { useAuthAuditLogsQuery as a } from "../../generated/global-operations.js";
|
|
6
6
|
import { useCreateAuthExportJobMutation as o } from "../../generated/wspace-operations.js";
|
|
7
7
|
import { useState as s } from "react";
|
|
8
8
|
import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
@@ -10,20 +10,20 @@ import { useFeatureFlagDetails as d } from "@burdenoff/fe-libs/shared/feature-fl
|
|
|
10
10
|
import { isQuotaExhaustedError as f } from "@burdenoff/fe-libs/shared/utils";
|
|
11
11
|
import { EmphasisPanel as p, IllustratedEmptyState as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
import { useGatewayClient as g } from "@burdenoff/fe-libs/shared/graphql";
|
|
13
|
-
|
|
13
|
+
// src/auth/pages/AuditLogPage.tsx
|
|
14
14
|
var _ = 20;
|
|
15
15
|
function v() {
|
|
16
|
-
let v = e(), [y, b] = s(1), x = (y - 1) * _, S = d("auth.audit-log")?.value === !0, { data: C, loading: w, error: T } =
|
|
16
|
+
let v = e(), [y, b] = s(1), x = (y - 1) * _, S = d("auth.audit-log")?.value === !0, { data: C, loading: w, error: T } = a({
|
|
17
17
|
skip: !S,
|
|
18
18
|
fetchPolicy: "cache-and-network",
|
|
19
19
|
variables: { pagination: {
|
|
20
20
|
limit: _,
|
|
21
21
|
offset: x
|
|
22
22
|
} }
|
|
23
|
-
}), E = C?.authAuditLogs ?? [], D = E.length === _,
|
|
24
|
-
|
|
23
|
+
}), E = C?.authAuditLogs ?? [], D = E.length === _, O = g("workspace"), [k, { loading: A }] = o({ client: O }), [j, M] = s(null), [N, P] = s(!1), F = async () => {
|
|
24
|
+
M(null);
|
|
25
25
|
try {
|
|
26
|
-
await
|
|
26
|
+
await k({ variables: { input: {
|
|
27
27
|
name: "Audit Log Export",
|
|
28
28
|
entityTypes: ["permission_audit_logs"],
|
|
29
29
|
format: "CSV",
|
|
@@ -32,12 +32,12 @@ function v() {
|
|
|
32
32
|
} } });
|
|
33
33
|
} catch (e) {
|
|
34
34
|
if (f(e)) {
|
|
35
|
-
|
|
35
|
+
P(!0), M(v("auth.auditLog.exportQuotaReached", "You have reached your export limit for this month."));
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
M(e instanceof Error ? e.message : "Failed to start export");
|
|
39
39
|
}
|
|
40
|
-
},
|
|
40
|
+
}, I = (e) => {
|
|
41
41
|
switch (e) {
|
|
42
42
|
case "LOGIN_SUCCESS": return "OK";
|
|
43
43
|
case "LOGIN_FAILURE": return "ER";
|
|
@@ -59,11 +59,11 @@ function v() {
|
|
|
59
59
|
children: v("auth.auditLog.title", "Security Audit Log")
|
|
60
60
|
}), /* @__PURE__ */ l("button", {
|
|
61
61
|
type: "button",
|
|
62
|
-
onClick: () => void
|
|
63
|
-
disabled:
|
|
62
|
+
onClick: () => void F(),
|
|
63
|
+
disabled: A,
|
|
64
64
|
"aria-label": v("auth.auditLog.exportAriaLabel", "Export audit log"),
|
|
65
65
|
className: "flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium",
|
|
66
|
-
children:
|
|
66
|
+
children: A ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(r, {
|
|
67
67
|
className: "size-4 animate-spin",
|
|
68
68
|
"aria-hidden": "true"
|
|
69
69
|
}), v("auth.auditLog.exporting", "Exporting...")] }) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l("svg", {
|
|
@@ -95,7 +95,7 @@ function v() {
|
|
|
95
95
|
children: v("auth.auditLog.emphasisBody", "Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.")
|
|
96
96
|
})]
|
|
97
97
|
}),
|
|
98
|
-
|
|
98
|
+
j && /* @__PURE__ */ u("div", {
|
|
99
99
|
role: "alert",
|
|
100
100
|
className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
101
101
|
children: [/* @__PURE__ */ l(n, {
|
|
@@ -103,12 +103,12 @@ function v() {
|
|
|
103
103
|
"aria-hidden": "true"
|
|
104
104
|
}), /* @__PURE__ */ l("p", {
|
|
105
105
|
className: "text-sm text-status-error-text",
|
|
106
|
-
children:
|
|
106
|
+
children: j
|
|
107
107
|
})]
|
|
108
108
|
}),
|
|
109
|
-
/* @__PURE__ */ l(
|
|
110
|
-
open:
|
|
111
|
-
onOpenChange:
|
|
109
|
+
/* @__PURE__ */ l(i, {
|
|
110
|
+
open: N,
|
|
111
|
+
onOpenChange: P,
|
|
112
112
|
title: v("auth.auditLog.exportQuotaTitle", "Export Limit Reached"),
|
|
113
113
|
description: v("auth.auditLog.exportQuotaDescription", "You have reached your export limit for this month."),
|
|
114
114
|
confirmText: v("auth.auditLog.exportQuotaViewBilling", "View Billing"),
|
|
@@ -153,7 +153,7 @@ function v() {
|
|
|
153
153
|
/* @__PURE__ */ l("div", {
|
|
154
154
|
className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary",
|
|
155
155
|
"aria-hidden": "true",
|
|
156
|
-
children:
|
|
156
|
+
children: I(e.action)
|
|
157
157
|
}),
|
|
158
158
|
/* @__PURE__ */ u("div", {
|
|
159
159
|
className: "flex-1",
|
|
@@ -166,7 +166,7 @@ function v() {
|
|
|
166
166
|
className: "text-sm text-text-secondary mb-2",
|
|
167
167
|
children: /* @__PURE__ */ l("time", {
|
|
168
168
|
dateTime: e.createdAt,
|
|
169
|
-
children:
|
|
169
|
+
children: t(e.createdAt)
|
|
170
170
|
})
|
|
171
171
|
}),
|
|
172
172
|
/* @__PURE__ */ u("p", {
|
|
@@ -221,7 +221,6 @@ function v() {
|
|
|
221
221
|
})
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
|
-
//#endregion
|
|
225
224
|
export { v as AuditLogPage };
|
|
226
225
|
|
|
227
226
|
//# sourceMappingURL=AuditLogPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditLogPage.js","names":[],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { formatDateTime } from \"../utils/formatters\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport {\n useAuthAuditLogsQuery,\n type AuditLogEntry_AuthAuditLogFragment,\n} from \"../../generated/global-operations\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\n\nconst PAGE_SIZE = 20;\n\n/**\n * Security audit log page with pagination and export\n *\n * Fragment: AuditLogEntry_AuthAuditLog (graphql/operations/global/fragments/AuditLogEntry.fragment.graphql)\n * Query: AuthAuditLogs (graphql/operations/global/queries/auditLog.graphql)\n *\n * TODO: When authAuditLogs backend migrates to AuthAuditLogConnection type,\n * update this query to use: authAuditLogs { auditLogs: [...], totalCount, hasMore }\n * and remove the client-side hasMore approximation below.\n */\nexport function AuditLogPage() {\n const tr = useTr();\n\n const [page, setPage] = useState(1);\n const offset = (page - 1) * PAGE_SIZE;\n\n // Default-deny: only show when the flag is explicitly enabled in the system.\n // useFeatureFlag uses default-allow (returns true when not found), so we use\n // useFeatureFlagDetails + explicit value check instead.\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n\n const { data, loading, error } = useAuthAuditLogsQuery({\n skip: !showAuditLog,\n fetchPolicy: \"cache-and-network\",\n variables: { pagination: { limit: PAGE_SIZE, offset } },\n });\n\n const auditLogs = data?.authAuditLogs ?? [];\n // Approximate hasMore: if we received a full page there may be more\n const hasMore = auditLogs.length === PAGE_SIZE;\n\n const wspaceClient = useGatewayClient(\"workspace\");\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation({ client: wspaceClient });\n const [exportError, setExportError] = useState<string | null>(null);\n const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Audit Log Export\",\n // permission_audit_logs covers auth audit logs in the export system\n entityTypes: [\"permission_audit_logs\"],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.auditLog.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n const getEventMarker = (eventType: string) => {\n switch (eventType) {\n case \"LOGIN_SUCCESS\":\n return \"OK\";\n case \"LOGIN_FAILURE\":\n return \"ER\";\n case \"LOGOUT\":\n return \"LO\";\n case \"PASSWORD_CHANGE\":\n return \"PW\";\n case \"MFA_ENABLED\":\n case \"MFA_VERIFIED\":\n return \"MF\";\n case \"ANOMALY_DETECTED\":\n return \"AL\";\n default:\n return \"AU\";\n }\n };\n\n if (!showAuditLog) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n description={tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.auditLog.title\", \"Security Audit Log\")}\n </h1>\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.auditLog.exportAriaLabel\", \"Export audit log\")}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.auditLog.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.auditLog.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.auditLog.purpose\",\n \"A running record of security-relevant events on your account — sign-ins, password and MFA changes, and anomalies. Scan it to spot anything you didn't do, and export it for your own records or compliance.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: what's tracked — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.auditLog.emphasisTitle\", \"What this log captures\")}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.auditLog.emphasisBody\",\n \"Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.\",\n )}\n </p>\n </EmphasisPanel>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\"auth.auditLog.exportQuotaTitle\", \"Export Limit Reached\")}\n description={tr(\n \"auth.auditLog.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\"auth.auditLog.exportQuotaViewBilling\", \"View Billing\")}\n cancelText={tr(\"auth.auditLog.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\n />\n\n {loading ? (\n <div aria-live=\"polite\" aria-busy=\"true\" className=\"text-center py-12\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-3 text-text-secondary\">\n {tr(\"auth.auditLog.loading\", \"Loading audit events...\")}\n </p>\n </div>\n ) : error ? (\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n description={tr(\n \"auth.auditLog.errorDesc\",\n \"Something went wrong fetching your security events. Please try again in a moment.\",\n )}\n />\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n description={tr(\n \"auth.auditLog.noEventsDesc\",\n \"As you sign in and change security settings, those events will appear here.\",\n )}\n />\n </output>\n ) : (\n <>\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={loading}>\n {auditLogs.map((event: AuditLogEntry_AuthAuditLogFragment) => (\n <div\n key={event.id}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {getEventMarker(event.action)}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"text-lg font-medium text-text-primary mb-1\">\n {event.action.replace(/_/g, \" \")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-2\">\n <time dateTime={event.createdAt}>\n {formatDateTime(event.createdAt)}\n </time>\n </p>\n <p className=\"text-sm text-text-secondary\">\n {event.description ??\n tr(\n \"auth.auditLog.defaultDescription\",\n \"Authentication event\",\n )}\n {event.ipAddress && ` • ${event.ipAddress}`}\n </p>\n </div>\n {event.action.includes(\"FAILURE\") && (\n <output className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\">\n {tr(\"auth.auditLog.failed\", \"Failed\")}\n </output>\n )}\n </div>\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <nav\n aria-label={tr(\n \"auth.auditLog.paginationAriaLabel\",\n \"Audit log pagination\",\n )}\n className=\"mt-6 flex items-center justify-between\"\n >\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.auditLog.page\", \"Page\")} {page}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((prev) => Math.max(1, prev - 1))}\n disabled={page === 1}\n aria-label={tr(\n \"auth.auditLog.previousPageAriaLabel\",\n \"Go to previous page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.previous\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.auditLog.nextPageAriaLabel\",\n \"Go to next page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.next\", \"Next\")}\n </button>\n </div>\n </nav>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuditLogPage.js","names":["_Fragment","PAGE_SIZE","AuditLogPage","tr","useTr","page","setPage","useState","offset","showAuditLog","useFeatureFlagDetails","useAuthAuditLogsQuery","auditLogs","data","hasMore","wspaceClient","useGatewayClient","createExportJob","isExporting","useCreateAuthExportJobMutation","exportError","setExportError","showExportQuotaDialog","setShowExportQuotaDialog","handleExport","err","isQuotaExhaustedError","getEventMarker","eventType","Fragment","SpinnerIcon","PagePurpose","EmphasisPanel","AlertIcon","ConfirmActionDialog","loading","error","IllustratedEmptyState","event","formatDateTime","prev"],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { formatDateTime } from \"../utils/formatters\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport {\n useAuthAuditLogsQuery,\n type AuditLogEntry_AuthAuditLogFragment,\n} from \"../../generated/global-operations\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\n\nconst PAGE_SIZE = 20;\n\n/**\n * Security audit log page with pagination and export\n *\n * Fragment: AuditLogEntry_AuthAuditLog (graphql/operations/global/fragments/AuditLogEntry.fragment.graphql)\n * Query: AuthAuditLogs (graphql/operations/global/queries/auditLog.graphql)\n *\n * TODO: When authAuditLogs backend migrates to AuthAuditLogConnection type,\n * update this query to use: authAuditLogs { auditLogs: [...], totalCount, hasMore }\n * and remove the client-side hasMore approximation below.\n */\nexport function AuditLogPage() {\n const tr = useTr();\n\n const [page, setPage] = useState(1);\n const offset = (page - 1) * PAGE_SIZE;\n\n // Default-deny: only show when the flag is explicitly enabled in the system.\n // useFeatureFlag uses default-allow (returns true when not found), so we use\n // useFeatureFlagDetails + explicit value check instead.\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n\n const { data, loading, error } = useAuthAuditLogsQuery({\n skip: !showAuditLog,\n fetchPolicy: \"cache-and-network\",\n variables: { pagination: { limit: PAGE_SIZE, offset } },\n });\n\n const auditLogs = data?.authAuditLogs ?? [];\n // Approximate hasMore: if we received a full page there may be more\n const hasMore = auditLogs.length === PAGE_SIZE;\n\n const wspaceClient = useGatewayClient(\"workspace\");\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation({ client: wspaceClient });\n const [exportError, setExportError] = useState<string | null>(null);\n const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Audit Log Export\",\n // permission_audit_logs covers auth audit logs in the export system\n entityTypes: [\"permission_audit_logs\"],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.auditLog.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n const getEventMarker = (eventType: string) => {\n switch (eventType) {\n case \"LOGIN_SUCCESS\":\n return \"OK\";\n case \"LOGIN_FAILURE\":\n return \"ER\";\n case \"LOGOUT\":\n return \"LO\";\n case \"PASSWORD_CHANGE\":\n return \"PW\";\n case \"MFA_ENABLED\":\n case \"MFA_VERIFIED\":\n return \"MF\";\n case \"ANOMALY_DETECTED\":\n return \"AL\";\n default:\n return \"AU\";\n }\n };\n\n if (!showAuditLog) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n description={tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.auditLog.title\", \"Security Audit Log\")}\n </h1>\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.auditLog.exportAriaLabel\", \"Export audit log\")}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.auditLog.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.auditLog.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.auditLog.purpose\",\n \"A running record of security-relevant events on your account — sign-ins, password and MFA changes, and anomalies. Scan it to spot anything you didn't do, and export it for your own records or compliance.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: what's tracked — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.auditLog.emphasisTitle\", \"What this log captures\")}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.auditLog.emphasisBody\",\n \"Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.\",\n )}\n </p>\n </EmphasisPanel>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\"auth.auditLog.exportQuotaTitle\", \"Export Limit Reached\")}\n description={tr(\n \"auth.auditLog.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\"auth.auditLog.exportQuotaViewBilling\", \"View Billing\")}\n cancelText={tr(\"auth.auditLog.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\n />\n\n {loading ? (\n <div aria-live=\"polite\" aria-busy=\"true\" className=\"text-center py-12\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-3 text-text-secondary\">\n {tr(\"auth.auditLog.loading\", \"Loading audit events...\")}\n </p>\n </div>\n ) : error ? (\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n description={tr(\n \"auth.auditLog.errorDesc\",\n \"Something went wrong fetching your security events. Please try again in a moment.\",\n )}\n />\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n description={tr(\n \"auth.auditLog.noEventsDesc\",\n \"As you sign in and change security settings, those events will appear here.\",\n )}\n />\n </output>\n ) : (\n <>\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={loading}>\n {auditLogs.map((event: AuditLogEntry_AuthAuditLogFragment) => (\n <div\n key={event.id}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {getEventMarker(event.action)}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"text-lg font-medium text-text-primary mb-1\">\n {event.action.replace(/_/g, \" \")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-2\">\n <time dateTime={event.createdAt}>\n {formatDateTime(event.createdAt)}\n </time>\n </p>\n <p className=\"text-sm text-text-secondary\">\n {event.description ??\n tr(\n \"auth.auditLog.defaultDescription\",\n \"Authentication event\",\n )}\n {event.ipAddress && ` • ${event.ipAddress}`}\n </p>\n </div>\n {event.action.includes(\"FAILURE\") && (\n <output className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\">\n {tr(\"auth.auditLog.failed\", \"Failed\")}\n </output>\n )}\n </div>\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <nav\n aria-label={tr(\n \"auth.auditLog.paginationAriaLabel\",\n \"Audit log pagination\",\n )}\n className=\"mt-6 flex items-center justify-between\"\n >\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.auditLog.page\", \"Page\")} {page}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((prev) => Math.max(1, prev - 1))}\n disabled={page === 1}\n aria-label={tr(\n \"auth.auditLog.previousPageAriaLabel\",\n \"Go to previous page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.previous\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.auditLog.nextPageAriaLabel\",\n \"Go to next page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.next\", \"Next\")}\n </button>\n </div>\n </nav>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,61 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { useAuthOperations as t } from "../hooks/useAuthOperations.js";
|
|
3
|
+
import { useAuthStore as n } from "../store/authStore.js";
|
|
4
4
|
import { AuthLayout as r } from "../components/AuthLayout.js";
|
|
5
5
|
import { SpinnerIcon as i } from "../components/AuthIcons.js";
|
|
6
6
|
import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
|
|
7
7
|
import { useSearchParams as l } from "react-router";
|
|
8
8
|
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
9
|
import { Button as p } from "@burdenoff/fe-libs/button";
|
|
10
|
-
|
|
11
|
-
var m = 5;
|
|
12
|
-
function
|
|
10
|
+
// src/auth/pages/EmailVerifyPage.tsx
|
|
11
|
+
var m = 5, h = /* @__PURE__ */ new Map();
|
|
12
|
+
function g(e) {
|
|
13
13
|
let t = "/auth/login", n = e ? `${t}?email=${encodeURIComponent(e)}` : t;
|
|
14
14
|
window.location.href = n;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
let
|
|
16
|
+
function _() {
|
|
17
|
+
let _ = e(), [v] = l(), y = v.get("token"), b = v.get("email"), { verifyEmail: x, resendVerification: S } = t(), C = n((e) => e.user), [w, T] = c("verifying"), E = s(b || void 0), [D, O] = c(!1), [k, A] = c(!1), [j, M] = c(m), N = s(null);
|
|
18
18
|
o(() => {
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
19
|
+
if (!y) return;
|
|
20
|
+
try {
|
|
21
|
+
window.sessionStorage.removeItem(`auth:verifyEmail:inflight:${y}`);
|
|
22
|
+
} catch {}
|
|
23
|
+
if (N.current?.token !== y) {
|
|
24
|
+
let e = h.get(y);
|
|
25
|
+
e || (e = Promise.resolve().then(() => x(y)).finally(() => h.delete(y)), h.set(y, e)), N.current = {
|
|
26
|
+
token: y,
|
|
27
|
+
request: e
|
|
28
|
+
}, T("verifying");
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
let e = !0;
|
|
31
|
+
return N.current.request.then((t) => {
|
|
32
|
+
e && (t.success ? (T("success"), t.email && (E.current = t.email)) : T("error"));
|
|
33
|
+
}).catch(() => {
|
|
34
|
+
e && T("error");
|
|
35
|
+
}), () => {
|
|
36
|
+
e = !1;
|
|
37
|
+
};
|
|
38
|
+
}, [y, x]), o(() => {
|
|
39
|
+
if (w !== "success") return;
|
|
32
40
|
let e = setInterval(() => {
|
|
33
|
-
|
|
41
|
+
M((t) => t <= 1 ? (clearInterval(e), g(E.current), 0) : t - 1);
|
|
34
42
|
}, 1e3);
|
|
35
43
|
return () => clearInterval(e);
|
|
36
|
-
}, [
|
|
37
|
-
let
|
|
38
|
-
let e =
|
|
39
|
-
if (!e)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
D(!1);
|
|
44
|
+
}, [w]);
|
|
45
|
+
let P = async () => {
|
|
46
|
+
let e = b || C?.email;
|
|
47
|
+
if (!e) console.error("No email available for resend");
|
|
48
|
+
else {
|
|
49
|
+
O(!0);
|
|
50
|
+
try {
|
|
51
|
+
await S(e), A(!0);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error("Failed to resend verification:", e);
|
|
54
|
+
} finally {
|
|
55
|
+
O(!1);
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
}, []),
|
|
54
|
-
|
|
58
|
+
}, F = a(() => {
|
|
59
|
+
g(E.current);
|
|
60
|
+
}, []), I = a(() => {
|
|
61
|
+
g();
|
|
55
62
|
}, []);
|
|
56
|
-
return
|
|
57
|
-
title:
|
|
58
|
-
subtitle:
|
|
63
|
+
return w === "verifying" ? /* @__PURE__ */ d(r, {
|
|
64
|
+
title: _("auth.emailVerify.verifyingTitle", "Verifying your email"),
|
|
65
|
+
subtitle: _("auth.emailVerify.verifyingSubtitle", "Please wait while we confirm your email address"),
|
|
59
66
|
children: /* @__PURE__ */ f("div", {
|
|
60
67
|
className: "flex flex-col items-center justify-center py-12",
|
|
61
68
|
children: [/* @__PURE__ */ d("div", {
|
|
@@ -63,12 +70,12 @@ function g() {
|
|
|
63
70
|
children: /* @__PURE__ */ d(i, { className: "size-8 text-[var(--color-text-link)] animate-spin" })
|
|
64
71
|
}), /* @__PURE__ */ d("p", {
|
|
65
72
|
className: "text-[var(--color-text-secondary)]",
|
|
66
|
-
children:
|
|
73
|
+
children: _("auth.emailVerify.moment", "This will only take a moment...")
|
|
67
74
|
})]
|
|
68
75
|
})
|
|
69
|
-
}) :
|
|
70
|
-
title:
|
|
71
|
-
subtitle:
|
|
76
|
+
}) : w === "success" ? /* @__PURE__ */ d(r, {
|
|
77
|
+
title: _("auth.emailVerify.verifiedTitle", "Email verified!"),
|
|
78
|
+
subtitle: _("auth.emailVerify.verifiedSubtitle", "Your email address has been confirmed"),
|
|
72
79
|
children: /* @__PURE__ */ f("div", {
|
|
73
80
|
className: "flex flex-col items-center justify-center py-6",
|
|
74
81
|
children: [
|
|
@@ -89,34 +96,34 @@ function g() {
|
|
|
89
96
|
}),
|
|
90
97
|
/* @__PURE__ */ d("p", {
|
|
91
98
|
className: "text-center text-[var(--color-text-secondary)] mb-6",
|
|
92
|
-
children:
|
|
99
|
+
children: _("auth.emailVerify.successMessage", "Your email has been successfully verified. You can now sign in to access all features.")
|
|
93
100
|
}),
|
|
94
101
|
/* @__PURE__ */ f("p", {
|
|
95
102
|
className: "text-center text-sm text-[var(--color-text-tertiary)] mb-6",
|
|
96
103
|
children: [
|
|
97
|
-
|
|
104
|
+
_("auth.emailVerify.redirecting", "Redirecting to sign in in"),
|
|
98
105
|
" ",
|
|
99
106
|
/* @__PURE__ */ d("span", {
|
|
100
107
|
className: "font-semibold text-[var(--color-text-primary)]",
|
|
101
|
-
children:
|
|
108
|
+
children: j
|
|
102
109
|
}),
|
|
103
110
|
" ",
|
|
104
|
-
|
|
111
|
+
_("auth.emailVerify.seconds", "seconds...")
|
|
105
112
|
]
|
|
106
113
|
}),
|
|
107
114
|
/* @__PURE__ */ d(p, {
|
|
108
115
|
type: "button",
|
|
109
|
-
onClick:
|
|
116
|
+
onClick: F,
|
|
110
117
|
variant: "default",
|
|
111
118
|
size: "lg",
|
|
112
119
|
className: "w-full font-semibold",
|
|
113
|
-
children:
|
|
120
|
+
children: _("auth.emailVerify.continueSignIn", "Continue to sign in now")
|
|
114
121
|
})
|
|
115
122
|
]
|
|
116
123
|
})
|
|
117
124
|
}) : /* @__PURE__ */ d(r, {
|
|
118
|
-
title:
|
|
119
|
-
subtitle:
|
|
125
|
+
title: _("auth.emailVerify.failedTitle", "Verification failed"),
|
|
126
|
+
subtitle: _("auth.emailVerify.failedSubtitle", "We couldn't verify your email address"),
|
|
120
127
|
children: /* @__PURE__ */ f("div", {
|
|
121
128
|
className: "flex flex-col items-center justify-center py-6",
|
|
122
129
|
children: [
|
|
@@ -137,34 +144,33 @@ function g() {
|
|
|
137
144
|
}),
|
|
138
145
|
/* @__PURE__ */ d("p", {
|
|
139
146
|
className: "text-center text-[var(--color-text-secondary)] mb-8",
|
|
140
|
-
children:
|
|
147
|
+
children: _("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
|
|
141
148
|
}),
|
|
142
|
-
|
|
149
|
+
k ? /* @__PURE__ */ d("div", {
|
|
143
150
|
className: "w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4",
|
|
144
151
|
children: /* @__PURE__ */ d("p", {
|
|
145
152
|
className: "text-sm text-[var(--color-status-success-text)]",
|
|
146
|
-
children:
|
|
153
|
+
children: _("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
|
|
147
154
|
})
|
|
148
155
|
}) : /* @__PURE__ */ d(p, {
|
|
149
156
|
type: "button",
|
|
150
|
-
onClick:
|
|
151
|
-
disabled:
|
|
157
|
+
onClick: P,
|
|
158
|
+
disabled: D,
|
|
152
159
|
variant: "default",
|
|
153
160
|
size: "lg",
|
|
154
161
|
className: "mb-4 w-full font-semibold",
|
|
155
|
-
children:
|
|
162
|
+
children: D ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(i, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: _("auth.emailVerify.sending", "Sending…") })] }) : _("auth.emailVerify.resend", "Resend verification email")
|
|
156
163
|
}),
|
|
157
164
|
/* @__PURE__ */ d("button", {
|
|
158
165
|
type: "button",
|
|
159
|
-
onClick:
|
|
166
|
+
onClick: I,
|
|
160
167
|
className: "w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all",
|
|
161
|
-
children:
|
|
168
|
+
children: _("auth.emailVerify.backToSignIn", "Back to sign in")
|
|
162
169
|
})
|
|
163
170
|
]
|
|
164
171
|
})
|
|
165
172
|
});
|
|
166
173
|
}
|
|
167
|
-
|
|
168
|
-
export { g as EmailVerifyPage };
|
|
174
|
+
export { _ as EmailVerifyPage };
|
|
169
175
|
|
|
170
176
|
//# sourceMappingURL=EmailVerifyPage.js.map
|