Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConnectUserNotFound, PasskeyChallengeCancelledError, PasskeyLoginSource } from '@corbado/web-core';
import { PasskeyChallengeCancelledError, PasskeyLoginSource } from '@corbado/web-core';
import type { ConnectLoginStartRsp } from '@corbado/web-core/dist/api/v2';
import log from 'loglevel';
import React, { useEffect, useRef, useState } from 'react';
Expand Down Expand Up @@ -79,10 +79,6 @@ const InitScreen = () => {
return;
}

if (resStart.val instanceof ConnectUserNotFound) {
return handleSituation(LoginSituationCode.PreAuthenticatorUserNotFound);
}

return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginErrorHard from './base/LoginErrorHard';
import { connectLoginFinishToComplete } from './LoginInitScreen';
import { type CboApiFallbackOperationError, connectLoginFinishToComplete } from './LoginInitScreen';

type Props = {
previousAssertionOptions: string;
Expand All @@ -32,6 +32,16 @@ const LoginErrorScreenHard = ({ previousAssertionOptions }: Props) => {
return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
}

if (resStart.val.assertionOptions.length === 0) {
const data: CboApiFallbackOperationError = {
initFallback: resStart.val.fallbackOperationError.initFallback,
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
message: resStart.val.fallbackOperationError.error?.message ?? null,
};

return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
}

setAssertionOptions(resStart.val.assertionOptions);

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

const handleSituation = (situationCode: LoginSituationCode) => {
const handleSituation = (situationCode: LoginSituationCode, data?: unknown) => {
const messageCode = `situation: ${situationCode}`;
log.debug(messageCode);

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

void getConnectService().recordEventLoginExplicitAbort(assertionOptions);
break;
case LoginSituationCode.CboApiFallbackOperationError: {
const { initFallback, identifierFallback, message } = data as CboApiFallbackOperationError;
if (initFallback) {
navigateToScreen(LoginScreenType.Invisible);
fallback(identifierFallback, message);
}
void getConnectService().recordEventLoginError(messageCode);

setLoading(false);
break;
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginErrorSoft from './base/LoginErrorSoft';
import type { CboApiFallbackOperationError } from './LoginInitScreen';
import { connectLoginFinishToComplete } from './LoginInitScreen';

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

if (resStart.val.assertionOptions.length === 0) {
const data: CboApiFallbackOperationError = {
initFallback: resStart.val.fallbackOperationError.initFallback,
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
message: resStart.val.fallbackOperationError.error?.message ?? null,
};

return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
}

const resFinish = await getConnectService().loginContinue(resStart.val);
if (resFinish.err) {
if (resFinish.val instanceof PasskeyChallengeCancelledError) {
Expand All @@ -55,6 +66,7 @@ const LoginErrorScreenSoft = ({ previousAssertionOptions }: Props) => {
const message = getLoginErrorMessage(situationCode);

switch (situationCode) {
case LoginSituationCode.CboApiNotAvailablePreAuthenticator:
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
navigateToScreen(LoginScreenType.Invisible);
Expand All @@ -79,6 +91,17 @@ const LoginErrorScreenSoft = ({ previousAssertionOptions }: Props) => {
void getConnectService().recordEventLoginExplicitAbort(previousAssertionOptions);
break;
}
case LoginSituationCode.CboApiFallbackOperationError: {
const { initFallback, identifierFallback, message } = data as CboApiFallbackOperationError;
if (initFallback) {
navigateToScreen(LoginScreenType.Invisible);
fallback(identifierFallback, message);
}
void getConnectService().recordEventLoginError(messageCode);

setLoading(false);
break;
}
}
};

Expand Down
83 changes: 37 additions & 46 deletions packages/connect-react/src/components/login/LoginInitScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import {
ConnectConditionalUIPasskeyDeleted,
ConnectCustomError,
ConnectExistingPasskeysNotAvailable,
ConnectNoPasskeyAvailableError,
ConnectUserNotFound,
PasskeyChallengeCancelledError,
PasskeyLoginSource,
} from '@corbado/web-core';
import { PasskeyChallengeCancelledError, PasskeyLoginSource } from '@corbado/web-core';
import type { ConnectLoginFinishRsp } from '@corbado/web-core/dist/api/v2';
import log from 'loglevel';
import type { FC } from 'react';
Expand All @@ -16,12 +8,17 @@ import useLoginProcess from '../../hooks/useLoginProcess';
import useShared from '../../hooks/useShared';
import { Flags } from '../../types/flags';
import { LoginScreenType } from '../../types/screenTypes';
import type { PreAuthenticatorCustomErrorData } from '../../types/situations';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import { StatefulLoader } from '../../utils/statefulLoader';
import LoginInitLoaded from './base/LoginInitLoaded';
import LoginInitLoading from './base/LoginInitLoading';

export type CboApiFallbackOperationError = {
initFallback: boolean;
identifierFallback: string;
message: string | null;
};

export enum LoginInitState {
SilentLoading,
Loading,
Expand All @@ -42,8 +39,7 @@ export const connectLoginFinishToComplete = (v: ConnectLoginFinishRsp): string =
};

const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
const { config, navigateToScreen, setCurrentIdentifier, setFlags, flags, loadedMs, fallback, fallbackCustom } =
useLoginProcess();
const { config, navigateToScreen, setCurrentIdentifier, setFlags, flags, loadedMs, fallback } = useLoginProcess();
const { sharedConfig, getConnectService } = useShared();
const [cuiBasedLoading, setCuiBasedLoading] = useState(false);
const [identifierBasedLoading, setIdentifierBasedLoading] = useState(false);
Expand Down Expand Up @@ -160,11 +156,6 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
return handleSituation(LoginSituationCode.ClientPasskeyConditionalOperationCancelled);
}

// if a passkey has been deleted, CUI will fail => fallback with message
if (res.val instanceof ConnectConditionalUIPasskeyDeleted) {
return handleSituation(LoginSituationCode.PasskeyNotAvailablePostConditionalAuthenticator);
}

// cuiStarted === true indicates that user has passed the authenticator
if (cuiStarted) {
return handleSituation(LoginSituationCode.CboApiNotAvailablePostConditionalAuthenticator);
Expand All @@ -173,6 +164,16 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
return handleSituation(LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator);
}

if (res.val.fallbackOperationError) {
const data: CboApiFallbackOperationError = {
initFallback: res.val.fallbackOperationError.initFallback,
identifierFallback: res.val.fallbackOperationError.identifier ?? '',
message: res.val.fallbackOperationError.error?.message ?? null,
};

return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
}

try {
await config.onComplete(connectLoginFinishToComplete(res.val));
} catch {
Expand All @@ -191,19 +192,6 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {

const resStart = await getConnectService().loginStart(identifier, PasskeyLoginSource.TextField, loadedMs);
if (resStart.err) {
if (resStart.val instanceof ConnectUserNotFound) {
return handleSituation(LoginSituationCode.PreAuthenticatorUserNotFound);
}
if (resStart.val instanceof ConnectCustomError) {
return handleSituation(LoginSituationCode.PreAuthenticatorCustomError, resStart.val);
}
if (resStart.val instanceof ConnectExistingPasskeysNotAvailable) {
return handleSituation(LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable);
}
if (resStart.val instanceof ConnectNoPasskeyAvailableError) {
return handleSituation(LoginSituationCode.PreAuthenticatorNoPasskeyAvailable);
}

return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
}

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

if (resStart.val.assertionOptions.length === 0) {
const data: CboApiFallbackOperationError = {
initFallback: resStart.val.fallbackOperationError.initFallback,
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
message: resStart.val.fallbackOperationError.error?.message ?? null,
};

return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
}

const res = await getConnectService().loginContinue(resStart.val);
if (res.err) {
setIdentifierBasedLoading(false);
Expand Down Expand Up @@ -255,13 +253,10 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
statefulLoader.current.finish();
break;
case LoginSituationCode.DeniedByPartialRollout:
case LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable:
case LoginSituationCode.PreAuthenticatorNoPasskeyAvailable:
automaticFallback(identifier, message);

statefulLoader.current.finish();
break;
case LoginSituationCode.PasskeyNotAvailablePostConditionalAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostConditionalAuthenticator:
case LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator:
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
Expand All @@ -280,26 +275,22 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
setIdentifierBasedLoading(false);
break;
}
case LoginSituationCode.PreAuthenticatorUserNotFound:
setError(message ?? '');
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setIdentifierBasedLoading(false);
break;
case LoginSituationCode.ExplicitFallbackByUser:
explicitFallback();

void getConnectService().recordEventLoginExplicitAbort();
break;
case LoginSituationCode.PreAuthenticatorCustomError: {
navigateToScreen(LoginScreenType.Invisible);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);
if (!data) {
return fallback(identifier, null);
case LoginSituationCode.CboApiFallbackOperationError: {
const typed = data as CboApiFallbackOperationError;

if (typed.initFallback) {
return automaticFallback(typed.identifierFallback, typed.message);
}

const typed = data as PreAuthenticatorCustomErrorData;
fallbackCustom(identifier, typed.code, typed.message);
setError(typed.message ?? '');
setCuiBasedLoading(false);
setIdentifierBasedLoading(false);
break;
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginOneTap from './base/LoginOneTap';
import { connectLoginFinishToComplete } from './LoginInitScreen';
import { type CboApiFallbackOperationError, connectLoginFinishToComplete } from './LoginInitScreen';

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

if (resStart.val.assertionOptions.length === 0) {
const data = {
initFallback: resStart.val.fallbackOperationError.initFallback,
identifierFallback: resStart.val.fallbackOperationError.identifier ?? '',
message: resStart.val.fallbackOperationError.error?.message ?? null,
};

return handleSituation(LoginSituationCode.CboApiFallbackOperationError, data);
}

const resFinish = await getConnectService().loginContinue(resStart.val);
if (resFinish.err) {
if (resFinish.val instanceof PasskeyChallengeCancelledError) {
Expand All @@ -53,7 +63,7 @@ export const LoginPasskeyReLoginScreen = () => {
navigateToScreen(LoginScreenType.Init, { prefilledIdentifier: identifier });
};

const handleSituation = (situationCode: LoginSituationCode) => {
const handleSituation = (situationCode: LoginSituationCode, data?: unknown) => {
const messageCode = `situation: ${situationCode}`;
log.debug(messageCode);

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

setLoading(false);
break;
case LoginSituationCode.CboApiFallbackOperationError: {
const { initFallback, identifierFallback, message } = data as CboApiFallbackOperationError;
if (initFallback) {
navigateToScreen(LoginScreenType.Invisible);
fallback(identifierFallback, message);
}
void getConnectService().recordEventLoginError(messageCode);

setLoading(false);
break;
}
}
};

Expand Down
12 changes: 1 addition & 11 deletions packages/connect-react/src/types/situations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum LoginSituationCode {
PreAuthenticatorCustomError,
PreAuthenticatorExistingPasskeysNotAvailable,
PreAuthenticatorNoPasskeyAvailable,
CboApiFallbackOperationError,
}

export enum AppendSituationCode {
Expand Down Expand Up @@ -41,11 +42,6 @@ export enum PasskeyListSituationCode {
CboApiPasskeysNotSupportedLight,
}

export type PreAuthenticatorCustomErrorData = {
code: string;
message: string;
};

export const getLoginErrorMessage = (code: LoginSituationCode): string | null => {
switch (code) {
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
Expand All @@ -54,12 +50,6 @@ export const getLoginErrorMessage = (code: LoginSituationCode): string | null =>
case LoginSituationCode.ClientPasskeyOperationCancelledTooManyTimes:
return "We couldn't log you in with your passkey due to a system error. Use your password to log in instead.";

case LoginSituationCode.PasskeyNotAvailablePostConditionalAuthenticator:
return 'You previously deleted this passkey. Use your password to log in instead.';

case LoginSituationCode.PreAuthenticatorUserNotFound:
return 'There is no account registered to that email address.';

default:
return null;
}
Expand Down
Loading
Loading