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
7 changes: 7 additions & 0 deletions packages/connect-react/src/components/CorbadoConnectDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -195,6 +196,12 @@ const CorbadoConnectDemo: FC<CorbadoConnectDemoConfig> = _ => {
'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: <PasskeyAppendNotSupportedModal hide={() => 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: <PasskeyAppendNotSupportedLightModal hide={() => console.log('hide')} />,
},
];

const append: Element[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ConnectConditionalUIPasskeyDeleted,
ConnectCustomError,
ConnectExistingPasskeysNotAvailable,
ConnectNoPasskeyAvailableError,
ConnectUserNotFound,
PasskeyChallengeCancelledError,
PasskeyLoginSource,
Expand Down Expand Up @@ -198,6 +199,9 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
if (resStart.val instanceof ConnectExistingPasskeysNotAvailable) {
return handleSituation(LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable);
}
if (resStart.val instanceof ConnectNoPasskeyAvailableError) {
return handleSituation(LoginSituationCode.PreAuthenticatorNoPasskeyAvailable);
}

return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
}
Expand Down Expand Up @@ -250,6 +254,8 @@ 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();
Expand All @@ -259,7 +265,6 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
case LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator:
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
case LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable:
automaticFallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

import { BaseModal } from '../shared/BaseModal';

type Props = {
hide: () => void;
};

const PasskeyAppendNotSupportedLightModal = ({ hide }: Props) => (
<BaseModal
onPrimaryButton={() => hide()}
onCloseButton={() => hide()}
headerText='No passkey created'
primaryButtonText='Okay'
children={
<>
<p className='cb-p'>This in-app view doesn't support passkeys. Use your standard browser.</p>
</>
}
/>
);

export default PasskeyAppendNotSupportedLightModal;
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -122,6 +123,10 @@ const PasskeyListScreen = () => {
}

if (!startAppendRes.val.attestationOptions) {
if (startAppendRes.val.isRestrictedBrowser) {
return handleSituation(PasskeyListSituationCode.CboApiPasskeysNotSupportedLight);
}

return handleSituation(PasskeyListSituationCode.CboApiPasskeysNotSupported);
}

Expand Down Expand Up @@ -178,6 +183,10 @@ const PasskeyListScreen = () => {
void getConnectService().recordEventAppendCredentialExistsError();
show(<AlreadyExistingModal hide={hide} />);
break;
case PasskeyListSituationCode.CboApiPasskeysNotSupportedLight:
setAppendLoading(false);
show(<PasskeyAppendNotSupportedLightModal hide={hide} />);
break;
case PasskeyListSituationCode.CboApiPasskeysNotSupported:
setAppendLoading(false);
show(<PasskeyAppendNotSupportedModal hide={hide} />);
Expand Down
2 changes: 2 additions & 0 deletions packages/connect-react/src/types/situations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum LoginSituationCode {
DeniedByPartialRollout,
PreAuthenticatorCustomError,
PreAuthenticatorExistingPasskeysNotAvailable,
PreAuthenticatorNoPasskeyAvailable,
}

export enum AppendSituationCode {
Expand All @@ -37,6 +38,7 @@ export enum PasskeyListSituationCode {
CboApiNotAvailablePostAuthenticator,
ClientPasskeyOperationCancelled,
ClientExcludeCredentialsMatch,
CboApiPasskeysNotSupportedLight,
}

export type PreAuthenticatorCustomErrorData = {
Expand Down
3 changes: 3 additions & 0 deletions packages/web-core/openapi/spec_v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,7 @@ components:
required:
- attestationOptions
- variant
- isRestrictedBrowser
properties:
attestationOptions:
type: string
Expand All @@ -1346,6 +1347,8 @@ components:
- default
- after-hybrid
- after-error
isRestrictedBrowser:
type: boolean

connectAppendFinishReq:
type: object
Expand Down
6 changes: 6 additions & 0 deletions packages/web-core/src/api/v2/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,12 @@ export interface ConnectAppendStartRsp {
* @memberof ConnectAppendStartRsp
*/
'variant': ConnectAppendStartRspVariantEnum;
/**
*
* @type {boolean}
* @memberof ConnectAppendStartRsp
*/
'isRestrictedBrowser': boolean;
}

export const ConnectAppendStartRspVariantEnum = {
Expand Down
4 changes: 2 additions & 2 deletions packages/web-core/src/services/ConnectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions packages/web-core/src/utils/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class CorbadoError extends Error {
}

static noPasskeyAvailable(): CorbadoError {
return new NoPasskeyAvailableError();
return new ConnectNoPasskeyAvailableError();
}

static onlyHybridPasskeyAvailable(): CorbadoError {
Expand Down Expand Up @@ -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';
Expand Down