Skip to content

Commit c19fc4b

Browse files
authored
Add generic fallback handling (#520)
* Add generic fallback handling * Fix error message * Stop wrong login error collection * Manage cui loading state
1 parent 9b0f9a6 commit c19fc4b

File tree

11 files changed

+159
-98
lines changed

11 files changed

+159
-98
lines changed

packages/connect-react/src/components/login-second-factor/InitScreen.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ConnectUserNotFound, PasskeyChallengeCancelledError, PasskeyLoginSource } from '@corbado/web-core';
1+
import { PasskeyChallengeCancelledError, PasskeyLoginSource } from '@corbado/web-core';
22
import type { ConnectLoginStartRsp } from '@corbado/web-core/dist/api/v2';
33
import log from 'loglevel';
44
import React, { useEffect, useRef, useState } from 'react';
@@ -79,10 +79,6 @@ const InitScreen = () => {
7979
return;
8080
}
8181

82-
if (resStart.val instanceof ConnectUserNotFound) {
83-
return handleSituation(LoginSituationCode.PreAuthenticatorUserNotFound);
84-
}
85-
8682
return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
8783
}
8884

packages/connect-react/src/components/login/LoginErrorScreenHard.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import useShared from '../../hooks/useShared';
77
import { LoginScreenType } from '../../types/screenTypes';
88
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
99
import LoginErrorHard from './base/LoginErrorHard';
10-
import { connectLoginFinishToComplete } from './LoginInitScreen';
10+
import { type CboApiFallbackOperationError, connectLoginFinishToComplete } from './LoginInitScreen';
1111

1212
type Props = {
1313
previousAssertionOptions: string;
@@ -32,6 +32,16 @@ const LoginErrorScreenHard = ({ previousAssertionOptions }: Props) => {
3232
return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
3333
}
3434

35+
if (resStart.val.assertionOptions.length === 0) {
36+
const data: CboApiFallbackOperationError = {
37+
initFallback: resStart.val.fallbackOperationError.initFallback,
38+
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
39+
message: resStart.val.fallbackOperationError.error?.message ?? null,
40+
};
41+
42+
return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
43+
}
44+
3545
setAssertionOptions(resStart.val.assertionOptions);
3646

3747
const resFinish = await getConnectService().loginContinue(resStart.val);
@@ -56,7 +66,7 @@ const LoginErrorScreenHard = ({ previousAssertionOptions }: Props) => {
5666
}
5767
};
5868

