diff --git a/packages/connect-react/src/components/CorbadoConnectDemo.tsx b/packages/connect-react/src/components/CorbadoConnectDemo.tsx index 4cec7b9d9..555f0339d 100644 --- a/packages/connect-react/src/components/CorbadoConnectDemo.tsx +++ b/packages/connect-react/src/components/CorbadoConnectDemo.tsx @@ -14,6 +14,7 @@ import LoginInitLoading from './login/base/LoginInitLoading'; import LoginOneTap from './login/base/LoginOneTap'; import AlreadyExistingModal from './passkeyList/AlreadyExistingModal'; import DeleteModal from './passkeyList/DeleteModal'; +import PasskeyAppendNotSupportedLightModal from './passkeyList/PasskeyAppendNotSupportedLightModal'; import PasskeyAppendNotSupportedModal from './passkeyList/PasskeyAppendNotSupportedModal'; import PasskeyList, { PasskeyListState } from './passkeyList/PasskeyList'; @@ -195,6 +196,12 @@ const CorbadoConnectDemo: FC = _ => { 'This modal is shown to the user when they try to append a passkey but the operation is not supported by their device (e.g. because it is too old).', reactElement: console.log('hide')} />, }, + { + headline: 'Passkey append not supported light modal', + description: + 'This modal is shown to the user when they try to append a passkey but the operation is not supported by their current browser (another browser on the same device might support it though).', + reactElement: console.log('hide')} />, + }, ]; const append: Element[] = [ diff --git a/packages/connect-react/src/components/login/LoginInitScreen.tsx b/packages/connect-react/src/components/login/LoginInitScreen.tsx index d423cca8c..e3985663f 100644 --- a/packages/connect-react/src/components/login/LoginInitScreen.tsx +++ b/packages/connect-react/src/components/login/LoginInitScreen.tsx @@ -2,6 +2,7 @@ import { ConnectConditionalUIPasskeyDeleted, ConnectCustomError, ConnectExistingPasskeysNotAvailable, + ConnectNoPasskeyAvailableError, ConnectUserNotFound, PasskeyChallengeCancelledError, PasskeyLoginSource, @@ -198,6 +199,9 @@ const LoginInitScreen: FC = ({ showFallback = false }) => { if (resStart.val instanceof ConnectExistingPasskeysNotAvailable) { return handleSituation(LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable); } + if (resStart.val instanceof ConnectNoPasskeyAvailableError) { + return handleSituation(LoginSituationCode.PreAuthenticatorNoPasskeyAvailable); + } return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator); } @@ -250,6 +254,8 @@ const LoginInitScreen: FC = ({ showFallback = false }) => { statefulLoader.current.finish(); break; case LoginSituationCode.DeniedByPartialRollout: + case LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable: + case LoginSituationCode.PreAuthenticatorNoPasskeyAvailable: automaticFallback(identifier, message); statefulLoader.current.finish(); @@ -259,7 +265,6 @@ const LoginInitScreen: FC = ({ showFallback = false }) => { case LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator: case LoginSituationCode.CtApiNotAvailablePostAuthenticator: case LoginSituationCode.CboApiNotAvailablePostAuthenticator: - case LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable: automaticFallback(identifier, message); void getConnectService().recordEventLoginErrorUnexpected(messageCode); diff --git a/packages/connect-react/src/components/passkeyList/PasskeyAppendNotSupportedLightModal.tsx b/packages/connect-react/src/components/passkeyList/PasskeyAppendNotSupportedLightModal.tsx new file mode 100644 index 000000000..2afc51e20 --- /dev/null +++ b/packages/connect-react/src/components/passkeyList/PasskeyAppendNotSupportedLightModal.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { BaseModal } from '../shared/BaseModal'; + +type Props = { + hide: () => void; +}; + +const PasskeyAppendNotSupportedLightModal = ({ hide }: Props) => ( + hide()} + onCloseButton={() => hide()} + headerText='No passkey created' + primaryButtonText='Okay' + children={ + <> +

This in-app view doesn't support passkeys. Use your standard browser.

+ + } + /> +); + +export default PasskeyAppendNotSupportedLightModal; diff --git a/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx b/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx index fc4d74342..3ce754dcf 100644 --- a/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx +++ b/packages/connect-react/src/components/passkeyList/PasskeyListScreen.tsx @@ -12,6 +12,7 @@ import { ConnectTokenType } from '../../types/tokens'; import { StatefulLoader } from '../../utils/statefulLoader'; import AlreadyExistingModal from './AlreadyExistingModal'; import DeleteModal from './DeleteModal'; +import PasskeyAppendNotSupportedLightModal from './PasskeyAppendNotSupportedLightModal'; import PasskeyAppendNotSupportedModal from './PasskeyAppendNotSupportedModal'; import PasskeyList, { PasskeyListState } from './PasskeyList'; @@ -122,6 +123,10 @@ const PasskeyListScreen = () => { } if (!startAppendRes.val.attestationOptions) { + if (startAppendRes.val.isRestrictedBrowser) { + return handleSituation(PasskeyListSituationCode.CboApiPasskeysNotSupportedLight); + } + return handleSituation(PasskeyListSituationCode.CboApiPasskeysNotSupported); } @@ -178,6 +183,10 @@ const PasskeyListScreen = () => { void getConnectService().recordEventAppendCredentialExistsError(); show(); break; + case PasskeyListSituationCode.CboApiPasskeysNotSupportedLight: + setAppendLoading(false); + show(); + break; case PasskeyListSituationCode.CboApiPasskeysNotSupported: setAppendLoading(false); show(); diff --git a/packages/connect-react/src/types/situations.ts b/packages/connect-react/src/types/situations.ts index 2c1a5a96d..89f9335e9 100644 --- a/packages/connect-react/src/types/situations.ts +++ b/packages/connect-react/src/types/situations.ts @@ -13,6 +13,7 @@ export enum LoginSituationCode { DeniedByPartialRollout, PreAuthenticatorCustomError, PreAuthenticatorExistingPasskeysNotAvailable, + PreAuthenticatorNoPasskeyAvailable, } export enum AppendSituationCode { @@ -37,6 +38,7 @@ export enum PasskeyListSituationCode { CboApiNotAvailablePostAuthenticator, ClientPasskeyOperationCancelled, ClientExcludeCredentialsMatch, + CboApiPasskeysNotSupportedLight, } export type PreAuthenticatorCustomErrorData = { diff --git a/packages/web-core/openapi/spec_v2.yaml b/packages/web-core/openapi/spec_v2.yaml index 121372c6e..8412e5e13 100644 --- a/packages/web-core/openapi/spec_v2.yaml +++ b/packages/web-core/openapi/spec_v2.yaml @@ -1337,6 +1337,7 @@ components: required: - attestationOptions - variant + - isRestrictedBrowser properties: attestationOptions: type: string @@ -1346,6 +1347,8 @@ components: - default - after-hybrid - after-error + isRestrictedBrowser: + type: boolean connectAppendFinishReq: type: object diff --git a/packages/web-core/src/api/v2/api.ts b/packages/web-core/src/api/v2/api.ts index e15ca20d2..dcf2b9a94 100644 --- a/packages/web-core/src/api/v2/api.ts +++ b/packages/web-core/src/api/v2/api.ts @@ -384,6 +384,12 @@ export interface ConnectAppendStartRsp { * @memberof ConnectAppendStartRsp */ 'variant': ConnectAppendStartRspVariantEnum; + /** + * + * @type {boolean} + * @memberof ConnectAppendStartRsp + */ + 'isRestrictedBrowser': boolean; } export const ConnectAppendStartRspVariantEnum = { diff --git a/packages/web-core/src/services/ConnectService.ts b/packages/web-core/src/services/ConnectService.ts index 03f27887e..c64f8e17c 100644 --- a/packages/web-core/src/services/ConnectService.ts +++ b/packages/web-core/src/services/ConnectService.ts @@ -45,7 +45,7 @@ export class ConnectService { constructor(projectId: string, frontendApiUrlSuffix: string, isDebug: boolean) { this.#projectId = projectId; - this.#timeout = 5 * 1000; + this.#timeout = 10 * 1000; this.#frontendApiUrlSuffix = frontendApiUrlSuffix; this.#webAuthnService = new WebAuthnService(); this.#visitorId = ''; @@ -143,7 +143,7 @@ export class ConnectService { const { req, flags } = await this.#getInitReq(); const res = await this.wrapWithErr(() => - this.#connectApi.connectLoginInit(req, { signal: abortController.signal }), + this.#connectApi.connectLoginInit(req, { signal: abortController.signal, timeout: 5 * 1000 }), ); if (res.err) { diff --git a/packages/web-core/src/utils/errors/errors.ts b/packages/web-core/src/utils/errors/errors.ts index 0ea6bddc7..82e8bc47b 100644 --- a/packages/web-core/src/utils/errors/errors.ts +++ b/packages/web-core/src/utils/errors/errors.ts @@ -153,7 +153,7 @@ export class CorbadoError extends Error { } static noPasskeyAvailable(): CorbadoError { - return new NoPasskeyAvailableError(); + return new ConnectNoPasskeyAvailableError(); } static onlyHybridPasskeyAvailable(): CorbadoError { @@ -239,7 +239,7 @@ export class UnknownUserError extends RecoverableError { } } -export class NoPasskeyAvailableError extends RecoverableError { +export class ConnectNoPasskeyAvailableError extends RecoverableError { constructor() { super('No passkey available'); this.name = 'errors.noPasskeyAvailable';