diff --git a/packages/experience/src/hooks/use-mfa-error-handler.ts b/packages/experience/src/hooks/use-mfa-error-handler.ts index 729839bbb..702a758ed 100644 --- a/packages/experience/src/hooks/use-mfa-error-handler.ts +++ b/packages/experience/src/hooks/use-mfa-error-handler.ts @@ -10,6 +10,7 @@ import { backupCodeErrorDataGuard, type BackupCodeBindingState, } from '@/types/guard'; +import { isNativeWebview } from '@/utils/native-sdk'; import type { ErrorHandlers } from './use-error-handler'; import useStartTotpBinding from './use-start-totp-binding'; @@ -66,14 +67,20 @@ const useMfaErrorHandler = ({ replace }: Options = {}) => { (flow: UserMfaFlow) => { return (error: RequestErrorBody) => { const [_, data] = validate(error.data, mfaErrorDataGuard); - const availableFactors = data?.availableFactors ?? []; + const factors = data?.availableFactors ?? []; const skippable = data?.skippable; - if (availableFactors.length === 0) { + if (factors.length === 0) { setToast(error.message); return; } + const availableFactors = + // Hide the webauthn factor on native webview if the user has other options, since it's not supported. + isNativeWebview() && factors.length > 1 + ? factors.filter((factor) => factor !== MfaFactor.WebAuthn) + : factors; + handleMfaRedirect(flow, { availableFactors, skippable }); }; }, diff --git a/packages/experience/src/hooks/use-webauthn-operation.ts b/packages/experience/src/hooks/use-webauthn-operation.ts index a077aab44..642fd1fdb 100644 --- a/packages/experience/src/hooks/use-webauthn-operation.ts +++ b/packages/experience/src/hooks/use-webauthn-operation.ts @@ -5,7 +5,11 @@ import { type WebAuthnRegistrationOptions, } from '@logto/schemas'; import { trySafe } from '@silverhand/essentials'; -import { startAuthentication, startRegistration } from '@simplewebauthn/browser'; +import { + browserSupportsWebAuthn, + startAuthentication, + startRegistration, +} from '@simplewebauthn/browser'; import type { RegistrationResponseJSON, AuthenticationResponseJSON, @@ -108,6 +112,11 @@ const useWebAuthnOperation = (flow: UserMfaFlow) => { ); return useCallback(async () => { + if (!browserSupportsWebAuthn()) { + setToast(t('mfa.webauthn_not_supported')); + return; + } + if (!webAuthnOptions) { /** * This error message is just for program robustness; in practice, this issue is unlikely to occur. diff --git a/packages/phrases-experience/src/locales/de/mfa.ts b/packages/phrases-experience/src/locales/de/mfa.ts index e48bd2ad2..78df3449b 100644 --- a/packages/phrases-experience/src/locales/de/mfa.ts +++ b/packages/phrases-experience/src/locales/de/mfa.ts @@ -53,6 +53,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/en/mfa.ts b/packages/phrases-experience/src/locales/en/mfa.ts index ef8cc6578..624ae4f40 100644 --- a/packages/phrases-experience/src/locales/en/mfa.ts +++ b/packages/phrases-experience/src/locales/en/mfa.ts @@ -50,6 +50,7 @@ const mfa = { secret_key_copied: 'Secret key copied.', backup_code_copied: 'Backup code copied.', webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', + webauthn_not_supported: 'WebAuthn is not supported in this browser.', webauthn_failed_to_create: 'Failed to create. Please try again.', webauthn_failed_to_verify: 'Failed to verify. Please try again.', }; diff --git a/packages/phrases-experience/src/locales/es/mfa.ts b/packages/phrases-experience/src/locales/es/mfa.ts index 0c12ff34d..c8adc313e 100644 --- a/packages/phrases-experience/src/locales/es/mfa.ts +++ b/packages/phrases-experience/src/locales/es/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/fr/mfa.ts b/packages/phrases-experience/src/locales/fr/mfa.ts index 0a671c57d..cec025f62 100644 --- a/packages/phrases-experience/src/locales/fr/mfa.ts +++ b/packages/phrases-experience/src/locales/fr/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/it/mfa.ts b/packages/phrases-experience/src/locales/it/mfa.ts index 8e82852d2..14085c51d 100644 --- a/packages/phrases-experience/src/locales/it/mfa.ts +++ b/packages/phrases-experience/src/locales/it/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/ja/mfa.ts b/packages/phrases-experience/src/locales/ja/mfa.ts index a7d29e363..b4ebdf3ff 100644 --- a/packages/phrases-experience/src/locales/ja/mfa.ts +++ b/packages/phrases-experience/src/locales/ja/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/ko/mfa.ts b/packages/phrases-experience/src/locales/ko/mfa.ts index 1cccfaefb..57fd7b858 100644 --- a/packages/phrases-experience/src/locales/ko/mfa.ts +++ b/packages/phrases-experience/src/locales/ko/mfa.ts @@ -50,6 +50,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/pl-pl/mfa.ts b/packages/phrases-experience/src/locales/pl-pl/mfa.ts index c788ee3fb..68e17f1ec 100644 --- a/packages/phrases-experience/src/locales/pl-pl/mfa.ts +++ b/packages/phrases-experience/src/locales/pl-pl/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/pt-br/mfa.ts b/packages/phrases-experience/src/locales/pt-br/mfa.ts index 8220f4e88..3714865ba 100644 --- a/packages/phrases-experience/src/locales/pt-br/mfa.ts +++ b/packages/phrases-experience/src/locales/pt-br/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/pt-pt/mfa.ts b/packages/phrases-experience/src/locales/pt-pt/mfa.ts index 504077e2c..b603ad785 100644 --- a/packages/phrases-experience/src/locales/pt-pt/mfa.ts +++ b/packages/phrases-experience/src/locales/pt-pt/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/ru/mfa.ts b/packages/phrases-experience/src/locales/ru/mfa.ts index 44c5ef10c..20e506017 100644 --- a/packages/phrases-experience/src/locales/ru/mfa.ts +++ b/packages/phrases-experience/src/locales/ru/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/tr-tr/mfa.ts b/packages/phrases-experience/src/locales/tr-tr/mfa.ts index e73596396..527ff2a95 100644 --- a/packages/phrases-experience/src/locales/tr-tr/mfa.ts +++ b/packages/phrases-experience/src/locales/tr-tr/mfa.ts @@ -52,6 +52,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/zh-cn/mfa.ts b/packages/phrases-experience/src/locales/zh-cn/mfa.ts index c5b1a53ae..335b99591 100644 --- a/packages/phrases-experience/src/locales/zh-cn/mfa.ts +++ b/packages/phrases-experience/src/locales/zh-cn/mfa.ts @@ -47,6 +47,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/zh-hk/mfa.ts b/packages/phrases-experience/src/locales/zh-hk/mfa.ts index 343dfee83..cde420f53 100644 --- a/packages/phrases-experience/src/locales/zh-hk/mfa.ts +++ b/packages/phrases-experience/src/locales/zh-hk/mfa.ts @@ -47,6 +47,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.', diff --git a/packages/phrases-experience/src/locales/zh-tw/mfa.ts b/packages/phrases-experience/src/locales/zh-tw/mfa.ts index e16a46904..21b5f6d81 100644 --- a/packages/phrases-experience/src/locales/zh-tw/mfa.ts +++ b/packages/phrases-experience/src/locales/zh-tw/mfa.ts @@ -47,6 +47,8 @@ const mfa = { /** UNTRANSLATED */ webauthn_not_ready: 'WebAuthn is not ready yet. Please try again later.', /** UNTRANSLATED */ + webauthn_not_supported: 'WebAuthn is not supported in this browser.', + /** UNTRANSLATED */ webauthn_failed_to_create: 'Failed to create. Please try again.', /** UNTRANSLATED */ webauthn_failed_to_verify: 'Failed to verify. Please try again.',