59-
const handleSituation = (situationCode: LoginSituationCode) => {
69+
const handleSituation = (situationCode: LoginSituationCode, data?: unknown) => {
6070
const messageCode = `situation: ${situationCode}`;
6171
log.debug(messageCode);
6272

@@ -92,6 +102,17 @@ const LoginErrorScreenHard = ({ previousAssertionOptions }: Props) => {
92102

93103
void getConnectService().recordEventLoginExplicitAbort(assertionOptions);
94104
break;
105+
case LoginSituationCode.CboApiFallbackOperationError: {
106+
const { initFallback, identifierFallback, message } = data as CboApiFallbackOperationError;
107+
if (initFallback) {
108+
navigateToScreen(LoginScreenType.Invisible);
109+
fallback(identifierFallback, message);
110+
}
111+
void getConnectService().recordEventLoginError(messageCode);
112+
113+
setLoading(false);
114+
break;
115+
}
95116
}
96117
};
97118

packages/connect-react/src/components/login/LoginErrorScreenSoft.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import useShared from '../../hooks/useShared';
77
import { LoginScreenType } from '../../types/screenTypes';
88
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
99
import LoginErrorSoft from './base/LoginErrorSoft';
10+
import type { CboApiFallbackOperationError } from './LoginInitScreen';
1011
import { connectLoginFinishToComplete } from './LoginInitScreen';
1112

1213
type Props = {
@@ -30,6 +31,16 @@ const LoginErrorScreenSoft = ({ previousAssertionOptions }: Props) => {
3031
return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
3132
}
3233

34+
if (resStart.val.assertionOptions.length === 0) {
35+
const data: CboApiFallbackOperationError = {
36+
initFallback: resStart.val.fallbackOperationError.initFallback,
37+
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
38+
message: resStart.val.fallbackOperationError.error?.message ?? null,
39+
};
40+
41+
return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
42+
}
43+
3344
const resFinish = await getConnectService().loginContinue(resStart.val);
3445
if (resFinish.err) {
3546
if (resFinish.val instanceof PasskeyChallengeCancelledError) {
@@ -55,6 +66,7 @@ const LoginErrorScreenSoft = ({ previousAssertionOptions }: Props) => {
5566
const message = getLoginErrorMessage(situationCode);
5667

5768
switch (situationCode) {
69+
case LoginSituationCode.CboApiNotAvailablePreAuthenticator:
5870
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
5971
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
6072
navigateToScreen(LoginScreenType.Invisible);
@@ -79,6 +91,17 @@ const LoginErrorScreenSoft = ({ previousAssertionOptions }: Props) => {
7991
void getConnectService().recordEventLoginExplicitAbort(previousAssertionOptions);
8092
break;
8193
}
94+
case LoginSituationCode.CboApiFallbackOperationError: {
95+
const { initFallback, identifierFallback, message } = data as CboApiFallbackOperationError;
96+
if (initFallback) {
97+
navigateToScreen(LoginScreenType.Invisible);
98+
fallback(identifierFallback, message);
99+
}
100+
void getConnectService().recordEventLoginError(messageCode);
101+
102+
setLoading(false);
103+
break;
104+
}
82105
}
83106
};
84107

packages/connect-react/src/components/login/LoginInitScreen.tsx

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import {
2-
ConnectConditionalUIPasskeyDeleted,
3-
ConnectCustomError,
4-
ConnectExistingPasskeysNotAvailable,
5-
ConnectNoPasskeyAvailableError,
6-
ConnectUserNotFound,
7-
PasskeyChallengeCancelledError,
8-
PasskeyLoginSource,
9-
} from '@corbado/web-core';
1+
import { PasskeyChallengeCancelledError, PasskeyLoginSource } from '@corbado/web-core';
102
import type { ConnectLoginFinishRsp } from '@corbado/web-core/dist/api/v2';
113
import log from 'loglevel';
124
import type { FC } from 'react';
@@ -16,12 +8,17 @@ import useLoginProcess from '../../hooks/useLoginProcess';
168
import useShared from '../../hooks/useShared';
179
import { Flags } from '../../types/flags';
1810
import { LoginScreenType } from '../../types/screenTypes';
19-
import type { PreAuthenticatorCustomErrorData } from '../../types/situations';
2011
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
2112
import { StatefulLoader } from '../../utils/statefulLoader';
2213
import LoginInitLoaded from './base/LoginInitLoaded';
2314
import LoginInitLoading from './base/LoginInitLoading';
2415

16+
export type CboApiFallbackOperationError = {
17+
initFallback: boolean;
18+
identifierFallback: string;
19+
message: string | null;
20+
};
21+
2522
export enum LoginInitState {
2623
SilentLoading,
2724
Loading,
@@ -42,8 +39,7 @@ export const connectLoginFinishToComplete = (v: ConnectLoginFinishRsp): string =
4239
};
4340

4441
const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
45-
const { config, navigateToScreen, setCurrentIdentifier, setFlags, flags, loadedMs, fallback, fallbackCustom } =
46-
useLoginProcess();
42+
const { config, navigateToScreen, setCurrentIdentifier, setFlags, flags, loadedMs, fallback } = useLoginProcess();
4743
const { sharedConfig, getConnectService } = useShared();
4844
const [cuiBasedLoading, setCuiBasedLoading] = useState(false);
4945
const [identifierBasedLoading, setIdentifierBasedLoading] = useState(false);
@@ -160,11 +156,6 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
160156
return handleSituation(LoginSituationCode.ClientPasskeyConditionalOperationCancelled);
161157
}
162158

163-
// if a passkey has been deleted, CUI will fail => fallback with message
164-
if (res.val instanceof ConnectConditionalUIPasskeyDeleted) {
165-
return handleSituation(LoginSituationCode.PasskeyNotAvailablePostConditionalAuthenticator);
166-
}
167-
168159
// cuiStarted === true indicates that user has passed the authenticator
169160
if (cuiStarted) {
170161
return handleSituation(LoginSituationCode.CboApiNotAvailablePostConditionalAuthenticator);
@@ -173,6 +164,16 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
173164
return handleSituation(LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator);
174165
}
175166

167+
if (res.val.fallbackOperationError) {
168+
const data: CboApiFallbackOperationError = {
169+
initFallback: res.val.fallbackOperationError.initFallback,
170+
identifierFallback: res.val.fallbackOperationError.identifier ?? '',
171+
message: res.val.fallbackOperationError.error?.message ?? null,
172+
};
173+
174+
return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
175+
}
176+
176177
try {
177178
await config.onComplete(connectLoginFinishToComplete(res.val));
178179
} catch {
@@ -191,19 +192,6 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
191192

192193
const resStart = await getConnectService().loginStart(identifier, PasskeyLoginSource.TextField, loadedMs);
193194
if (resStart.err) {
194-
if (resStart.val instanceof ConnectUserNotFound) {
195-
return handleSituation(LoginSituationCode.PreAuthenticatorUserNotFound);
196-
}
197-
if (resStart.val instanceof ConnectCustomError) {
198-
return handleSituation(LoginSituationCode.PreAuthenticatorCustomError, resStart.val);
199-
}
200-
if (resStart.val instanceof ConnectExistingPasskeysNotAvailable) {
201-
return handleSituation(LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable);
202-
}
203-
if (resStart.val instanceof ConnectNoPasskeyAvailableError) {
204-
return handleSituation(LoginSituationCode.PreAuthenticatorNoPasskeyAvailable);
205-
}
206-
207195
return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
208196
}
209197

@@ -212,6 +200,16 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
212200
return;
213201
}
214202

203+
if (resStart.val.assertionOptions.length === 0) {
204+
const data: CboApiFallbackOperationError = {
205+
initFallback: resStart.val.fallbackOperationError.initFallback,
206+
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
207+
message: resStart.val.fallbackOperationError.error?.message ?? null,
208+
};
209+
210+
return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
211+
}
212+
215213
const res = await getConnectService().loginContinue(resStart.val);
216214
if (res.err) {
217215
setIdentifierBasedLoading(false);
@@ -255,13 +253,10 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
255253
statefulLoader.current.finish();
256254
break;
257255
case LoginSituationCode.DeniedByPartialRollout:
258-
case LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable:
259-
case LoginSituationCode.PreAuthenticatorNoPasskeyAvailable:
260256
automaticFallback(identifier, message);
261257

262258
statefulLoader.current.finish();
263259
break;
264-
case LoginSituationCode.PasskeyNotAvailablePostConditionalAuthenticator:
265260
case LoginSituationCode.CboApiNotAvailablePostConditionalAuthenticator:
266261
case LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator:
267262
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
@@ -280,26 +275,22 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
280275
setIdentifierBasedLoading(false);
281276
break;
282277
}
283-
case LoginSituationCode.PreAuthenticatorUserNotFound:
284-
setError(message ?? '');
285-
void getConnectService().recordEventLoginErrorUnexpected(messageCode);
286-
287-
setIdentifierBasedLoading(false);
288-
break;
289278
case LoginSituationCode.ExplicitFallbackByUser:
290279
explicitFallback();
291280

292281
void getConnectService().recordEventLoginExplicitAbort();
293282
break;
294-
case LoginSituationCode.PreAuthenticatorCustomError: {
295-
navigateToScreen(LoginScreenType.Invisible);
296-
void getConnectService().recordEventLoginErrorUnexpected(messageCode);
297-
if (!data) {
298-
return fallback(identifier, null);
283+
case LoginSituationCode.CboApiFallbackOperationError: {
284+
const typed = data as CboApiFallbackOperationError;
285+
286+
if (typed.initFallback) {
287+
return automaticFallback(typed.identifierFallback, typed.message);
299288
}
300289

301-
const typed = data as PreAuthenticatorCustomErrorData;
302-
fallbackCustom(identifier, typed.code, typed.message);
290+
setError(typed.message ?? '');
291+
setCuiBasedLoading(false);
292+
setIdentifierBasedLoading(false);
293+
break;
303294
}
304295
}
305296
};

packages/connect-react/src/components/login/LoginPasskeyReLoginScreen.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import useShared from '../../hooks/useShared';
77
import { LoginScreenType } from '../../types/screenTypes';
88
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
99
import LoginOneTap from './base/LoginOneTap';
10-
import { connectLoginFinishToComplete } from './LoginInitScreen';
10+
import { type CboApiFallbackOperationError, connectLoginFinishToComplete } from './LoginInitScreen';
1111

1212
export const LoginPasskeyReLoginScreen = () => {
1313
const { config, navigateToScreen, setCurrentIdentifier, currentIdentifier, loadedMs, fallback } = useLoginProcess();
@@ -32,6 +32,16 @@ export const LoginPasskeyReLoginScreen = () => {
3232
return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
3333
}
3434

35+
if (resStart.val.assertionOptions.length === 0) {
36+
const data = {
37+
initFallback: resStart.val.fallbackOperationError.initFallback,
38+
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
39+
message: resStart.val.fallbackOperationError.error?.message ?? null,
40+
};
41+
42+
return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
43+
}
44+
3545
const resFinish = await getConnectService().loginContinue(resStart.val);
3646
if (resFinish.err) {
3747
if (resFinish.val instanceof PasskeyChallengeCancelledError) {
@@ -53,7 +63,7 @@ export const LoginPasskeyReLoginScreen = () => {
5363
navigateToScreen(LoginScreenType.Init, { prefilledIdentifier: identifier });
5464
};
5565

56-
const handleSituation = (situationCode: LoginSituationCode) => {
66+
const handleSituation = (situationCode: LoginSituationCode, data?: unknown) => {
5767
const messageCode = `situation: ${situationCode}`;
5868
log.debug(messageCode);
5969

@@ -77,6 +87,17 @@ export const LoginPasskeyReLoginScreen = () => {
7787

7888
setLoading(false);
7989
break;
90+
case LoginSituationCode.CboApiFallbackOperationError: {
91+
const { initFallback, identifierFallback, message } = data as CboApiFallbackOperationError;
92+
if (initFallback) {
93+
navigateToScreen(LoginScreenType.Invisible);
94+
fallback(identifierFallback, message);
95+
}
96+
void getConnectService().recordEventLoginError(messageCode);
97+
98+
setLoading(false);
99+
break;
100+
}
80101
}
81102
};
82103

packages/connect-react/src/types/situations.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum LoginSituationCode {
1414
PreAuthenticatorCustomError,
1515
PreAuthenticatorExistingPasskeysNotAvailable,
1616
PreAuthenticatorNoPasskeyAvailable,
17+
CboApiFallbackOperationError,
1718
}
1819

1920
export enum AppendSituationCode {
@@ -41,11 +42,6 @@ export enum PasskeyListSituationCode {
4142
CboApiPasskeysNotSupportedLight,
4243
}
4344

44-
export type PreAuthenticatorCustomErrorData = {
45-
code: string;
46-
message: string;
47-
};
48-
4945
export const getLoginErrorMessage = (code: LoginSituationCode): string | null => {
5046
switch (code) {
5147
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
@@ -54,12 +50,6 @@ export const getLoginErrorMessage = (code: LoginSituationCode): string | null =>
5450
case LoginSituationCode.ClientPasskeyOperationCancelledTooManyTimes:
5551
return "We couldn't log you in with your passkey due to a system error. Use your password to log in instead.";
5652

57-
case LoginSituationCode.PasskeyNotAvailablePostConditionalAuthenticator:
58-
return 'You previously deleted this passkey. Use your password to log in instead.';
59-
60-
case LoginSituationCode.PreAuthenticatorUserNotFound:
61-
return 'There is no account registered to that email address.';
62-
6353
default:
6454
return null;
6555
}

0 commit comments

Comments
 (0)