@vertesia/ui 1.5.0-dev.20260804.124748Z → 1.5.0-dev.20260807.073259Z
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/lib/core/components/ComboBox.d.ts.map +1 -1
- package/lib/core/components/ComboBox.js +4 -4
- package/lib/core/components/ComboBox.js.map +1 -1
- package/lib/core/components/FileUpload.js.map +1 -1
- package/lib/core/components/MenuList.d.ts +2 -1
- package/lib/core/components/MenuList.d.ts.map +1 -1
- package/lib/core/components/TagsInput.js +1 -1
- package/lib/core/components/popup/PopupController.d.ts.map +1 -1
- package/lib/core/components/popup/PopupController.js.map +1 -1
- package/lib/core/components/popup/position.js.map +1 -1
- package/lib/core/components/popup/utils.js.map +1 -1
- package/lib/core/components/shadcn/LanguageSwitcher.js.map +1 -1
- package/lib/core/components/shadcn/MessageBox.js.map +1 -1
- package/lib/core/components/shadcn/Panel.js +1 -1
- package/lib/core/components/shadcn/badge.d.ts +2 -2
- package/lib/core/components/shadcn/button.d.ts +2 -2
- package/lib/core/components/shadcn/button.js +1 -1
- package/lib/core/components/shadcn/command.d.ts +4 -4
- package/lib/core/components/shadcn/command.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/DynamicLabel.js +1 -1
- package/lib/core/components/shadcn/filters/comboBox/DateCombobox.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js +1 -1
- package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js +1 -1
- package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/TextCombobox.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/filter/dateFilter.js +1 -1
- package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/filters.js.map +1 -1
- package/lib/core/components/shadcn/input.d.ts +2 -2
- package/lib/core/components/shadcn/modal/dialog.d.ts.map +1 -1
- package/lib/core/components/shadcn/popover.d.ts +1 -1
- package/lib/core/components/shadcn/popover.d.ts.map +1 -1
- package/lib/core/components/shadcn/popover.js.map +1 -1
- package/lib/core/components/shadcn/radioGroup.d.ts.map +1 -1
- package/lib/core/components/shadcn/selectBox.js +1 -1
- package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/core/components/shadcn/tabs.js +1 -1
- package/lib/core/components/shadcn/text.d.ts +2 -2
- package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
- package/lib/core/components/toast/ToastProvider.js +1 -1
- package/lib/core/hooks/CompositeState.d.ts +1 -1
- package/lib/core/hooks/CompositeState.d.ts.map +1 -1
- package/lib/core/hooks/CompositeState.js.map +1 -1
- package/lib/core/hooks/SharedState.d.ts.map +1 -1
- package/lib/core/hooks/useFetch.d.ts.map +1 -1
- package/lib/core/hooks/useFetch.js +2 -1
- package/lib/core/hooks/useFetch.js.map +1 -1
- package/lib/core/hooks/useFlag.d.ts.map +1 -1
- package/lib/core/hooks/useIntersectionObserver.js.map +1 -1
- package/lib/core/hooks/useSafeLayoutEffect.d.ts +2 -2
- package/lib/core/hooks/useSafeLayoutEffect.d.ts.map +1 -1
- package/lib/core/hooks/useScrollableSearch.js.map +1 -1
- package/lib/core/hooks/useSharedValue.d.ts.map +1 -1
- package/lib/core/hooks/useSharedValue.js.map +1 -1
- package/lib/core/utils/a11y.d.ts.map +1 -1
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/PayloadBuilder.d.ts +1 -1
- package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
- package/lib/features/agent/PayloadBuilder.js.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.js +1 -1
- package/lib/features/agent/chat/AskUserWidget.js +1 -1
- package/lib/features/agent/chat/DocumentEditingActionCard.js +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +26 -3
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts +7 -0
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +25 -0
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/BatchProgressPanel.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SlideInPanel.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/StreamingMessage.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +8 -0
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
- package/lib/features/agent/chat/SlidingThinkingIndicator.js +1 -1
- package/lib/features/agent/chat/VegaLiteChart.js +1 -1
- package/lib/features/agent/chat/VegaLiteChart.js.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentStream.js +26 -0
- package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
- package/lib/features/agent/chat/hooks/useFileProcessing.d.ts +8 -1
- package/lib/features/agent/chat/hooks/useFileProcessing.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useFileProcessing.js +31 -2
- package/lib/features/agent/chat/hooks/useFileProcessing.js.map +1 -1
- package/lib/features/agent/chat/useArtifactUrlCache.js.map +1 -1
- package/lib/features/agent/chat/workstreams.d.ts +1 -1
- package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/DocumentsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/InteractionsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/PromptsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/RunsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/WorkflowExecutionsFacetsNav.d.ts.map +1 -1
- package/lib/features/magic-pdf/GroundedExtractionView.js +1 -1
- package/lib/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -1
- package/lib/features/magic-pdf/MagicPdfProvider.js.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.js +1 -1
- package/lib/features/oauth/OAuthProviderConnectButton.js +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
- package/lib/features/pdf-viewer/PdfPageRenderer.js +1 -1
- package/lib/features/pdf-viewer/PdfPageSlider.js +1 -1
- package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
- package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
- package/lib/features/store/collections/CollectionsTable.js +2 -2
- package/lib/features/store/collections/CollectionsTable.js.map +1 -1
- package/lib/features/store/collections/CreateCollection.js +1 -1
- package/lib/features/store/collections/EditCollectionView.js +1 -1
- package/lib/features/store/objects/DocumentQuickFilter.js +1 -1
- package/lib/features/store/objects/DocumentSelectionProvider.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentSelectionProvider.js.map +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.d.ts +2 -1
- package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.js +1 -1
- package/lib/features/store/objects/components/DocumentEditingPanel.js +1 -1
- package/lib/features/store/objects/components/PropertiesEditorModal.js +1 -1
- package/lib/features/store/objects/components/TextEditorPanel.js +1 -1
- package/lib/features/store/objects/components/documentEditingTarget.d.ts.map +1 -1
- package/lib/features/store/objects/components/useContentPanelHooks.d.ts +2 -2
- package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
- package/lib/features/store/objects/layout/DocumentTableColumn.d.ts +1 -1
- package/lib/features/store/objects/layout/DocumentTableColumn.d.ts.map +1 -1
- package/lib/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
- package/lib/features/store/objects/layout/documentLayout.js +1 -1
- package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
- package/lib/features/store/objects/search/DocumentSearchContext.d.ts +25 -25
- package/lib/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
- package/lib/features/store/objects/search/DocumentSearchContext.js.map +1 -1
- package/lib/features/store/objects/selection/ObjectsActionContextClass.d.ts.map +1 -1
- package/lib/features/store/objects/selection/ObjectsActionContextClass.js.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js +1 -1
- package/lib/features/store/objects/selection/actions/ConfirmAction.js +1 -1
- package/lib/features/store/objects/selection/actions/DeleteObjectsAction.js +1 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +1 -1
- package/lib/features/store/objects/upload/DocumentUploadModal.js +1 -1
- package/lib/features/store/objects/upload/useSmartFileUploadProcessing.d.ts.map +1 -1
- package/lib/features/store/objects/upload/useUploadHandler.d.ts.map +1 -1
- package/lib/features/store/types/ContentObjectTypesSearch.js +1 -1
- package/lib/features/store/types/IntakePolicyEditor.js +1 -1
- package/lib/features/store/types/TypeRegistry.d.ts +240 -180
- package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
- package/lib/features/store/types/TypeRegistry.js.map +1 -1
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +239 -179
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
- package/lib/features/store/types/search/ObjectTypeSearchContext.js.map +1 -1
- package/lib/features/user/UserInfo.d.ts +14 -14
- package/lib/features/user/UserInfo.js +1 -1
- package/lib/features/user/UserInfo.js.map +1 -1
- package/lib/features/utils/mimeType.d.ts.map +1 -1
- package/lib/features/utils/rendition.js.map +1 -1
- package/lib/features/utils/text.js.map +1 -1
- package/lib/i18n/locales/ar.json +25 -0
- package/lib/i18n/locales/de.json +25 -0
- package/lib/i18n/locales/en.json +25 -0
- package/lib/i18n/locales/es.json +25 -0
- package/lib/i18n/locales/fr.json +25 -0
- package/lib/i18n/locales/it.json +25 -0
- package/lib/i18n/locales/ja.json +25 -0
- package/lib/i18n/locales/ko.json +25 -0
- package/lib/i18n/locales/pt.json +25 -0
- package/lib/i18n/locales/ru.json +25 -0
- package/lib/i18n/locales/tr.json +25 -0
- package/lib/i18n/locales/zh-TW.json +25 -0
- package/lib/i18n/locales/zh.json +25 -0
- package/lib/i18n/rtl.d.ts +1 -1
- package/lib/i18n/rtl.d.ts.map +1 -1
- package/lib/layout/FullHeightLayout.d.ts +7 -6
- package/lib/layout/FullHeightLayout.d.ts.map +1 -1
- package/lib/layout/Sidebar.d.ts +1 -1
- package/lib/layout/Sidebar.js +1 -1
- package/lib/rich-text/EditorToolbar.js +1 -1
- package/lib/router/HistoryNavigator.d.ts +2 -2
- package/lib/router/HistoryNavigator.d.ts.map +1 -1
- package/lib/router/HistoryNavigator.js.map +1 -1
- package/lib/router/PathMatcher.d.ts.map +1 -1
- package/lib/router/PathMatcher.js.map +1 -1
- package/lib/router/PathWithParams.d.ts.map +1 -1
- package/lib/router/Redirect.js.map +1 -1
- package/lib/router/Route404.js +1 -1
- package/lib/router/Router.d.ts.map +1 -1
- package/lib/router/Router.js.map +1 -1
- package/lib/session/UserSession.d.ts +1 -1
- package/lib/session/UserSession.d.ts.map +1 -1
- package/lib/session/UserSession.js.map +1 -1
- package/lib/session/UserSessionProvider.d.ts +2 -0
- package/lib/session/UserSessionProvider.d.ts.map +1 -1
- package/lib/session/UserSessionProvider.js +81 -53
- package/lib/session/UserSessionProvider.js.map +1 -1
- package/lib/session/auth/composable.d.ts +49 -1
- package/lib/session/auth/composable.d.ts.map +1 -1
- package/lib/session/auth/composable.js +179 -53
- package/lib/session/auth/composable.js.map +1 -1
- package/lib/shell/VertesiaShell.js +1 -1
- package/lib/shell/apps/AppInstallationProvider.d.ts +58 -50
- package/lib/shell/apps/AppInstallationProvider.d.ts.map +1 -1
- package/lib/shell/apps/AppProjectSelector.js +1 -1
- package/lib/shell/login/SignInEmailStep.js +1 -1
- package/lib/shell/login/SignInPrimitives.d.ts +21 -20
- package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
- package/lib/shell/login/SignInPrimitives.js.map +1 -1
- package/lib/shell/login/SignInRecoveryStep.d.ts +22 -0
- package/lib/shell/login/SignInRecoveryStep.d.ts.map +1 -0
- package/lib/shell/login/SignInRecoveryStep.js +59 -0
- package/lib/shell/login/SignInRecoveryStep.js.map +1 -0
- package/lib/shell/login/SigninScreen.d.ts.map +1 -1
- package/lib/shell/login/SigninScreen.js +84 -11
- package/lib/shell/login/SigninScreen.js.map +1 -1
- package/lib/shell/login/SignupForm.js +1 -1
- package/lib/shell/login/TerminalLogin.js +1 -1
- package/lib/shell/login/UserSessionMenu.js +1 -1
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-features.js +26 -26
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.js +13 -13
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js.map +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +1 -1
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/form/FormContext.d.ts.map +1 -1
- package/lib/widgets/form/FormContext.js.map +1 -1
- package/lib/widgets/form/ManagedObject.d.ts +2 -2
- package/lib/widgets/form/ManagedObject.d.ts.map +1 -1
- package/lib/widgets/form/ManagedObject.js.map +1 -1
- package/lib/widgets/form/schema.d.ts.map +1 -1
- package/lib/widgets/form/schema.js.map +1 -1
- package/lib/widgets/json-view/JSONCode.js +1 -1
- package/lib/widgets/json-view/JSONTable.js +1 -1
- package/lib/widgets/markdown/ArtifactContentRenderer.js.map +1 -1
- package/lib/widgets/markdown/CodeBlockPlaceholder.d.ts +1 -1
- package/lib/widgets/markdown/CodeBlockPlaceholder.d.ts.map +1 -1
- package/lib/widgets/markdown/CodeBlockPlaceholder.js +1 -1
- package/lib/widgets/markdown/CodeBlockRendering.d.ts.map +1 -1
- package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js +1 -1
- package/lib/widgets/markdown/remarkDirectiveHandler.d.ts.map +1 -1
- package/lib/widgets/markdown/textDiff.js.map +1 -1
- package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
- package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
- package/lib/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
- package/lib/widgets/schema-editor/editor/PropertyEditor.js +1 -1
- package/lib/widgets/schema-editor/json-schema4-utils.js.map +1 -1
- package/lib/widgets/xml-viewer/components/Elements/index.js +1 -1
- package/lib/widgets/xml-viewer/components/Tag/index.js +1 -1
- package/lib/widgets/xml-viewer/constants/index.d.ts.map +1 -1
- package/lib/widgets/xml-viewer/helpers/index.d.ts +1 -1
- package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
- package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/core/hooks/useFetch.ts +2 -1
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +13 -0
- package/src/features/agent/chat/ModernAgentConversation.tsx +24 -3
- package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.test.ts +38 -0
- package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +26 -0
- package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +9 -0
- package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +21 -0
- package/src/features/agent/chat/hooks/useAgentStream.test.tsx +116 -1
- package/src/features/agent/chat/hooks/useAgentStream.ts +26 -0
- package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +87 -0
- package/src/features/agent/chat/hooks/useFileProcessing.ts +32 -1
- package/src/features/store/collections/CollectionsTable.tsx +1 -1
- package/src/i18n/locales/ar.json +25 -0
- package/src/i18n/locales/de.json +25 -0
- package/src/i18n/locales/en.json +25 -0
- package/src/i18n/locales/es.json +25 -0
- package/src/i18n/locales/fr.json +25 -0
- package/src/i18n/locales/it.json +25 -0
- package/src/i18n/locales/ja.json +25 -0
- package/src/i18n/locales/ko.json +25 -0
- package/src/i18n/locales/pt.json +25 -0
- package/src/i18n/locales/ru.json +25 -0
- package/src/i18n/locales/tr.json +25 -0
- package/src/i18n/locales/zh-TW.json +25 -0
- package/src/i18n/locales/zh.json +25 -0
- package/src/session/UserSessionProvider.test.ts +69 -0
- package/src/session/UserSessionProvider.tsx +96 -60
- package/src/session/auth/composable.scope.test.ts +88 -0
- package/src/session/auth/composable.test.ts +205 -0
- package/src/session/auth/composable.ts +227 -58
- package/src/shell/login/SignInPrimitives.tsx +1 -0
- package/src/shell/login/SignInRecoveryStep.test.tsx +112 -0
- package/src/shell/login/SignInRecoveryStep.tsx +177 -0
- package/src/shell/login/SigninScreen.tsx +145 -27
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Handle client caching and refresh of auth token
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type AuthTokenPayload,
|
|
6
|
+
NO_ACCESSIBLE_ACCOUNT_ERROR_CODE,
|
|
7
|
+
REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,
|
|
8
|
+
RESTRICTED_ENVIRONMENT_ERROR_CODE,
|
|
9
|
+
} from '@vertesia/common';
|
|
5
10
|
import { Env } from '@vertesia/ui/env';
|
|
6
11
|
import { jwtDecode } from 'jwt-decode';
|
|
7
12
|
import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from '../constants';
|
|
@@ -10,6 +15,27 @@ import { getFirebaseAuth, getFirebaseAuthToken } from './firebase';
|
|
|
10
15
|
let AUTH_TOKEN_RAW: string | undefined;
|
|
11
16
|
let AUTH_TOKEN: AuthTokenPayload | undefined;
|
|
12
17
|
|
|
18
|
+
function clearRejectedPersistedScope(accountId?: string, projectId?: string) {
|
|
19
|
+
if (!accountId) return;
|
|
20
|
+
|
|
21
|
+
const projectKey = `${LastSelectedProjectId_KEY}-${accountId}`;
|
|
22
|
+
if (projectId) {
|
|
23
|
+
const persistedProjectMatches = localStorage.getItem(projectKey) === projectId;
|
|
24
|
+
if (persistedProjectMatches) {
|
|
25
|
+
localStorage.removeItem(projectKey);
|
|
26
|
+
}
|
|
27
|
+
if (persistedProjectMatches && localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {
|
|
28
|
+
localStorage.removeItem(LastSelectedAccountId_KEY);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {
|
|
34
|
+
localStorage.removeItem(LastSelectedAccountId_KEY);
|
|
35
|
+
localStorage.removeItem(projectKey);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
interface ComposableTokenResponse {
|
|
14
40
|
rawToken: string;
|
|
15
41
|
token: AuthTokenPayload;
|
|
@@ -17,6 +43,35 @@ interface ComposableTokenResponse {
|
|
|
17
43
|
message?: string;
|
|
18
44
|
}
|
|
19
45
|
|
|
46
|
+
export interface AuthenticatedIdentity {
|
|
47
|
+
email: string;
|
|
48
|
+
name?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function identityFromAcceptedToken(token: string): AuthenticatedIdentity | undefined {
|
|
52
|
+
try {
|
|
53
|
+
const payload = jwtDecode<{ email?: unknown; name?: unknown }>(token);
|
|
54
|
+
if (typeof payload.email !== 'string' || !payload.email) return undefined;
|
|
55
|
+
return {
|
|
56
|
+
email: payload.email,
|
|
57
|
+
name: typeof payload.name === 'string' && payload.name ? payload.name : undefined,
|
|
58
|
+
};
|
|
59
|
+
} catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resolveAuthSelection(currentUrl: URL): { accountId?: string; projectId?: string } {
|
|
65
|
+
const urlAccount = currentUrl.searchParams.get('a') ?? undefined;
|
|
66
|
+
const urlProject = currentUrl.searchParams.get('p') ?? undefined;
|
|
67
|
+
const accountId =
|
|
68
|
+
urlAccount ??
|
|
69
|
+
(urlProject === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);
|
|
70
|
+
const projectId = urlProject ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${accountId}`) ?? undefined;
|
|
71
|
+
|
|
72
|
+
return { accountId, projectId };
|
|
73
|
+
}
|
|
74
|
+
|
|
20
75
|
function normalizeIssuer(value: string | undefined): string | undefined {
|
|
21
76
|
return value?.replace(/\/+$/, '');
|
|
22
77
|
}
|
|
@@ -114,7 +169,9 @@ export async function fetchComposableToken(
|
|
|
114
169
|
error: error,
|
|
115
170
|
},
|
|
116
171
|
});
|
|
117
|
-
throw new
|
|
172
|
+
throw new AuthenticationServiceError('The authentication service could not be reached', stsEndpoint, {
|
|
173
|
+
cause: error,
|
|
174
|
+
});
|
|
118
175
|
});
|
|
119
176
|
|
|
120
177
|
if (idToken && stsRes?.status === 404) {
|
|
@@ -211,15 +268,20 @@ export async function fetchComposableToken(
|
|
|
211
268
|
throw new UserNotFoundError('User not found', idTokenDecoded.email);
|
|
212
269
|
}
|
|
213
270
|
|
|
271
|
+
if (stsRes.status === 401) {
|
|
272
|
+
Env.logger.warn('STS rejected the identity credential', {
|
|
273
|
+
vertesia: { account_id: accountId, project_id: projectId, status: stsRes.status },
|
|
274
|
+
});
|
|
275
|
+
throw new CredentialError('Your sign-in credentials could not be verified', stsEndpoint, {
|
|
276
|
+
status: stsRes.status,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
214
280
|
if (stsRes.status === 403) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const body = await stsRes
|
|
220
|
-
.clone()
|
|
221
|
-
.json()
|
|
222
|
-
.catch(() => undefined);
|
|
281
|
+
const body = (await stsRes.json().catch(() => undefined)) as
|
|
282
|
+
| { errorCode?: string; message?: string }
|
|
283
|
+
| undefined;
|
|
284
|
+
const identity = identityFromAcceptedToken(idToken);
|
|
223
285
|
if (body?.errorCode === RESTRICTED_ENVIRONMENT_ERROR_CODE) {
|
|
224
286
|
Env.logger.warn('403: User lacks early-access for this restricted environment', {
|
|
225
287
|
vertesia: {
|
|
@@ -232,44 +294,46 @@ export async function fetchComposableToken(
|
|
|
232
294
|
);
|
|
233
295
|
}
|
|
234
296
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
297
|
+
if (body?.errorCode === NO_ACCESSIBLE_ACCOUNT_ERROR_CODE) {
|
|
298
|
+
Env.logger.warn('STS identity has no accessible account', {
|
|
299
|
+
vertesia: { error_code: body.errorCode, status: stsRes.status },
|
|
300
|
+
});
|
|
301
|
+
throw new NoAccessibleAccountError(stsEndpoint, body.message, identity);
|
|
302
|
+
}
|
|
240
303
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
Env.logger.
|
|
304
|
+
// During rolling deployment an older STS may return an uncoded user-token 403.
|
|
305
|
+
// Treat it as a requested-scope rejection; all calls in this function issue user tokens.
|
|
306
|
+
if (!body?.errorCode || body.errorCode === REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE) {
|
|
307
|
+
Env.logger.warn('STS rejected the requested account or project', {
|
|
245
308
|
vertesia: {
|
|
246
309
|
account_id: accountId,
|
|
247
310
|
project_id: projectId,
|
|
248
311
|
status: stsRes.status,
|
|
249
|
-
|
|
312
|
+
error_code: body?.errorCode ?? REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,
|
|
250
313
|
},
|
|
251
314
|
});
|
|
252
|
-
throw new
|
|
315
|
+
throw new RequestedScopeUnavailableError(
|
|
316
|
+
stsEndpoint,
|
|
317
|
+
accountId,
|
|
318
|
+
projectId,
|
|
319
|
+
body?.errorCode ? body.message : undefined,
|
|
320
|
+
identity,
|
|
321
|
+
);
|
|
253
322
|
}
|
|
254
323
|
|
|
255
|
-
|
|
256
|
-
Env.logger.warn('403: Access denied - clearing cached account and retrying', {
|
|
324
|
+
Env.logger.error('STS returned an unrecognized forbidden response', {
|
|
257
325
|
vertesia: {
|
|
258
326
|
account_id: accountId,
|
|
259
327
|
project_id: projectId,
|
|
260
328
|
status: stsRes.status,
|
|
261
|
-
|
|
329
|
+
error_code: body.errorCode,
|
|
262
330
|
},
|
|
263
331
|
});
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Retry without account/project scope - let user log in to their default account
|
|
272
|
-
return fetchComposableToken(getIdToken, undefined, undefined, ttl, retryCount + 1);
|
|
332
|
+
throw new AuthenticationServiceError('The authentication service rejected the request', stsEndpoint, {
|
|
333
|
+
status: stsRes.status,
|
|
334
|
+
errorCode: body.errorCode,
|
|
335
|
+
cause: body,
|
|
336
|
+
});
|
|
273
337
|
}
|
|
274
338
|
|
|
275
339
|
if (!stsRes.ok) {
|
|
@@ -283,7 +347,10 @@ export async function fetchComposableToken(
|
|
|
283
347
|
project_id: projectId,
|
|
284
348
|
},
|
|
285
349
|
});
|
|
286
|
-
throw new
|
|
350
|
+
throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {
|
|
351
|
+
status: stsRes.status,
|
|
352
|
+
cause: errorText,
|
|
353
|
+
});
|
|
287
354
|
}
|
|
288
355
|
|
|
289
356
|
const { token } = await stsRes.json();
|
|
@@ -300,11 +367,7 @@ export async function fetchComposableToken(
|
|
|
300
367
|
throw error;
|
|
301
368
|
}
|
|
302
369
|
|
|
303
|
-
|
|
304
|
-
localStorage.removeItem(LastSelectedAccountId_KEY);
|
|
305
|
-
if (accountId) {
|
|
306
|
-
localStorage.removeItem(`${LastSelectedProjectId_KEY}-${accountId}`);
|
|
307
|
-
}
|
|
370
|
+
if (error instanceof Error && error.message === 'Customer-domain user requires an invite to join') throw error;
|
|
308
371
|
console.error('Failed to get composable token from STS', error);
|
|
309
372
|
Env.logger.error('Failed to get composable token from STS', {
|
|
310
373
|
vertesia: {
|
|
@@ -313,7 +376,9 @@ export async function fetchComposableToken(
|
|
|
313
376
|
error: error,
|
|
314
377
|
},
|
|
315
378
|
});
|
|
316
|
-
throw new
|
|
379
|
+
throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {
|
|
380
|
+
cause: error,
|
|
381
|
+
});
|
|
317
382
|
}
|
|
318
383
|
}
|
|
319
384
|
|
|
@@ -354,14 +419,26 @@ export async function getComposableToken(
|
|
|
354
419
|
forceRefresh = false,
|
|
355
420
|
useInternalAuth = false,
|
|
356
421
|
): Promise<ComposableTokenResponse> {
|
|
357
|
-
const selectedAccount =
|
|
422
|
+
const selectedAccount =
|
|
423
|
+
accountId ??
|
|
424
|
+
(projectId === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);
|
|
358
425
|
const selectedProject =
|
|
359
426
|
projectId ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${selectedAccount}`) ?? undefined;
|
|
360
427
|
const devAuthToken = Env.isLocalDev ? Env.devAuthToken : undefined;
|
|
361
428
|
const suppliedToken = devAuthToken ?? initToken ?? AUTH_TOKEN_RAW;
|
|
362
429
|
|
|
363
|
-
|
|
364
|
-
|
|
430
|
+
const cachedTokenMatchesScope =
|
|
431
|
+
(!selectedAccount || AUTH_TOKEN?.account?.id === selectedAccount) &&
|
|
432
|
+
(!selectedProject || AUTH_TOKEN?.project?.id === selectedProject);
|
|
433
|
+
|
|
434
|
+
// Token is still valid for more than 5 minutes and belongs to the requested scope.
|
|
435
|
+
if (
|
|
436
|
+
!forceRefresh &&
|
|
437
|
+
AUTH_TOKEN_RAW &&
|
|
438
|
+
AUTH_TOKEN &&
|
|
439
|
+
cachedTokenMatchesScope &&
|
|
440
|
+
AUTH_TOKEN.exp > Date.now() / 1000 + 300
|
|
441
|
+
) {
|
|
365
442
|
return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
|
|
366
443
|
}
|
|
367
444
|
|
|
@@ -378,19 +455,34 @@ export async function getComposableToken(
|
|
|
378
455
|
return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
|
|
379
456
|
}
|
|
380
457
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
458
|
+
try {
|
|
459
|
+
//token is close to expire, refresh it
|
|
460
|
+
if (!devAuthToken && !useInternalAuth && getFirebaseAuth().currentUser) {
|
|
461
|
+
//we have a firebase user, get the token from there
|
|
462
|
+
AUTH_TOKEN_RAW = await fetchComposableTokenFromFirebaseToken(selectedAccount, selectedProject);
|
|
463
|
+
} else if (!devAuthToken && (initToken || AUTH_TOKEN_RAW)) {
|
|
464
|
+
// we have a token already and no firebase user, refresh it
|
|
465
|
+
AUTH_TOKEN_RAW = await fetchComposableToken(
|
|
466
|
+
() => Promise.resolve(initToken ?? AUTH_TOKEN_RAW),
|
|
467
|
+
selectedAccount,
|
|
468
|
+
selectedProject,
|
|
469
|
+
);
|
|
470
|
+
} else if (devAuthToken) {
|
|
471
|
+
AUTH_TOKEN_RAW = devAuthToken;
|
|
472
|
+
}
|
|
473
|
+
} catch (error: unknown) {
|
|
474
|
+
if (
|
|
475
|
+
error instanceof RequestedScopeUnavailableError ||
|
|
476
|
+
error instanceof NoAccessibleAccountError ||
|
|
477
|
+
error instanceof CredentialError
|
|
478
|
+
) {
|
|
479
|
+
AUTH_TOKEN_RAW = undefined;
|
|
480
|
+
AUTH_TOKEN = undefined;
|
|
481
|
+
if (error instanceof RequestedScopeUnavailableError) {
|
|
482
|
+
clearRejectedPersistedScope(selectedAccount, selectedProject);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
throw error;
|
|
394
486
|
}
|
|
395
487
|
|
|
396
488
|
if (!AUTH_TOKEN_RAW) {
|
|
@@ -430,13 +522,90 @@ export class UserNotFoundError extends Error {
|
|
|
430
522
|
|
|
431
523
|
export class STSError extends Error {
|
|
432
524
|
stsURL: string;
|
|
433
|
-
|
|
434
|
-
|
|
525
|
+
readonly status?: number;
|
|
526
|
+
readonly errorCode?: string;
|
|
527
|
+
constructor(message: string, stsURL: string, options?: ErrorOptions & { status?: number; errorCode?: string }) {
|
|
528
|
+
super(message, options);
|
|
435
529
|
this.name = 'STSError';
|
|
436
530
|
this.stsURL = stsURL;
|
|
531
|
+
this.status = options?.status;
|
|
532
|
+
this.errorCode = options?.errorCode;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export type RequestedScopeKind = 'account' | 'project';
|
|
537
|
+
export type AuthSelectorSource = 'url' | 'persisted';
|
|
538
|
+
|
|
539
|
+
export class RequestedScopeUnavailableError extends STSError {
|
|
540
|
+
requestedScope: RequestedScopeKind;
|
|
541
|
+
selectorSource?: AuthSelectorSource;
|
|
542
|
+
recoveryUrl?: string;
|
|
543
|
+
|
|
544
|
+
constructor(
|
|
545
|
+
stsURL: string,
|
|
546
|
+
public readonly accountId?: string,
|
|
547
|
+
public readonly projectId?: string,
|
|
548
|
+
responseMessage?: string,
|
|
549
|
+
public readonly identity?: AuthenticatedIdentity,
|
|
550
|
+
) {
|
|
551
|
+
super(responseMessage ?? 'The requested account or project is not available.', stsURL, {
|
|
552
|
+
status: 403,
|
|
553
|
+
errorCode: REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,
|
|
554
|
+
});
|
|
555
|
+
this.name = 'RequestedScopeUnavailableError';
|
|
556
|
+
this.requestedScope = projectId ? 'project' : 'account';
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** @deprecated Use RequestedScopeUnavailableError. */
|
|
561
|
+
export const TokenAuthorizationError = RequestedScopeUnavailableError;
|
|
562
|
+
/** @deprecated Use RequestedScopeUnavailableError. */
|
|
563
|
+
export type TokenAuthorizationError = RequestedScopeUnavailableError;
|
|
564
|
+
|
|
565
|
+
export class NoAccessibleAccountError extends STSError {
|
|
566
|
+
constructor(
|
|
567
|
+
stsURL: string,
|
|
568
|
+
responseMessage?: string,
|
|
569
|
+
public readonly identity?: AuthenticatedIdentity,
|
|
570
|
+
) {
|
|
571
|
+
super(responseMessage ?? 'No accessible account is available for this user.', stsURL, {
|
|
572
|
+
status: 403,
|
|
573
|
+
errorCode: NO_ACCESSIBLE_ACCOUNT_ERROR_CODE,
|
|
574
|
+
});
|
|
575
|
+
this.name = 'NoAccessibleAccountError';
|
|
437
576
|
}
|
|
438
577
|
}
|
|
439
578
|
|
|
579
|
+
export class CredentialError extends STSError {
|
|
580
|
+
constructor(message: string, stsURL: string, options?: ErrorOptions & { status?: number; errorCode?: string }) {
|
|
581
|
+
super(message, stsURL, options);
|
|
582
|
+
this.name = 'CredentialError';
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export class AuthenticationServiceError extends STSError {
|
|
587
|
+
constructor(message: string, stsURL: string, options?: ErrorOptions & { status?: number; errorCode?: string }) {
|
|
588
|
+
super(message, stsURL, options);
|
|
589
|
+
this.name = 'AuthenticationServiceError';
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export function isRequestedScopeUnavailableError(error: unknown): error is RequestedScopeUnavailableError {
|
|
594
|
+
return error instanceof RequestedScopeUnavailableError;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function isNoAccessibleAccountError(error: unknown): error is NoAccessibleAccountError {
|
|
598
|
+
return error instanceof NoAccessibleAccountError;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export function isCredentialError(error: unknown): error is CredentialError {
|
|
602
|
+
return error instanceof CredentialError;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function isAuthenticationServiceError(error: unknown): error is AuthenticationServiceError {
|
|
606
|
+
return error instanceof AuthenticationServiceError;
|
|
607
|
+
}
|
|
608
|
+
|
|
440
609
|
/**
|
|
441
610
|
* Thrown when the STS rejects sign-in because the current environment (preview/preprod) is
|
|
442
611
|
* restricted to early-access users and this user lacks the `early-access` annotation. The
|
|
@@ -65,6 +65,7 @@ const SIGNIN_STEP_BUTTON_VARIANTS = {
|
|
|
65
65
|
|
|
66
66
|
interface SignInStepButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
67
67
|
variant?: keyof typeof SIGNIN_STEP_BUTTON_VARIANTS;
|
|
68
|
+
ref?: Ref<HTMLButtonElement>;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/** Button used across the sign-in steps. */
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import SignInRecoveryStep from './SignInRecoveryStep';
|
|
4
|
+
|
|
5
|
+
const translations: Record<string, string> = {
|
|
6
|
+
'auth.recovery.scopeProject.eyebrow': 'Link Unavailable',
|
|
7
|
+
'auth.recovery.scopeProject.title': 'Project not available',
|
|
8
|
+
'auth.recovery.scopeProject.body':
|
|
9
|
+
'You’re signed in, but the project in this link isn’t available. The link may be outdated, or you may not have access.',
|
|
10
|
+
'auth.recovery.scopeAccount.eyebrow': 'Link Unavailable',
|
|
11
|
+
'auth.recovery.scopeAccount.title': 'Account not available',
|
|
12
|
+
'auth.recovery.scopeAccount.body': 'Account unavailable body',
|
|
13
|
+
'auth.recovery.noAccessibleAccount.eyebrow': 'Account Required',
|
|
14
|
+
'auth.recovery.noAccessibleAccount.title': 'No account is available',
|
|
15
|
+
'auth.recovery.noAccessibleAccount.body': 'No accessible account body',
|
|
16
|
+
'auth.recovery.credential.eyebrow': 'Sign-In Required',
|
|
17
|
+
'auth.recovery.credential.title': 'Your sign-in could not be verified',
|
|
18
|
+
'auth.recovery.credential.body': 'Credential body',
|
|
19
|
+
'auth.recovery.service.eyebrow': 'Service Unavailable',
|
|
20
|
+
'auth.recovery.service.title': "We couldn't complete sign-in",
|
|
21
|
+
'auth.recovery.service.body': 'Service body',
|
|
22
|
+
'auth.recovery.continue': 'Continue to Vertesia',
|
|
23
|
+
'auth.recovery.tryAgain': 'Try again',
|
|
24
|
+
'auth.recovery.useDifferentAccount': 'Use a different account',
|
|
25
|
+
'auth.recovery.supportPrefix': 'If you need help, contact',
|
|
26
|
+
'auth.recovery.technicalDetails': 'Technical details',
|
|
27
|
+
'auth.recovery.errorCode': 'Error code',
|
|
28
|
+
'auth.recovery.httpStatus': 'HTTP status',
|
|
29
|
+
'auth.recovery.requestedAccount': 'Requested account',
|
|
30
|
+
'auth.recovery.requestedProject': 'Requested project',
|
|
31
|
+
'auth.recovery.errorMessage': 'Error message',
|
|
32
|
+
'auth.returning.notYou': 'Not you?',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
vi.mock('@vertesia/ui/i18n', () => ({
|
|
36
|
+
useUITranslation: () => ({ t: (key: string) => translations[key] ?? key }),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
describe('SignInRecoveryStep', () => {
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
document.body.innerHTML = '';
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('shows project-safe copy, focuses the primary action, and prevents duplicate submissions', () => {
|
|
45
|
+
const onContinue = vi.fn();
|
|
46
|
+
render(<SignInRecoveryStep kind="scopeProject" onContinue={onContinue} onUseDifferentAccount={vi.fn()} />);
|
|
47
|
+
|
|
48
|
+
expect(screen.getByText('Project not available')).toBeTruthy();
|
|
49
|
+
expect(screen.getByText(/project in this link isn’t available/)).toBeTruthy();
|
|
50
|
+
const continueButton = screen.getByRole('button', { name: 'Continue to Vertesia' });
|
|
51
|
+
expect(document.activeElement).toBe(continueButton);
|
|
52
|
+
|
|
53
|
+
fireEvent.click(continueButton);
|
|
54
|
+
fireEvent.click(continueButton);
|
|
55
|
+
|
|
56
|
+
expect(onContinue).toHaveBeenCalledOnce();
|
|
57
|
+
expect((continueButton as HTMLButtonElement).disabled).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('omits continue for an identity with no accessible account and includes support guidance', () => {
|
|
61
|
+
const onUseDifferentAccount = vi.fn();
|
|
62
|
+
render(<SignInRecoveryStep kind="noAccessibleAccount" onUseDifferentAccount={onUseDifferentAccount} />);
|
|
63
|
+
|
|
64
|
+
expect(screen.queryByRole('button', { name: 'Continue to Vertesia' })).toBeNull();
|
|
65
|
+
expect(screen.getByText('No account is available')).toBeTruthy();
|
|
66
|
+
expect(screen.getByRole('link', { name: 'support@vertesiahq.com' }).getAttribute('href')).toBe(
|
|
67
|
+
'mailto:support@vertesiahq.com',
|
|
68
|
+
);
|
|
69
|
+
expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Use a different account' }));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('shows submitted selectors and the normalized server error in collapsed technical details', () => {
|
|
73
|
+
render(
|
|
74
|
+
<SignInRecoveryStep
|
|
75
|
+
details={{
|
|
76
|
+
accountId: 'apple',
|
|
77
|
+
errorCode: 'requested_scope_unavailable',
|
|
78
|
+
message: 'The requested account or project is not available.',
|
|
79
|
+
projectId: 'banana',
|
|
80
|
+
status: 403,
|
|
81
|
+
}}
|
|
82
|
+
kind="scopeProject"
|
|
83
|
+
onContinue={vi.fn()}
|
|
84
|
+
onUseDifferentAccount={vi.fn()}
|
|
85
|
+
/>,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const disclosure = screen.getByText('Technical details').closest('details');
|
|
89
|
+
expect(disclosure?.hasAttribute('open')).toBe(false);
|
|
90
|
+
expect(screen.getByText('requested_scope_unavailable')).toBeTruthy();
|
|
91
|
+
expect(screen.getByText('apple')).toBeTruthy();
|
|
92
|
+
expect(screen.getByText('banana')).toBeTruthy();
|
|
93
|
+
expect(screen.getByText('The requested account or project is not available.')).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('reuses the returning-user identity row for the authenticated credential', () => {
|
|
97
|
+
const onUseDifferentAccount = vi.fn();
|
|
98
|
+
render(
|
|
99
|
+
<SignInRecoveryStep
|
|
100
|
+
identity={{ email: 'leon@example.com', name: 'Leon Ruggiero' }}
|
|
101
|
+
kind="scopeProject"
|
|
102
|
+
onContinue={vi.fn()}
|
|
103
|
+
onUseDifferentAccount={onUseDifferentAccount}
|
|
104
|
+
/>,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(screen.getByText('Leon Ruggiero')).toBeTruthy();
|
|
108
|
+
expect(screen.getByText('leon@example.com')).toBeTruthy();
|
|
109
|
+
fireEvent.click(screen.getByRole('button', { name: 'Not you?' }));
|
|
110
|
+
expect(onUseDifferentAccount).toHaveBeenCalledOnce();
|
|
111
|
+
});
|
|
112
|
+
});
|