@vertesia/ui 1.5.0 → 1.5.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/lib/core/components/SelectList.d.ts.map +1 -1
- package/lib/core/components/SelectList.js +3 -3
- package/lib/core/components/SelectList.js.map +1 -1
- package/lib/core/components/SidePanel.d.ts.map +1 -1
- package/lib/core/components/SidePanel.js +2 -1
- package/lib/core/components/SidePanel.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +51 -17
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +5 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +9 -2
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.d.ts +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.js +5 -5
- package/lib/features/agent/chat/hooks/useAgentPlans.js.map +1 -1
- package/lib/features/magic-pdf/GroundedExtractionView.js +3 -3
- package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.d.ts +2 -2
- package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.js +9 -7
- package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js +7 -3
- package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
- package/lib/features/oauth/useMcpConnections.d.ts +4 -2
- package/lib/features/oauth/useMcpConnections.d.ts.map +1 -1
- package/lib/features/oauth/useMcpConnections.js +15 -9
- package/lib/features/oauth/useMcpConnections.js.map +1 -1
- package/lib/features/store/objects/selection/ObjectsActionContext.d.ts.map +1 -1
- package/lib/features/store/objects/selection/ObjectsActionContext.js +2 -0
- package/lib/features/store/objects/selection/ObjectsActionContext.js.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.d.ts +4 -2
- package/lib/features/store/objects/selection/SelectionActions.d.ts.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js +6 -3
- package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
- package/lib/features/store/objects/selection/actions/RunIntakeAction.d.ts +8 -0
- package/lib/features/store/objects/selection/actions/RunIntakeAction.d.ts.map +1 -0
- package/lib/features/store/objects/selection/actions/RunIntakeAction.js +65 -0
- package/lib/features/store/objects/selection/actions/RunIntakeAction.js.map +1 -0
- package/lib/features/store/objects/selection/actions/index.d.ts +1 -0
- package/lib/features/store/objects/selection/actions/index.d.ts.map +1 -1
- package/lib/features/store/objects/selection/actions/index.js +1 -0
- package/lib/features/store/objects/selection/actions/index.js.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.js +11 -3
- package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.js +14 -14
- package/lib/features/store/types/IntakePolicyEditor.js.map +1 -1
- package/lib/features/store/types/TypeRegistry.d.ts +275 -150
- package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +275 -150
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
- package/lib/i18n/locales/ar.json +28 -7
- package/lib/i18n/locales/de.json +24 -7
- package/lib/i18n/locales/en.json +24 -7
- package/lib/i18n/locales/es.json +24 -7
- package/lib/i18n/locales/fr.json +25 -7
- package/lib/i18n/locales/it.json +24 -7
- package/lib/i18n/locales/ja.json +24 -7
- package/lib/i18n/locales/ko.json +24 -7
- package/lib/i18n/locales/pt.json +24 -7
- package/lib/i18n/locales/ru.json +26 -7
- package/lib/i18n/locales/tr.json +24 -7
- package/lib/i18n/locales/zh-TW.json +24 -7
- package/lib/i18n/locales/zh.json +24 -7
- package/lib/shell/apps/AppInstallationProvider.d.ts +4 -4
- package/lib/shell/login/SignInIcons.d.ts.map +1 -1
- package/lib/shell/login/SignInIcons.js +2 -1
- package/lib/shell/login/SignInIcons.js.map +1 -1
- package/lib/shell/login/SignInPasswordStep.d.ts +9 -0
- package/lib/shell/login/SignInPasswordStep.d.ts.map +1 -0
- package/lib/shell/login/SignInPasswordStep.js +58 -0
- package/lib/shell/login/SignInPasswordStep.js.map +1 -0
- package/lib/shell/login/SignInPrimitives.d.ts +13 -0
- package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
- package/lib/shell/login/SignInPrimitives.js +11 -1
- package/lib/shell/login/SignInPrimitives.js.map +1 -1
- package/lib/shell/login/SignInProvidersStep.d.ts +3 -1
- package/lib/shell/login/SignInProvidersStep.d.ts.map +1 -1
- package/lib/shell/login/SignInProvidersStep.js +4 -2
- package/lib/shell/login/SignInProvidersStep.js.map +1 -1
- package/lib/shell/login/SignInReturningStep.d.ts +3 -1
- package/lib/shell/login/SignInReturningStep.d.ts.map +1 -1
- package/lib/shell/login/SignInReturningStep.js +18 -8
- package/lib/shell/login/SignInReturningStep.js.map +1 -1
- package/lib/shell/login/SignInTenantStep.d.ts +3 -1
- package/lib/shell/login/SignInTenantStep.d.ts.map +1 -1
- package/lib/shell/login/SignInTenantStep.js +2 -2
- package/lib/shell/login/SignInTenantStep.js.map +1 -1
- package/lib/shell/login/SignInWithProviderButton.d.ts +5 -3
- package/lib/shell/login/SignInWithProviderButton.d.ts.map +1 -1
- package/lib/shell/login/SignInWithProviderButton.js +8 -7
- package/lib/shell/login/SignInWithProviderButton.js.map +1 -1
- package/lib/shell/login/SigninScreen.d.ts.map +1 -1
- package/lib/shell/login/SigninScreen.js +13 -4
- package/lib/shell/login/SigninScreen.js.map +1 -1
- package/lib/shell/login/signInUtils.d.ts +20 -4
- package/lib/shell/login/signInUtils.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.js +47 -1
- package/lib/shell/login/signInUtils.js.map +1 -1
- package/lib/vertesia-ui-core.js +1 -1
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-features.js +14 -14
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.BoNE1APG.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BoNE1APG.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BoPtiSPb.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BoPtiSPb.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.C4BBW5v5.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.C4BBW5v5.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.C81wuNkY.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.C81wuNkY.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.C9GipKIH.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.C9GipKIH.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CWsoS0W9.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CWsoS0W9.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Cr9NtzZB.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Cr9NtzZB.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DEpkHOD5.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DEpkHOD5.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DPXaRKFO.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DPXaRKFO.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.D_QNm1Xg.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.D_QNm1Xg.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.F26Cc0f7.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.F26Cc0f7.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.YkXIESV_.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.YkXIESV_.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.js +3 -3
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-shell.js +15 -9
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
- package/package.json +7 -7
- package/src/core/components/SelectList.tsx +5 -2
- package/src/core/components/SidePanel.tsx +65 -60
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +150 -1
- package/src/features/agent/chat/ModernAgentConversation.tsx +59 -21
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +17 -1
- package/src/features/agent/chat/hooks/useAgentPlans.test.ts +52 -0
- package/src/features/agent/chat/hooks/useAgentPlans.ts +5 -5
- package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
- package/src/features/oauth/McpConnectionsButton.test.tsx +56 -0
- package/src/features/oauth/McpConnectionsButton.tsx +9 -6
- package/src/features/oauth/McpConnectionsDialog.tsx +14 -5
- package/src/features/oauth/useMcpConnections.test.ts +10 -0
- package/src/features/oauth/useMcpConnections.ts +18 -10
- package/src/features/store/objects/selection/ObjectsActionContext.tsx +2 -0
- package/src/features/store/objects/selection/SelectionActions.tsx +13 -6
- package/src/features/store/objects/selection/actions/RunIntakeAction.tsx +69 -0
- package/src/features/store/objects/selection/actions/index.ts +1 -0
- package/src/features/store/types/ContentObjectTypesTable.tsx +20 -3
- package/src/features/store/types/IntakePolicyEditor.tsx +33 -28
- package/src/i18n/locales/ar.json +28 -7
- package/src/i18n/locales/de.json +24 -7
- package/src/i18n/locales/en.json +24 -7
- package/src/i18n/locales/es.json +24 -7
- package/src/i18n/locales/fr.json +25 -7
- package/src/i18n/locales/it.json +24 -7
- package/src/i18n/locales/ja.json +24 -7
- package/src/i18n/locales/ko.json +24 -7
- package/src/i18n/locales/pt.json +24 -7
- package/src/i18n/locales/ru.json +26 -7
- package/src/i18n/locales/tr.json +24 -7
- package/src/i18n/locales/zh-TW.json +24 -7
- package/src/i18n/locales/zh.json +24 -7
- package/src/shell/login/SignInIcons.tsx +2 -1
- package/src/shell/login/SignInPasswordRequired.test.tsx +82 -0
- package/src/shell/login/SignInPasswordStep.test.tsx +71 -0
- package/src/shell/login/SignInPasswordStep.tsx +103 -0
- package/src/shell/login/SignInPrimitives.tsx +53 -1
- package/src/shell/login/SignInProvidersStep.tsx +8 -4
- package/src/shell/login/SignInReturningStep.tsx +20 -8
- package/src/shell/login/SignInTenantStep.tsx +6 -2
- package/src/shell/login/SignInWithProviderButton.tsx +11 -7
- package/src/shell/login/SigninScreen.tsx +32 -3
- package/src/shell/login/signInPassword.test.ts +133 -0
- package/src/shell/login/signInUtils.ts +60 -7
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts +0 -7
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts.map +0 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +0 -51
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +0 -1
- package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DN79y06J.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DN79y06J.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js.map +0 -1
- package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +0 -104
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { useCallback, useEffect, useState } from 'react';
|
|
16
16
|
import SignInAuthPending from './SignInAuthPending';
|
|
17
17
|
import SignInEmailStep, { type TenantInfo } from './SignInEmailStep';
|
|
18
|
+
import SignInPasswordStep from './SignInPasswordStep';
|
|
18
19
|
import SignInProvidersStep from './SignInProvidersStep';
|
|
19
20
|
import SignInRecoveryStep, { type SignInRecoveryKind } from './SignInRecoveryStep';
|
|
20
21
|
import SignInRestrictedEnvStep from './SignInRestrictedEnvStep';
|
|
@@ -79,6 +80,7 @@ type Mode =
|
|
|
79
80
|
| 'email'
|
|
80
81
|
| 'providers'
|
|
81
82
|
| 'tenant'
|
|
83
|
+
| 'password'
|
|
82
84
|
| 'blocked'
|
|
83
85
|
| 'returning'
|
|
84
86
|
| 'pending'
|
|
@@ -185,7 +187,13 @@ function SigninScreenImpl({
|
|
|
185
187
|
const onProceedFromEmail = useCallback((e: string, t: TenantInfo | undefined) => {
|
|
186
188
|
setEmail(e);
|
|
187
189
|
setTenant(t);
|
|
188
|
-
|
|
190
|
+
// A password tenant has no identity provider to pick; ask for its password directly.
|
|
191
|
+
setMode(t?.provider === 'password' ? 'password' : t ? 'tenant' : 'providers');
|
|
192
|
+
}, []);
|
|
193
|
+
|
|
194
|
+
const onPasswordRequired = useCallback((e: string) => {
|
|
195
|
+
setEmail(e);
|
|
196
|
+
setMode('password');
|
|
189
197
|
}, []);
|
|
190
198
|
|
|
191
199
|
const onBack = useCallback(() => {
|
|
@@ -336,6 +344,14 @@ function SigninScreenImpl({
|
|
|
336
344
|
);
|
|
337
345
|
} else if (mode === 'signup' && !localStorage.getItem('tenantName')) {
|
|
338
346
|
content = <SignupForm onSignup={onSignup} goBack={startOver} />;
|
|
347
|
+
} else if (mode === 'password' && email) {
|
|
348
|
+
content = (
|
|
349
|
+
<SignInPasswordStep
|
|
350
|
+
email={email}
|
|
351
|
+
tenantName={tenant?.label || tenant?.name || storedSession?.tenantName || undefined}
|
|
352
|
+
onBack={onBack}
|
|
353
|
+
/>
|
|
354
|
+
);
|
|
339
355
|
} else if (mode === 'tenant' && tenant) {
|
|
340
356
|
content = (
|
|
341
357
|
<SignInTenantStep
|
|
@@ -343,13 +359,26 @@ function SigninScreenImpl({
|
|
|
343
359
|
tenant={tenant}
|
|
344
360
|
onBack={onBack}
|
|
345
361
|
onProviderClicked={() => onProviderClicked((tenant.provider ?? 'oidc') as ProviderId)}
|
|
362
|
+
onPasswordRequired={onPasswordRequired}
|
|
346
363
|
/>
|
|
347
364
|
);
|
|
348
365
|
} else if (mode === 'providers') {
|
|
349
|
-
content =
|
|
366
|
+
content = (
|
|
367
|
+
<SignInProvidersStep
|
|
368
|
+
email={email}
|
|
369
|
+
onBack={onBack}
|
|
370
|
+
onProviderClicked={onProviderClicked}
|
|
371
|
+
onPasswordRequired={onPasswordRequired}
|
|
372
|
+
/>
|
|
373
|
+
);
|
|
350
374
|
} else if (mode === 'returning' && storedSession) {
|
|
351
375
|
content = (
|
|
352
|
-
<SignInReturningStep
|
|
376
|
+
<SignInReturningStep
|
|
377
|
+
session={storedSession}
|
|
378
|
+
onNotYou={onNotYou}
|
|
379
|
+
onProviderClicked={onProviderClicked}
|
|
380
|
+
onPasswordRequired={onPasswordRequired}
|
|
381
|
+
/>
|
|
353
382
|
);
|
|
354
383
|
} else {
|
|
355
384
|
content = <SignInEmailStep initialEmail={email} onProceed={onProceedFromEmail} />;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { FirebaseError } from 'firebase/app';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
auth: { tenantId: null as string | null },
|
|
6
|
+
setFirebaseTenant: vi.fn(),
|
|
7
|
+
signInWithEmailAndPassword: vi.fn(),
|
|
8
|
+
signInWithRedirect: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock('@vertesia/ui/session', () => ({
|
|
12
|
+
getFirebaseAuth: () => mocks.auth,
|
|
13
|
+
setFirebaseTenant: mocks.setFirebaseTenant,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('firebase/auth', async (importOriginal) => ({
|
|
17
|
+
...(await importOriginal<typeof import('firebase/auth')>()),
|
|
18
|
+
signInWithEmailAndPassword: mocks.signInWithEmailAndPassword,
|
|
19
|
+
signInWithRedirect: mocks.signInWithRedirect,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const { passwordFailureReason, readPendingSignin, signInWithPassword, startSignIn } = await import('./signInUtils');
|
|
23
|
+
|
|
24
|
+
const PASSWORD_TENANT = {
|
|
25
|
+
label: 'Vertesia Reviewers',
|
|
26
|
+
name: 'reviewers',
|
|
27
|
+
firebaseTenantId: 'reviewers-abc12',
|
|
28
|
+
provider: 'password',
|
|
29
|
+
domain: ['reviewers.vertesia.io'],
|
|
30
|
+
};
|
|
31
|
+
const OIDC_TENANT = { ...PASSWORD_TENANT, label: 'Acme', name: 'acme', provider: 'oidc', domain: ['acme.com'] };
|
|
32
|
+
const EMAIL = 'claude@reviewers.vertesia.io';
|
|
33
|
+
|
|
34
|
+
describe('passwordFailureReason', () => {
|
|
35
|
+
// One answer for every credential failure, so the form cannot be used to probe which
|
|
36
|
+
// addresses exist or are disabled.
|
|
37
|
+
it.each([
|
|
38
|
+
'auth/invalid-credential',
|
|
39
|
+
'auth/wrong-password',
|
|
40
|
+
'auth/user-not-found',
|
|
41
|
+
'auth/user-disabled',
|
|
42
|
+
'auth/invalid-email',
|
|
43
|
+
])('folds %s into invalid-credentials', (code) => {
|
|
44
|
+
expect(passwordFailureReason(new FirebaseError(code, code))).toBe('invalid-credentials');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('reports throttling separately', () => {
|
|
48
|
+
expect(passwordFailureReason(new FirebaseError('auth/too-many-requests', 'throttled'))).toBe(
|
|
49
|
+
'too-many-attempts',
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('treats anything else as a generic failure', () => {
|
|
54
|
+
expect(passwordFailureReason(new FirebaseError('auth/network-request-failed', 'offline'))).toBe('failed');
|
|
55
|
+
expect(passwordFailureReason(new Error('boom'))).toBe('failed');
|
|
56
|
+
expect(passwordFailureReason(undefined)).toBe('failed');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('signInWithPassword', () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
mocks.setFirebaseTenant.mockReset();
|
|
63
|
+
mocks.signInWithEmailAndPassword.mockReset();
|
|
64
|
+
mocks.signInWithRedirect.mockReset();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
sessionStorage.clear();
|
|
69
|
+
localStorage.clear();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('signs a password-tenant user in place and records the pending sign-in', async () => {
|
|
73
|
+
mocks.setFirebaseTenant.mockResolvedValue(PASSWORD_TENANT);
|
|
74
|
+
mocks.signInWithEmailAndPassword.mockResolvedValue({});
|
|
75
|
+
|
|
76
|
+
await expect(signInWithPassword(EMAIL, 'secret')).resolves.toEqual({ ok: true });
|
|
77
|
+
|
|
78
|
+
expect(mocks.setFirebaseTenant).toHaveBeenCalledWith(EMAIL);
|
|
79
|
+
expect(mocks.signInWithEmailAndPassword).toHaveBeenCalledWith(mocks.auth, EMAIL, 'secret');
|
|
80
|
+
expect(readPendingSignin()).toEqual({
|
|
81
|
+
email: EMAIL,
|
|
82
|
+
provider: 'password',
|
|
83
|
+
tenantName: 'Vertesia Reviewers',
|
|
84
|
+
});
|
|
85
|
+
expect(mocks.signInWithRedirect).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// The password form is reachable only through tenant resolution, but the helper re-checks: an
|
|
89
|
+
// address that resolves to a federated tenant must never be offered a password.
|
|
90
|
+
it('refuses an address whose tenant is not a password tenant', async () => {
|
|
91
|
+
mocks.setFirebaseTenant.mockResolvedValue(OIDC_TENANT);
|
|
92
|
+
|
|
93
|
+
await expect(signInWithPassword('someone@acme.com', 'secret')).resolves.toEqual({
|
|
94
|
+
ok: false,
|
|
95
|
+
reason: 'not-password-tenant',
|
|
96
|
+
});
|
|
97
|
+
expect(mocks.signInWithEmailAndPassword).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('fails without signing in when the tenant cannot be resolved', async () => {
|
|
101
|
+
mocks.setFirebaseTenant.mockResolvedValue(undefined);
|
|
102
|
+
|
|
103
|
+
await expect(signInWithPassword(EMAIL, 'secret')).resolves.toEqual({ ok: false, reason: 'failed' });
|
|
104
|
+
expect(mocks.signInWithEmailAndPassword).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('clears the pending sign-in when Firebase rejects the credential', async () => {
|
|
108
|
+
mocks.setFirebaseTenant.mockResolvedValue(PASSWORD_TENANT);
|
|
109
|
+
mocks.signInWithEmailAndPassword.mockRejectedValue(new FirebaseError('auth/invalid-credential', 'bad'));
|
|
110
|
+
|
|
111
|
+
await expect(signInWithPassword(EMAIL, 'wrong')).resolves.toEqual({
|
|
112
|
+
ok: false,
|
|
113
|
+
reason: 'invalid-credentials',
|
|
114
|
+
});
|
|
115
|
+
expect(readPendingSignin()).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('startSignIn', () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
mocks.setFirebaseTenant.mockReset();
|
|
122
|
+
mocks.signInWithRedirect.mockReset();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// A returning session's stored provider can go stale; a password tenant has nowhere to redirect.
|
|
126
|
+
it('does not redirect when the address resolves to a password tenant', async () => {
|
|
127
|
+
mocks.setFirebaseTenant.mockResolvedValue(PASSWORD_TENANT);
|
|
128
|
+
|
|
129
|
+
await expect(startSignIn('google', EMAIL)).resolves.toEqual({ ok: false, reason: 'password-required' });
|
|
130
|
+
expect(mocks.signInWithRedirect).not.toHaveBeenCalled();
|
|
131
|
+
expect(readPendingSignin()).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { getFirebaseAuth, setFirebaseTenant } from '@vertesia/ui/session';
|
|
2
|
+
import { FirebaseError } from 'firebase/app';
|
|
2
3
|
import {
|
|
3
4
|
type AuthProvider,
|
|
4
5
|
signOut as firebaseSignOut,
|
|
5
6
|
GithubAuthProvider,
|
|
6
7
|
GoogleAuthProvider,
|
|
7
8
|
OAuthProvider,
|
|
9
|
+
signInWithEmailAndPassword,
|
|
8
10
|
signInWithRedirect,
|
|
9
11
|
} from 'firebase/auth';
|
|
10
12
|
|
|
11
13
|
// Matches auth-tenants.json `provider` values. Tenant context is derived from
|
|
12
14
|
// email resolution at sign-in time, not encoded here.
|
|
13
|
-
export type ProviderId = 'google' | 'github' | 'microsoft' | 'oidc';
|
|
15
|
+
export type ProviderId = 'google' | 'github' | 'microsoft' | 'oidc' | 'password';
|
|
16
|
+
|
|
17
|
+
/** Providers that sign in by redirecting to an identity provider. `password` signs in in place. */
|
|
18
|
+
export type RedirectProviderId = Exclude<ProviderId, 'password'>;
|
|
14
19
|
|
|
15
20
|
export interface LastSuccessfulLogin {
|
|
16
21
|
email: string;
|
|
@@ -140,28 +145,28 @@ const buildOidcProvider: ProviderBuilder = (email) => {
|
|
|
140
145
|
return p;
|
|
141
146
|
};
|
|
142
147
|
|
|
143
|
-
const PROVIDER_BUILDERS: Record<
|
|
148
|
+
const PROVIDER_BUILDERS: Record<RedirectProviderId, ProviderBuilder> = {
|
|
144
149
|
google: buildGoogleProvider,
|
|
145
150
|
github: buildGithubProvider,
|
|
146
151
|
microsoft: buildMicrosoftProvider,
|
|
147
152
|
oidc: buildOidcProvider,
|
|
148
153
|
};
|
|
149
154
|
|
|
150
|
-
function buildFirebaseProvider(idp:
|
|
155
|
+
function buildFirebaseProvider(idp: RedirectProviderId, email?: string, redirectTo?: string): AuthProvider {
|
|
151
156
|
return PROVIDER_BUILDERS[idp](email, redirectTo);
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
export async function startSignIn(
|
|
155
|
-
provider:
|
|
160
|
+
provider: RedirectProviderId,
|
|
156
161
|
email: string,
|
|
157
162
|
redirectTo?: string,
|
|
158
|
-
): Promise<{ ok: true } | { ok: false; reason: 'no-email' }> {
|
|
163
|
+
): Promise<{ ok: true } | { ok: false; reason: 'no-email' | 'password-required' }> {
|
|
159
164
|
if (!email) return { ok: false, reason: 'no-email' };
|
|
160
165
|
|
|
161
166
|
// A tenant-mapped email uses the tenant's provider (overriding `provider`); otherwise use `provider`.
|
|
162
167
|
const tenant = await setFirebaseTenant(email);
|
|
163
168
|
const auth = getFirebaseAuth();
|
|
164
|
-
let effectiveIdp = provider;
|
|
169
|
+
let effectiveIdp: ProviderId = provider;
|
|
165
170
|
let tenantName: string | undefined;
|
|
166
171
|
|
|
167
172
|
if (tenant) {
|
|
@@ -175,13 +180,61 @@ export async function startSignIn(
|
|
|
175
180
|
if (auth.tenantId) auth.tenantId = null;
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
// A password tenant has no identity provider to redirect to; its step collects the password.
|
|
184
|
+
if (effectiveIdp === 'password') return { ok: false, reason: 'password-required' };
|
|
185
|
+
|
|
178
186
|
writePendingSignin({ email, provider: effectiveIdp, tenantName });
|
|
179
187
|
void signInWithRedirect(auth, buildFirebaseProvider(effectiveIdp, email, redirectTo));
|
|
180
188
|
return { ok: true };
|
|
181
189
|
}
|
|
182
190
|
|
|
191
|
+
export type PasswordSignInFailure = 'not-password-tenant' | 'invalid-credentials' | 'too-many-attempts' | 'failed';
|
|
192
|
+
|
|
193
|
+
// Firebase reports an unknown user, a wrong password and a disabled user with distinct codes on
|
|
194
|
+
// some SDK versions; they are folded into one answer so the form does not reveal which it was.
|
|
195
|
+
const INVALID_CREDENTIAL_CODES = new Set([
|
|
196
|
+
'auth/invalid-credential',
|
|
197
|
+
'auth/invalid-email',
|
|
198
|
+
'auth/user-disabled',
|
|
199
|
+
'auth/user-not-found',
|
|
200
|
+
'auth/wrong-password',
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
export function passwordFailureReason(err: unknown): Exclude<PasswordSignInFailure, 'not-password-tenant'> {
|
|
204
|
+
if (!(err instanceof FirebaseError)) return 'failed';
|
|
205
|
+
if (INVALID_CREDENTIAL_CODES.has(err.code)) return 'invalid-credentials';
|
|
206
|
+
if (err.code === 'auth/too-many-requests') return 'too-many-attempts';
|
|
207
|
+
return 'failed';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Signs in a user of a password tenant, in place — there is no redirect. Only an address whose
|
|
212
|
+
* domain resolves to a tenant configured for password sign-in may use it; the tenant is resolved
|
|
213
|
+
* again here rather than trusted from the caller, so a returning session cannot carry a stale one.
|
|
214
|
+
* On success the session's auth-state listener completes the login.
|
|
215
|
+
*/
|
|
216
|
+
export async function signInWithPassword(
|
|
217
|
+
email: string,
|
|
218
|
+
password: string,
|
|
219
|
+
): Promise<{ ok: true } | { ok: false; reason: PasswordSignInFailure }> {
|
|
220
|
+
const tenant = await setFirebaseTenant(email);
|
|
221
|
+
if (!tenant) return { ok: false, reason: 'failed' };
|
|
222
|
+
if (tenant.provider !== 'password') return { ok: false, reason: 'not-password-tenant' };
|
|
223
|
+
|
|
224
|
+
const tenantName = tenant.label || tenant.name || undefined;
|
|
225
|
+
localStorage.setItem('tenantName', tenantName ?? '');
|
|
226
|
+
writePendingSignin({ email, provider: 'password', tenantName });
|
|
227
|
+
try {
|
|
228
|
+
await signInWithEmailAndPassword(getFirebaseAuth(), email, password);
|
|
229
|
+
return { ok: true };
|
|
230
|
+
} catch (err: unknown) {
|
|
231
|
+
clearPendingSignin();
|
|
232
|
+
return { ok: false, reason: passwordFailureReason(err) };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
183
236
|
/** Starts a provider sign-in directly, skipping email/tenant resolution and clearing any tenant routing. */
|
|
184
|
-
export function startSignInWithoutTenant(provider:
|
|
237
|
+
export function startSignInWithoutTenant(provider: RedirectProviderId, redirectTo?: string): void {
|
|
185
238
|
const auth = getFirebaseAuth();
|
|
186
239
|
localStorage.removeItem('tenantName');
|
|
187
240
|
if (auth.tenantId) auth.tenantId = null;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standalone "Start Workflow" button for the content objects toolbar. Renders only when objects are
|
|
3
|
-
* selected; opens a modal to pick a workflow rule and runs it on the selected object ids. Gated by
|
|
4
|
-
* the caller via the `allowWorkflowRun` prop on {@link SelectionActions}.
|
|
5
|
-
*/
|
|
6
|
-
export declare function StartWorkflowButton(): import("react").JSX.Element | null;
|
|
7
|
-
//# sourceMappingURL=StartWorkflowButton.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StartWorkflowButton.d.ts","sourceRoot":"","sources":["../../../../../../src/features/store/objects/selection/actions/StartWorkflowButton.tsx"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,wBAAgB,mBAAmB,uCAelC"}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Button, Modal, ModalBody, ModalFooter, ModalTitle, SelectList, Spinner, useFetch, useToast, } from '@vertesia/ui/core';
|
|
3
|
-
import { useUITranslation } from '@vertesia/ui/i18n';
|
|
4
|
-
import { useUserSession } from '@vertesia/ui/session';
|
|
5
|
-
import { useState } from 'react';
|
|
6
|
-
import { useDocumentSelection } from '../../DocumentSelectionProvider.js';
|
|
7
|
-
/**
|
|
8
|
-
* Standalone "Start Workflow" button for the content objects toolbar. Renders only when objects are
|
|
9
|
-
* selected; opens a modal to pick a workflow rule and runs it on the selected object ids. Gated by
|
|
10
|
-
* the caller via the `allowWorkflowRun` prop on {@link SelectionActions}.
|
|
11
|
-
*/
|
|
12
|
-
export function StartWorkflowButton() {
|
|
13
|
-
const { t } = useUITranslation();
|
|
14
|
-
const selection = useDocumentSelection();
|
|
15
|
-
const [isOpen, setOpen] = useState(false);
|
|
16
|
-
if (!selection?.hasSelection()) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
return (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => setOpen(true), children: t('store.actions.startWorkflow') }), isOpen && _jsx(StartWorkflowModal, { objectIds: selection.getObjectIds(), onClose: () => setOpen(false) })] }));
|
|
20
|
-
}
|
|
21
|
-
function StartWorkflowModal({ objectIds, onClose }) {
|
|
22
|
-
const { t } = useUITranslation();
|
|
23
|
-
const { client } = useUserSession();
|
|
24
|
-
const toast = useToast();
|
|
25
|
-
const [selected, setSelected] = useState(undefined);
|
|
26
|
-
const [isStarting, setIsStarting] = useState(false);
|
|
27
|
-
const { data: rules, isLoading } = useFetch(() => client.store.workflows.rules.list(), []);
|
|
28
|
-
const onStart = async () => {
|
|
29
|
-
if (!selected || isStarting)
|
|
30
|
-
return;
|
|
31
|
-
setIsStarting(true);
|
|
32
|
-
try {
|
|
33
|
-
await client.store.workflows.rules.execute(selected.id, objectIds);
|
|
34
|
-
toast({ title: t('store.actions.workflowStarted'), status: 'success', duration: 3000 });
|
|
35
|
-
onClose();
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
toast({
|
|
39
|
-
title: t('store.actions.errorStartingWorkflow'),
|
|
40
|
-
description: err instanceof Error ? err.message : undefined,
|
|
41
|
-
status: 'error',
|
|
42
|
-
duration: 9000,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
setIsStarting(false);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
return (_jsxs(Modal, { isOpen: true, onClose: onClose, size: "lg", children: [_jsx(ModalTitle, { children: t('store.actions.startWorkflowByRule') }), _jsxs(ModalBody, { children: [_jsx("div", { className: "pb-2", children: t('store.actions.chooseWorkflowRule') }), isLoading ? (_jsx("div", { className: "flex justify-center py-4", children: _jsx(Spinner, {}) })) : (_jsx("div", { className: "max-h-[420px] overflow-y-auto border-border border rounded-md", children: _jsx(SelectList, { className: "text-start w-full", options: rules ?? [], value: selected, onChange: setSelected, optionLayout: (rule) => ({ label: rule.name }) }) }))] }), _jsxs(ModalFooter, { align: "right", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isStarting, children: "Cancel" }), _jsx(Button, { onClick: onStart, isDisabled: !selected || isStarting, isLoading: isStarting, children: "Start" })] })] }));
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=StartWorkflowButton.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StartWorkflowButton.js","sourceRoot":"","sources":["../../../../../../src/features/store/objects/selection/actions/StartWorkflowButton.tsx"],"names":[],"mappings":";AACA,OAAO,EACH,MAAM,EACN,KAAK,EACL,SAAS,EACT,WAAW,EACX,UAAU,EACV,UAAU,EACV,OAAO,EACP,QAAQ,EACR,QAAQ,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IAC/B,MAAM,EAAE,CAAC,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;IACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE1C,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,CACH,8BACI,KAAC,MAAM,IAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,YAAG,CAAC,CAAC,6BAA6B,CAAC,GAAU,EAChF,MAAM,IAAI,KAAC,kBAAkB,IAAC,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAI,IACtG,CACN,CAAC;AACN,CAAC;AAOD,SAAS,kBAAkB,CAAC,EAAE,SAAS,EAAE,OAAO,EAA2B;IACvE,MAAM,EAAE,CAAC,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAA+B,SAAS,CAAC,CAAC;IAClF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEpD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAE3F,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACvB,IAAI,CAAC,QAAQ,IAAI,UAAU;YAAE,OAAO;QACpC,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC;YACD,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;YACnE,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,+BAA+B,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACxF,OAAO,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,KAAK,CAAC;gBACF,KAAK,EAAE,CAAC,CAAC,qCAAqC,CAAC;gBAC/C,WAAW,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;gBAC3D,MAAM,EAAE,OAAO;gBACf,QAAQ,EAAE,IAAI;aACjB,CAAC,CAAC;QACP,CAAC;gBAAS,CAAC;YACP,aAAa,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,CACH,MAAC,KAAK,IAAC,MAAM,QAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAC,IAAI,aACrC,KAAC,UAAU,cAAE,CAAC,CAAC,mCAAmC,CAAC,GAAc,EACjE,MAAC,SAAS,eACN,cAAK,SAAS,EAAC,MAAM,YAAE,CAAC,CAAC,kCAAkC,CAAC,GAAO,EAClE,SAAS,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,0BAA0B,YACrC,KAAC,OAAO,KAAG,GACT,CACT,CAAC,CAAC,CAAC,CACA,cAAK,SAAS,EAAC,+DAA+D,YAC1E,KAAC,UAAU,IACP,SAAS,EAAC,mBAAmB,EAC7B,OAAO,EAAE,KAAK,IAAI,EAAE,EACpB,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,WAAW,EACrB,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,GAChD,GACA,CACT,IACO,EACZ,MAAC,WAAW,IAAC,KAAK,EAAC,OAAO,aACtB,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,uBAEvD,EACT,KAAC,MAAM,IAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,QAAQ,IAAI,UAAU,EAAE,SAAS,EAAE,UAAU,sBAE3E,IACC,IACV,CACX,CAAC;AACN,CAAC"}
|