@vertesia/ui 1.6.0-dev.20261004.085740Z → 1.6.0-dev.20261009.095813Z
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/README.md +24 -0
- package/lib/boot/vite.d.ts +3 -0
- package/lib/boot/vite.d.ts.map +1 -1
- package/lib/boot/vite.js +12 -0
- package/lib/boot/vite.js.map +1 -1
- package/lib/core/components/shadcn/button.d.ts +1 -0
- package/lib/core/components/shadcn/button.d.ts.map +1 -1
- package/lib/core/components/shadcn/button.js +2 -2
- package/lib/core/components/shadcn/button.js.map +1 -1
- package/lib/core/components/shadcn/selectBox.d.ts.map +1 -1
- package/lib/core/components/shadcn/selectBox.js +2 -2
- package/lib/core/components/shadcn/selectBox.js.map +1 -1
- package/lib/core/components/shadcn/tabs.d.ts +2 -1
- package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/core/components/shadcn/tabs.js +6 -4
- package/lib/core/components/shadcn/tabs.js.map +1 -1
- package/lib/env/index.d.ts +9 -1
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +1 -1
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.js +16 -10
- package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts +7 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.js +17 -16
- package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +2 -253
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts +1 -3
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js +2 -8
- package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/agentMarkdownStyles.d.ts +4 -0
- package/lib/features/agent/chat/ModernAgentOutput/agentMarkdownStyles.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/agentMarkdownStyles.js +248 -0
- package/lib/features/agent/chat/ModernAgentOutput/agentMarkdownStyles.js.map +1 -0
- package/lib/features/media-viewer/ImagePanel.d.ts +1 -1
- package/lib/features/media-viewer/ImagePanel.d.ts.map +1 -1
- package/lib/features/media-viewer/ImagePanel.js +40 -10
- package/lib/features/media-viewer/ImagePanel.js.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.js +1 -1
- package/lib/features/store/types/IntakePolicyEditor.js.map +1 -1
- package/lib/i18n/locales/ar.json +15 -0
- package/lib/i18n/locales/de.json +15 -0
- package/lib/i18n/locales/en.json +15 -0
- package/lib/i18n/locales/es.json +15 -0
- package/lib/i18n/locales/fr.json +15 -0
- package/lib/i18n/locales/it.json +15 -0
- package/lib/i18n/locales/ja.json +15 -0
- package/lib/i18n/locales/ko.json +15 -0
- package/lib/i18n/locales/pt.json +15 -0
- package/lib/i18n/locales/ru.json +15 -0
- package/lib/i18n/locales/tr.json +15 -0
- package/lib/i18n/locales/zh-TW.json +15 -0
- package/lib/i18n/locales/zh.json +15 -0
- package/lib/session/auth/composable.d.ts +1 -5
- package/lib/session/auth/composable.d.ts.map +1 -1
- package/lib/session/auth/composable.js +1 -10
- package/lib/session/auth/composable.js.map +1 -1
- package/lib/session/auth/firebase.d.ts.map +1 -1
- package/lib/session/auth/firebase.js +8 -5
- package/lib/session/auth/firebase.js.map +1 -1
- package/lib/session/auth/oauth.d.ts.map +1 -1
- package/lib/session/auth/oauth.js +43 -17
- package/lib/session/auth/oauth.js.map +1 -1
- package/lib/session/auth/vertesiaHosts.d.ts +0 -40
- package/lib/session/auth/vertesiaHosts.d.ts.map +1 -1
- package/lib/session/auth/vertesiaHosts.js +2 -45
- package/lib/session/auth/vertesiaHosts.js.map +1 -1
- package/lib/session/index.d.ts +1 -1
- package/lib/session/index.d.ts.map +1 -1
- package/lib/session/index.js +1 -1
- package/lib/session/index.js.map +1 -1
- package/lib/session/scopeSelection.d.ts +4 -0
- package/lib/session/scopeSelection.d.ts.map +1 -1
- package/lib/session/scopeSelection.js +11 -0
- package/lib/session/scopeSelection.js.map +1 -1
- package/lib/shell/AuthScreenPreview.d.ts.map +1 -1
- package/lib/shell/AuthScreenPreview.js +1 -0
- package/lib/shell/AuthScreenPreview.js.map +1 -1
- package/lib/shell/login/SignInFlow.d.ts +3 -1
- package/lib/shell/login/SignInFlow.d.ts.map +1 -1
- package/lib/shell/login/SignInFlow.js +13 -4
- package/lib/shell/login/SignInFlow.js.map +1 -1
- 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 +9 -3
- package/lib/shell/login/SigninScreen.js.map +1 -1
- package/lib/shell/login/signInUtils.d.ts +24 -4
- package/lib/shell/login/signInUtils.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.js +53 -1
- package/lib/shell/login/signInUtils.js.map +1 -1
- package/lib/vertesia-ui-boot.js.map +1 -1
- package/lib/vertesia-ui-core.js +42 -42
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +574 -589
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.1NWiB6fw.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.1NWiB6fw.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.6Ozlf6Ks.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.6Ozlf6Ks.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.AttJpg8U.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.AttJpg8U.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BI_xdp49.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BI_xdp49.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.C-2EEX6w.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.C-2EEX6w.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CAmo54FO.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CAmo54FO.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CbBzmHNP.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CbBzmHNP.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DZgTzkIg.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DZgTzkIg.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DZsITxq5.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DZsITxq5.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Dbf2GHth.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Dbf2GHth.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.js +2 -2
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-i18n.lleWnH5n.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.lleWnH5n.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.wG_XSkbr.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.wG_XSkbr.chunk.js.map +1 -0
- package/lib/vertesia-ui-layout.js +15 -15
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +40 -40
- 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 +54 -47
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +47 -47
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/package.json +20 -20
- package/src/boot/vite.ts +15 -0
- package/src/core/components/shadcn/button.tsx +3 -2
- package/src/core/components/shadcn/selectBox.tsx +12 -4
- package/src/core/components/shadcn/tabs.tsx +9 -3
- package/src/css/index.css +1 -1
- package/src/css/typography.css +111 -88
- package/src/env/index.ts +10 -3
- package/src/features/agent/chat/AgentRequestInputOverlay.tsx +73 -34
- package/src/features/agent/chat/AskUserWidget.tsx +225 -206
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +2 -253
- package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.tsx +2 -8
- package/src/features/agent/chat/ModernAgentOutput/agentMarkdownStyles.ts +248 -0
- package/src/features/media-viewer/ImagePanel.tsx +51 -10
- package/src/features/store/types/IntakePolicyEditor.tsx +2 -1
- package/src/i18n/locales/ar.json +15 -0
- package/src/i18n/locales/de.json +15 -0
- package/src/i18n/locales/en.json +15 -0
- package/src/i18n/locales/es.json +15 -0
- package/src/i18n/locales/fr.json +15 -0
- package/src/i18n/locales/it.json +15 -0
- package/src/i18n/locales/ja.json +15 -0
- package/src/i18n/locales/ko.json +15 -0
- package/src/i18n/locales/pt.json +15 -0
- package/src/i18n/locales/ru.json +15 -0
- package/src/i18n/locales/tr.json +15 -0
- package/src/i18n/locales/zh-TW.json +15 -0
- package/src/i18n/locales/zh.json +15 -0
- package/src/session/auth/composable.ts +1 -11
- package/src/session/auth/firebase.ts +9 -6
- package/src/session/auth/oauth.ts +47 -16
- package/src/session/auth/vertesiaHosts.ts +2 -47
- package/src/session/index.ts +1 -1
- package/src/session/scopeSelection.ts +13 -0
- package/src/shell/AuthScreenPreview.tsx +1 -0
- package/src/shell/login/SignInFlow.tsx +23 -2
- package/src/shell/login/SignInIcons.tsx +2 -1
- 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 +8 -3
- package/src/shell/login/signInUtils.ts +68 -8
- package/lib/vertesia-ui-i18n.BCPusS2_.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BCPusS2_.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BHWlHchf.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BHWlHchf.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BkNx8SZu.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BkNx8SZu.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.C1LasLrO.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.C1LasLrO.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D8PQ5yKU.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D8PQ5yKU.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DUtVV1Bq.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DUtVV1Bq.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DgA36_k5.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DgA36_k5.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DmG-fjjZ.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DmG-fjjZ.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DreWvQIk.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DreWvQIk.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.Pp8Tj48d.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.Pp8Tj48d.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.gais--x5.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.gais--x5.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.xkqaKpY_.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.xkqaKpY_.chunk.js.map +0 -1
|
@@ -21,6 +21,8 @@ interface SignInReturningStepProps {
|
|
|
21
21
|
session: LastSuccessfulLogin;
|
|
22
22
|
onNotYou: () => void;
|
|
23
23
|
onProviderClicked: (provider: ProviderId) => void;
|
|
24
|
+
/** The address signs in with a password, so the parent shows the password step instead. */
|
|
25
|
+
onPasswordRequired: (email: string) => void;
|
|
24
26
|
redirectTo?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -28,6 +30,7 @@ export default function SignInReturningStep({
|
|
|
28
30
|
session,
|
|
29
31
|
onNotYou,
|
|
30
32
|
onProviderClicked,
|
|
33
|
+
onPasswordRequired,
|
|
31
34
|
redirectTo,
|
|
32
35
|
}: SignInReturningStepProps) {
|
|
33
36
|
const { t } = useUITranslation();
|
|
@@ -36,14 +39,23 @@ export default function SignInReturningStep({
|
|
|
36
39
|
const avatar = <SignInInitialsBadge initials={emailInitial(session.email)} />;
|
|
37
40
|
// A stored tenantName means we resolved the user's organization.
|
|
38
41
|
const hasTenant = !!session.tenantName;
|
|
42
|
+
const lastProvider = session.lastProvider;
|
|
39
43
|
const primaryLabel =
|
|
40
|
-
|
|
41
|
-
? t('auth.
|
|
42
|
-
:
|
|
44
|
+
lastProvider === 'password'
|
|
45
|
+
? t('auth.password.continue')
|
|
46
|
+
: lastProvider === 'oidc'
|
|
47
|
+
? t('auth.continueWithSignIn')
|
|
48
|
+
: t('auth.continueWithProvider', { provider: providerLabel(lastProvider) });
|
|
43
49
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
const continueWithLastProvider = async () => {
|
|
51
|
+
if (lastProvider === 'password') {
|
|
52
|
+
onPasswordRequired(session.email);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
onProviderClicked(lastProvider);
|
|
56
|
+
// The stored provider can be stale: the address may now resolve to a password tenant.
|
|
57
|
+
const result = await startSignIn(lastProvider, session.email, redirectTo);
|
|
58
|
+
if (!result.ok && result.reason === 'password-required') onPasswordRequired(session.email);
|
|
47
59
|
};
|
|
48
60
|
|
|
49
61
|
return (
|
|
@@ -76,9 +88,9 @@ export default function SignInReturningStep({
|
|
|
76
88
|
|
|
77
89
|
<div className="flex flex-col gap-2">
|
|
78
90
|
<SignInProviderButton
|
|
79
|
-
provider={
|
|
91
|
+
provider={lastProvider}
|
|
80
92
|
label={primaryLabel}
|
|
81
|
-
onClick={
|
|
93
|
+
onClick={continueWithLastProvider}
|
|
82
94
|
variant="filled"
|
|
83
95
|
/>
|
|
84
96
|
<SignInStepButton variant="ghost" onClick={onNotYou}>
|
|
@@ -8,13 +8,15 @@ import {
|
|
|
8
8
|
SignInStepLayout,
|
|
9
9
|
} from './SignInPrimitives';
|
|
10
10
|
import SignInWithProviderButton from './SignInWithProviderButton';
|
|
11
|
-
import { type
|
|
11
|
+
import { providerLabel, type RedirectProviderId } from './signInUtils';
|
|
12
12
|
|
|
13
13
|
interface SignInTenantStepProps {
|
|
14
14
|
email: string;
|
|
15
15
|
tenant: TenantInfo;
|
|
16
16
|
onBack: () => void;
|
|
17
17
|
onProviderClicked: () => void;
|
|
18
|
+
/** The tenant re-resolved as a password tenant, so the parent shows the password step. */
|
|
19
|
+
onPasswordRequired: (email: string) => void;
|
|
18
20
|
redirectTo?: string;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -32,12 +34,13 @@ export default function SignInTenantStep({
|
|
|
32
34
|
tenant,
|
|
33
35
|
onBack,
|
|
34
36
|
onProviderClicked,
|
|
37
|
+
onPasswordRequired,
|
|
35
38
|
redirectTo,
|
|
36
39
|
}: SignInTenantStepProps) {
|
|
37
40
|
const { t } = useUITranslation();
|
|
38
41
|
const tenantName = tenant.label || tenant.name || t('auth.blocked.tenantFallback');
|
|
39
42
|
// Brands keep their identity; anything else routes through OIDC.
|
|
40
|
-
const provider:
|
|
43
|
+
const provider: RedirectProviderId =
|
|
41
44
|
tenant.provider === 'google' || tenant.provider === 'github' || tenant.provider === 'microsoft'
|
|
42
45
|
? tenant.provider
|
|
43
46
|
: 'oidc';
|
|
@@ -68,6 +71,7 @@ export default function SignInTenantStep({
|
|
|
68
71
|
redirectTo={redirectTo}
|
|
69
72
|
variant="filled"
|
|
70
73
|
onClick={onProviderClicked}
|
|
74
|
+
onPasswordRequired={() => onPasswordRequired(email)}
|
|
71
75
|
/>
|
|
72
76
|
<SignInStepButton variant="ghost" onClick={onBack}>
|
|
73
77
|
{t('auth.tenant.notPartOf', { name: tenantName })}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { useUITranslation } from '@vertesia/ui/i18n';
|
|
2
2
|
import { SignInProviderButton } from './SignInPrimitives';
|
|
3
|
-
import { type
|
|
3
|
+
import { providerLabel, type RedirectProviderId, startSignIn, startSignInWithoutTenant } from './signInUtils';
|
|
4
4
|
|
|
5
5
|
interface SignInWithProviderButtonProps {
|
|
6
|
-
provider:
|
|
6
|
+
provider: RedirectProviderId;
|
|
7
7
|
/** Email for the tenant-aware flow. Omit for a no-tenant sign-in (e.g. SignInModal). */
|
|
8
8
|
email?: string;
|
|
9
9
|
redirectTo?: string;
|
|
10
10
|
variant?: 'outline' | 'filled';
|
|
11
11
|
/** Fired on click, before the redirect. */
|
|
12
12
|
onClick?: () => void;
|
|
13
|
+
/** Fired instead of a redirect when the email resolves to a password tenant. */
|
|
14
|
+
onPasswordRequired?: () => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
// "Continue with <provider>" button that owns its sign-in redirect.
|
|
@@ -19,6 +21,7 @@ export default function SignInWithProviderButton({
|
|
|
19
21
|
redirectTo,
|
|
20
22
|
variant = 'outline',
|
|
21
23
|
onClick,
|
|
24
|
+
onPasswordRequired,
|
|
22
25
|
}: SignInWithProviderButtonProps) {
|
|
23
26
|
const { t } = useUITranslation();
|
|
24
27
|
const label =
|
|
@@ -26,15 +29,16 @@ export default function SignInWithProviderButton({
|
|
|
26
29
|
? t('auth.continueWithSignIn')
|
|
27
30
|
: t('auth.continueWithProvider', { provider: providerLabel(provider) });
|
|
28
31
|
|
|
29
|
-
const signIn = () => {
|
|
32
|
+
const signIn = async () => {
|
|
30
33
|
onClick?.();
|
|
31
|
-
if (email) {
|
|
32
|
-
void startSignIn(provider, email, redirectTo);
|
|
33
|
-
} else {
|
|
34
|
+
if (!email) {
|
|
34
35
|
// OIDC needs a resolved tenant; the no-email path is only hit by no-tenant buttons.
|
|
35
36
|
startSignInWithoutTenant(provider, redirectTo);
|
|
37
|
+
return;
|
|
36
38
|
}
|
|
39
|
+
const result = await startSignIn(provider, email, redirectTo);
|
|
40
|
+
if (!result.ok && result.reason === 'password-required') onPasswordRequired?.();
|
|
37
41
|
};
|
|
38
42
|
|
|
39
|
-
return <SignInProviderButton provider={provider} label={label} onClick={signIn} variant={variant} />;
|
|
43
|
+
return <SignInProviderButton provider={provider} label={label} onClick={() => void signIn()} variant={variant} />;
|
|
40
44
|
}
|
|
@@ -18,7 +18,7 @@ import SignInRecoveryStep, { type SignInRecoveryKind } from './SignInRecoverySte
|
|
|
18
18
|
import SignInRestrictedEnvStep from './SignInRestrictedEnvStep';
|
|
19
19
|
import SignInTenantBlockedStep from './SignInTenantBlockedStep';
|
|
20
20
|
import SignupForm from './SignupForm';
|
|
21
|
-
import { isInviteRequiredError, readPendingSignin, resetSignInState } from './signInUtils';
|
|
21
|
+
import { isInviteRequiredError, isTenantSignIn, readPendingSignin, resetSignInState } from './signInUtils';
|
|
22
22
|
|
|
23
23
|
/** Presentation only: the shared screen still owns session gating and recovery transitions. */
|
|
24
24
|
export interface SignInScreenViewProps {
|
|
@@ -152,7 +152,12 @@ function SigninScreenImpl({
|
|
|
152
152
|
useEffect(() => {
|
|
153
153
|
if (!authError) return;
|
|
154
154
|
if (authError instanceof UserNotFoundError) {
|
|
155
|
-
|
|
155
|
+
if (isTenantSignIn()) {
|
|
156
|
+
setEmail(authError.email);
|
|
157
|
+
setMode('blocked');
|
|
158
|
+
} else {
|
|
159
|
+
setMode('signup');
|
|
160
|
+
}
|
|
156
161
|
} else if (authError instanceof RestrictedEnvironmentError) {
|
|
157
162
|
setMode('restricted');
|
|
158
163
|
} else if (authError instanceof RequestedScopeUnavailableError) {
|
|
@@ -280,7 +285,7 @@ function SigninScreenImpl({
|
|
|
280
285
|
onUseDifferentAccount={useDifferentAccount}
|
|
281
286
|
/>
|
|
282
287
|
);
|
|
283
|
-
} else if (mode === 'signup' && !
|
|
288
|
+
} else if (mode === 'signup' && !isTenantSignIn()) {
|
|
284
289
|
content = <SignupForm onSignup={onSignup} goBack={startOver} />;
|
|
285
290
|
} else {
|
|
286
291
|
// Every remaining mode is a core one; an unmatched recovery mode falls through to the
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { Env } from '@vertesia/ui/env';
|
|
2
2
|
import { getFirebaseAuth, setFirebaseTenant } from '@vertesia/ui/session';
|
|
3
|
+
import { FirebaseError } from 'firebase/app';
|
|
3
4
|
import {
|
|
4
5
|
type AuthProvider,
|
|
5
6
|
signOut as firebaseSignOut,
|
|
6
7
|
GithubAuthProvider,
|
|
7
8
|
GoogleAuthProvider,
|
|
8
9
|
OAuthProvider,
|
|
10
|
+
signInWithEmailAndPassword,
|
|
9
11
|
signInWithRedirect,
|
|
10
12
|
} from 'firebase/auth';
|
|
11
13
|
|
|
12
14
|
// Matches auth-tenants.json `provider` values. Tenant context is derived from
|
|
13
15
|
// email resolution at sign-in time, not encoded here.
|
|
14
|
-
export type ProviderId = 'google' | 'github' | 'microsoft' | 'oidc';
|
|
16
|
+
export type ProviderId = 'google' | 'github' | 'microsoft' | 'oidc' | 'password';
|
|
17
|
+
|
|
18
|
+
/** Providers that sign in by redirecting to an identity provider. `password` signs in in place. */
|
|
19
|
+
export type RedirectProviderId = Exclude<ProviderId, 'password'>;
|
|
15
20
|
|
|
16
21
|
export interface LastSuccessfulLogin {
|
|
17
22
|
email: string;
|
|
@@ -82,6 +87,13 @@ export function clearPendingSignin(): void {
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Detects a tenant sign-in by the key's presence, including tenants with an empty label or name.
|
|
92
|
+
*/
|
|
93
|
+
export function isTenantSignIn(): boolean {
|
|
94
|
+
return localStorage.getItem('tenantName') !== null;
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
/**
|
|
86
98
|
* Promotes the pending sign-in record to the last-successful-login entry, stamping the
|
|
87
99
|
* authenticated user's display name.
|
|
@@ -166,28 +178,28 @@ const buildOidcProvider: ProviderBuilder = (email) => {
|
|
|
166
178
|
return p;
|
|
167
179
|
};
|
|
168
180
|
|
|
169
|
-
const PROVIDER_BUILDERS: Record<
|
|
181
|
+
const PROVIDER_BUILDERS: Record<RedirectProviderId, ProviderBuilder> = {
|
|
170
182
|
google: buildGoogleProvider,
|
|
171
183
|
github: buildGithubProvider,
|
|
172
184
|
microsoft: buildMicrosoftProvider,
|
|
173
185
|
oidc: buildOidcProvider,
|
|
174
186
|
};
|
|
175
187
|
|
|
176
|
-
function buildFirebaseProvider(idp:
|
|
188
|
+
function buildFirebaseProvider(idp: RedirectProviderId, email?: string, redirectTo?: string): AuthProvider {
|
|
177
189
|
return PROVIDER_BUILDERS[idp](email, redirectTo);
|
|
178
190
|
}
|
|
179
191
|
|
|
180
192
|
export async function startSignIn(
|
|
181
|
-
provider:
|
|
193
|
+
provider: RedirectProviderId,
|
|
182
194
|
email: string,
|
|
183
195
|
redirectTo?: string,
|
|
184
|
-
): Promise<{ ok: true } | { ok: false; reason: 'no-email' }> {
|
|
196
|
+
): Promise<{ ok: true } | { ok: false; reason: 'no-email' | 'password-required' }> {
|
|
185
197
|
if (!email) return { ok: false, reason: 'no-email' };
|
|
186
198
|
|
|
187
199
|
// A tenant-mapped email uses the tenant's provider (overriding `provider`); otherwise use `provider`.
|
|
188
200
|
const tenant = await setFirebaseTenant(email);
|
|
189
201
|
const auth = getFirebaseAuth();
|
|
190
|
-
let effectiveIdp = provider;
|
|
202
|
+
let effectiveIdp: ProviderId = provider;
|
|
191
203
|
let tenantName: string | undefined;
|
|
192
204
|
|
|
193
205
|
if (tenant) {
|
|
@@ -201,20 +213,68 @@ export async function startSignIn(
|
|
|
201
213
|
auth.tenantId = Env.firebase?.tenantId ?? null;
|
|
202
214
|
}
|
|
203
215
|
|
|
216
|
+
// A password tenant has no identity provider to redirect to; its step collects the password.
|
|
217
|
+
if (effectiveIdp === 'password') return { ok: false, reason: 'password-required' };
|
|
218
|
+
|
|
204
219
|
writePendingSignin({ email, provider: effectiveIdp, tenantName });
|
|
205
220
|
void signInWithRedirect(auth, buildFirebaseProvider(effectiveIdp, email, redirectTo));
|
|
206
221
|
return { ok: true };
|
|
207
222
|
}
|
|
208
223
|
|
|
224
|
+
export type PasswordSignInFailure = 'not-password-tenant' | 'invalid-credentials' | 'too-many-attempts' | 'failed';
|
|
225
|
+
|
|
226
|
+
// Firebase reports an unknown user, a wrong password and a disabled user with distinct codes on
|
|
227
|
+
// some SDK versions; they are folded into one answer so the form does not reveal which it was.
|
|
228
|
+
const INVALID_CREDENTIAL_CODES = new Set([
|
|
229
|
+
'auth/invalid-credential',
|
|
230
|
+
'auth/invalid-email',
|
|
231
|
+
'auth/user-disabled',
|
|
232
|
+
'auth/user-not-found',
|
|
233
|
+
'auth/wrong-password',
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
export function passwordFailureReason(err: unknown): Exclude<PasswordSignInFailure, 'not-password-tenant'> {
|
|
237
|
+
if (!(err instanceof FirebaseError)) return 'failed';
|
|
238
|
+
if (INVALID_CREDENTIAL_CODES.has(err.code)) return 'invalid-credentials';
|
|
239
|
+
if (err.code === 'auth/too-many-requests') return 'too-many-attempts';
|
|
240
|
+
return 'failed';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Signs in a user of a password tenant, in place — there is no redirect. Only an address whose
|
|
245
|
+
* domain resolves to a tenant configured for password sign-in may use it; the tenant is resolved
|
|
246
|
+
* again here rather than trusted from the caller, so a returning session cannot carry a stale one.
|
|
247
|
+
* On success the session's auth-state listener completes the login.
|
|
248
|
+
*/
|
|
249
|
+
export async function signInWithPassword(
|
|
250
|
+
email: string,
|
|
251
|
+
password: string,
|
|
252
|
+
): Promise<{ ok: true } | { ok: false; reason: PasswordSignInFailure }> {
|
|
253
|
+
const tenant = await setFirebaseTenant(email);
|
|
254
|
+
if (!tenant) return { ok: false, reason: 'failed' };
|
|
255
|
+
if (tenant.provider !== 'password') return { ok: false, reason: 'not-password-tenant' };
|
|
256
|
+
|
|
257
|
+
const tenantName = tenant.label || tenant.name || undefined;
|
|
258
|
+
localStorage.setItem('tenantName', tenantName ?? '');
|
|
259
|
+
writePendingSignin({ email, provider: 'password', tenantName });
|
|
260
|
+
try {
|
|
261
|
+
await signInWithEmailAndPassword(getFirebaseAuth(), email, password);
|
|
262
|
+
return { ok: true };
|
|
263
|
+
} catch (err: unknown) {
|
|
264
|
+
clearPendingSignin();
|
|
265
|
+
return { ok: false, reason: passwordFailureReason(err) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
209
269
|
/** Starts sign-in without email discovery, preserving an explicitly configured tenant. */
|
|
210
|
-
export function startSignInWithoutTenant(provider:
|
|
270
|
+
export function startSignInWithoutTenant(provider: RedirectProviderId, redirectTo?: string): void {
|
|
211
271
|
const auth = getFirebaseAuth();
|
|
212
272
|
localStorage.removeItem('tenantName');
|
|
213
273
|
auth.tenantId = Env.firebase?.tenantId ?? null;
|
|
214
274
|
void signInWithRedirect(
|
|
215
275
|
auth,
|
|
216
276
|
buildFirebaseProvider(
|
|
217
|
-
Env.firebase?.tenantId ? ((Env.firebase.providerType as
|
|
277
|
+
Env.firebase?.tenantId ? ((Env.firebase.providerType as RedirectProviderId) ?? 'oidc') : provider,
|
|
218
278
|
undefined,
|
|
219
279
|
redirectTo,
|
|
220
280
|
),
|