From 6204f58696997cca654f7fa0fd639fbd2e5ba986 Mon Sep 17 00:00:00 2001 From: ruijialin Date: Thu, 16 Jan 2025 16:42:34 -0500 Subject: [PATCH 1/5] update android passkey implementation --- .../onboarding/components/FidoNameInput.tsx | 91 ++++++++ .../hooks/useRegisterAndAuthenticateFido.ts | 71 ++++++ .../onboarding/hooks/useSeedlessManageMFA.ts | 39 +++- .../seedless/addRecoveryMethods.tsx | 39 ++-- .../onboarding/seedless/fidoNameInput.tsx | 60 ++++++ .../app/seedless/hooks/useSeedlessRegister.ts | 2 +- .../app/seedless/hooks/useVerifyMFA.ts | 5 +- .../seedless/screens/AddRecoveryMethods.tsx | 2 +- .../services/SeedlessSessionManager.ts | 2 +- .../utils/startRefreshSeedlessTokenFlow.ts | 6 +- .../passkey/PasskeyService.android.ts | 204 ------------------ .../app/services/passkey/PasskeyService.ts | 158 +++++++++----- .../core-mobile/app/services/passkey/types.ts | 4 +- packages/core-mobile/ios/.xcode.env | 2 +- packages/core-mobile/ios/Podfile.lock | 4 +- packages/core-mobile/package.json | 2 +- packages/core-mobile/react-native.config.js | 5 - yarn.lock | 12 +- 18 files changed, 401 insertions(+), 307 deletions(-) create mode 100644 packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx create mode 100644 packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts create mode 100644 packages/core-mobile/app/new/routes/onboarding/seedless/fidoNameInput.tsx delete mode 100644 packages/core-mobile/app/services/passkey/PasskeyService.android.ts diff --git a/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx b/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx new file mode 100644 index 0000000000..13bfa89913 --- /dev/null +++ b/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx @@ -0,0 +1,91 @@ +import React from 'react' +import { + Text, + TextInput, + useTheme, + View, + Icons, + Button, + TouchableOpacity +} from '@avalabs/k2-alpine' +import BlurredBarsContentLayout from 'common/components/BlurredBarsContentLayout' +import { FIDONameInputProps } from 'new/routes/onboarding/seedless/fidoNameInput' + +const FidoNameInput = ({ + title, + description, + textInputPlaceholder, + name, + setName, + handleSave +}: Omit & { + name: string + setName: (value: string) => void + handleSave: () => void +}): JSX.Element => { + const { + theme: { colors } + } = useTheme() + + return ( + + + + {title} + + {description} + + + + setName('')}> + + + + + + + + + + ) +} + +export default FidoNameInput diff --git a/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts b/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts new file mode 100644 index 0000000000..1866b3c1ab --- /dev/null +++ b/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts @@ -0,0 +1,71 @@ +import { FidoType } from 'services/passkey/types' +import PasskeyService from 'services/passkey/PasskeyService' +import AnalyticsService from 'services/analytics/AnalyticsService' +import SeedlessService from 'seedless/services/SeedlessService' +import Logger from 'utils/Logger' +import { showSnackbar } from 'common/utils/toast' +import { showLogoModal, hideLogoModal } from 'common/components/LogoModal' +import { useRecoveryMethodContext } from '../contexts/RecoveryMethodProvider' +import useSeedlessManageMFA from './useSeedlessManageMFA' + +export const useRegisterAndAuthenticateFido = (): { + registerAndAuthenticateFido: ({ + name, + fidoType, + onAccountVerified + }: { + name?: string + fidoType: FidoType + onAccountVerified: () => void + }) => Promise +} => { + const { oidcAuth } = useRecoveryMethodContext() + const { fidoRegisterInit } = useSeedlessManageMFA() + + const registerAndAuthenticateFido = async ({ + name, + fidoType, + onAccountVerified + }: { + name?: string + fidoType: FidoType + onAccountVerified: () => void + }): Promise => { + const passkeyName = name && name.length > 0 ? name : fidoType.toString() + + showLogoModal() + + try { + const withSecurityKey = fidoType === FidoType.YUBI_KEY + + fidoRegisterInit(passkeyName, async challenge => { + const credential = await PasskeyService.create( + challenge.options, + withSecurityKey + ) + + await challenge.answer(credential) + + AnalyticsService.capture('SeedlessMfaAdded') + + if (oidcAuth) { + await SeedlessService.sessionManager.approveFido( + oidcAuth.oidcToken, + oidcAuth.mfaId, + withSecurityKey + ) + + AnalyticsService.capture('SeedlessMfaVerified', { type: fidoType }) + } + onAccountVerified() + }) + } catch (e) { + console.log('registerAndAuthenticateFido', e) + Logger.error(`${fidoType} registration failed`, e) + showSnackbar(`Unable to register ${fidoType}`) + } finally { + hideLogoModal() + } + } + return { registerAndAuthenticateFido } +} diff --git a/packages/core-mobile/app/new/features/onboarding/hooks/useSeedlessManageMFA.ts b/packages/core-mobile/app/new/features/onboarding/hooks/useSeedlessManageMFA.ts index 046a3760b4..9746641ba0 100644 --- a/packages/core-mobile/app/new/features/onboarding/hooks/useSeedlessManageMFA.ts +++ b/packages/core-mobile/app/new/features/onboarding/hooks/useSeedlessManageMFA.ts @@ -1,4 +1,4 @@ -import { TotpChallenge } from '@cubist-labs/cubesigner-sdk' +import { AddFidoChallenge, TotpChallenge } from '@cubist-labs/cubesigner-sdk' import { showSnackbar } from 'common/utils/toast' import useVerifyMFA from 'seedless/hooks/useVerifyMFA' import SeedlessService from 'seedless/services/SeedlessService' @@ -8,6 +8,10 @@ function useSeedlessManageMFA(): { totpResetInit: ( onInitialzied: (challenge: TotpChallenge) => void ) => Promise + fidoRegisterInit: ( + name: string, + onInitialzied: (challenge: AddFidoChallenge) => Promise + ) => Promise } { const { verifyMFA } = useVerifyMFA(SeedlessService.sessionManager) @@ -40,8 +44,39 @@ function useSeedlessManageMFA(): { } } + async function fidoRegisterInit( + name: string, + onInitialzied: (challenge: AddFidoChallenge) => Promise + ): Promise { + try { + const fidoRegisterInitResponse = + await SeedlessService.sessionManager.fidoRegisterInit(name) + + if (fidoRegisterInitResponse.requiresMfa()) { + const handleVerifySuccess: HandleVerifyMfaSuccess< + AddFidoChallenge + > = async addFidoChallenge => { + onInitialzied(addFidoChallenge) + } + + verifyMFA({ + response: fidoRegisterInitResponse, + onVerifySuccess: handleVerifySuccess + }) + } else { + const addFidoChallenge = fidoRegisterInitResponse.data() + + onInitialzied(addFidoChallenge) + } + } catch (e) { + Logger.error('fidoRegisterInit error', e) + showSnackbar('Unable to reset totp. Please try again.') + } + } + return { - totpResetInit + totpResetInit, + fidoRegisterInit } } diff --git a/packages/core-mobile/app/new/routes/onboarding/seedless/addRecoveryMethods.tsx b/packages/core-mobile/app/new/routes/onboarding/seedless/addRecoveryMethods.tsx index ee31cd2e87..04a44565c0 100644 --- a/packages/core-mobile/app/new/routes/onboarding/seedless/addRecoveryMethods.tsx +++ b/packages/core-mobile/app/new/routes/onboarding/seedless/addRecoveryMethods.tsx @@ -5,8 +5,9 @@ import { RecoveryMethods, useAvailableRecoveryMethods } from 'features/onboarding/hooks/useAvailableRecoveryMethods' -import { useRecoveryMethodContext } from 'features/onboarding/contexts/RecoveryMethodProvider' import { AddRecoveryMethods as Component } from 'features/onboarding/components/AddRecoveryMethods' +import { useRecoveryMethodContext } from 'features/onboarding/contexts/RecoveryMethodProvider' +import { FidoType } from 'services/passkey/types' const AddRecoveryMethods = (): JSX.Element => { const { navigate } = useRouter() @@ -21,27 +22,27 @@ const AddRecoveryMethods = (): JSX.Element => { const handleOnNext = (): void => { if (selectedMethod === RecoveryMethods.Passkey) { - // navigate({ - // pathname: './fidoNameInput', - // params: { - // title: 'How would you like to name your passkey?', - // description: 'Add a Passkey name, so it’s easier to find later', - // textInputPlaceholder: 'Passkey name', - // fidoType: FidoType.PASS_KEY - // } - // }) + navigate({ + pathname: './fidoNameInput', + params: { + title: 'How would you like to name your passkey?', + description: 'Add a Passkey name, so it’s easier to find later', + textInputPlaceholder: 'Passkey name', + fidoType: FidoType.PASS_KEY + } + }) return } if (selectedMethod === RecoveryMethods.Yubikey) { - // navigate({ - // pathname: './fidoNameInput', - // params: { - // title: 'How would you like to name your YubiKey?', - // description: 'Add a YubiKey name, so it’s easier to find later', - // textInputPlaceholder: 'YubiKey name', - // fidoType: FidoType.YUBI_KEY - // } - // }) + navigate({ + pathname: './fidoNameInput', + params: { + title: 'How would you like to name your YubiKey?', + description: 'Add a YubiKey name, so it’s easier to find later', + textInputPlaceholder: 'YubiKey name', + fidoType: FidoType.YUBI_KEY + } + }) return } if (selectedMethod === RecoveryMethods.Authenticator) { diff --git a/packages/core-mobile/app/new/routes/onboarding/seedless/fidoNameInput.tsx b/packages/core-mobile/app/new/routes/onboarding/seedless/fidoNameInput.tsx new file mode 100644 index 0000000000..5945b4c780 --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/seedless/fidoNameInput.tsx @@ -0,0 +1,60 @@ +import React, { useCallback, useState } from 'react' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { FidoType } from 'services/passkey/types' +import { useRegisterAndAuthenticateFido } from 'features/onboarding/hooks/useRegisterAndAuthenticateFido' +import { hideLogoModal, showLogoModal } from 'common/components/LogoModal' +import SeedlessService from 'seedless/services/SeedlessService' +import Component from 'features/onboarding/components/FidoNameInput' + +export type FIDONameInputProps = { + fidoType: FidoType + title: string + description: string + textInputPlaceholder: string +} + +const FidoNameInput = (): JSX.Element => { + const router = useRouter() + const { registerAndAuthenticateFido } = useRegisterAndAuthenticateFido() + const { title, description, textInputPlaceholder, fidoType } = + useLocalSearchParams() + + const [name, setName] = useState('') + + const onAccountVerified = useCallback(async (): Promise => { + showLogoModal() + const walletName = await SeedlessService.getAccountName() + hideLogoModal() + + if (walletName) { + router.navigate('./createPin') + return + } + router.navigate('./nameYourWallet') + }, [router]) + + const handleSave = (): void => { + if (router.canGoBack()) { + router.back() + } + fidoType && + registerAndAuthenticateFido({ + name, + fidoType, + onAccountVerified + }) + } + + return ( + + ) +} + +export default FidoNameInput diff --git a/packages/core-mobile/app/seedless/hooks/useSeedlessRegister.ts b/packages/core-mobile/app/seedless/hooks/useSeedlessRegister.ts index 9b322b1122..a1b8173457 100644 --- a/packages/core-mobile/app/seedless/hooks/useSeedlessRegister.ts +++ b/packages/core-mobile/app/seedless/hooks/useSeedlessRegister.ts @@ -182,7 +182,7 @@ export const useSeedlessRegister = (): ReturnType => { await SeedlessService.sessionManager.approveFido( oidcAuth.oidcToken, oidcAuth.mfaId, - false + true ) AnalyticsService.capture('SeedlessMfaVerified', { type: 'Fido' }) diff --git a/packages/core-mobile/app/seedless/hooks/useVerifyMFA.ts b/packages/core-mobile/app/seedless/hooks/useVerifyMFA.ts index 14ba3d7dfe..842e28eb69 100644 --- a/packages/core-mobile/app/seedless/hooks/useVerifyMFA.ts +++ b/packages/core-mobile/app/seedless/hooks/useVerifyMFA.ts @@ -106,10 +106,7 @@ function useVerifyMFA(sessionManager: SeedlessSessionManager): { onVerifySuccess: (response: T) => void }) => { const challenge = await sessionManager.fidoApproveStart(response.mfaId()) - const credential = await PasskeyService.authenticate( - challenge.options, - true - ) + const credential = await PasskeyService.get(challenge.options, true) const mfaRequestInfo = await challenge.answer(credential) if (!mfaRequestInfo.receipt?.confirmation) { throw new Error('FIDO authentication failed') diff --git a/packages/core-mobile/app/seedless/screens/AddRecoveryMethods.tsx b/packages/core-mobile/app/seedless/screens/AddRecoveryMethods.tsx index 72aa1afdd9..ae4156282c 100644 --- a/packages/core-mobile/app/seedless/screens/AddRecoveryMethods.tsx +++ b/packages/core-mobile/app/seedless/screens/AddRecoveryMethods.tsx @@ -76,7 +76,7 @@ export const AddRecoveryMethods = (): JSX.Element => { const withSecurityKey = fidoType === FidoType.YUBI_KEY fidoRegisterInit(passkeyName, async challenge => { - const credential = await PasskeyService.register( + const credential = await PasskeyService.create( challenge.options, withSecurityKey ) diff --git a/packages/core-mobile/app/seedless/services/SeedlessSessionManager.ts b/packages/core-mobile/app/seedless/services/SeedlessSessionManager.ts index f5cda62c96..f38a61147b 100644 --- a/packages/core-mobile/app/seedless/services/SeedlessSessionManager.ts +++ b/packages/core-mobile/app/seedless/services/SeedlessSessionManager.ts @@ -224,7 +224,7 @@ class SeedlessSessionManager { withSecurityKey: boolean ): Promise { const challenge = await this.fidoApproveStart(mfaId) - const credential = await PasskeyService.authenticate( + const credential = await PasskeyService.get( challenge.options, withSecurityKey ) diff --git a/packages/core-mobile/app/seedless/utils/startRefreshSeedlessTokenFlow.ts b/packages/core-mobile/app/seedless/utils/startRefreshSeedlessTokenFlow.ts index c8f52fe033..ee22110645 100644 --- a/packages/core-mobile/app/seedless/utils/startRefreshSeedlessTokenFlow.ts +++ b/packages/core-mobile/app/seedless/utils/startRefreshSeedlessTokenFlow.ts @@ -145,11 +145,7 @@ async function fidoRefreshFlow( sessionManager: SeedlessSessionManager ): Promise> { try { - await sessionManager.approveFido( - oidcToken, - mfaId, - false //FIXME: this parameter is not needed, should refactor approveFido to remove it, - ) + await sessionManager.approveFido(oidcToken, mfaId, true) return { success: true, value: undefined diff --git a/packages/core-mobile/app/services/passkey/PasskeyService.android.ts b/packages/core-mobile/app/services/passkey/PasskeyService.android.ts deleted file mode 100644 index 00e6c22379..0000000000 --- a/packages/core-mobile/app/services/passkey/PasskeyService.android.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { InAppBrowser } from 'react-native-inappbrowser-reborn' -import { - PasskeyAuthenticationRequest, - PasskeyRegistrationRequest -} from 'react-native-passkey/lib/typescript/Passkey' -import { - FIDOAuthenticationRequest, - FIDOAuthenticationResult, - FIDORegistrationRequest, - FIDORegistrationResult, - FidoType, - PasskeyServiceInterface -} from 'services/passkey/types' -import { base64UrlToBuffer, bufferToBase64Url } from 'utils/data/base64' -import { FIDO_CALLBACK_URL, RP_ID } from './consts' - -const BROWSER_OPTIONS = { - showTitle: false, - toolbarColor: '#000000', - secondaryToolbarColor: '#000000', - navigationBarColor: '#000000', - navigationBarDividerColor: '#ffffff', - enableUrlBarHiding: true, - enableDefaultShare: false, - showInRecents: false, - browserPackage: 'com.android.chrome' // force using chrome or else it will use default browser sometimes -} - -const IDENTITY_URL = `https://${RP_ID}/` - -enum Action { - REGISTER = 'register', - AUTHENTICATE = 'authenticate' -} - -type GenerateAuthUrlsParams = - | { - options: PasskeyRegistrationRequest - action: Action.REGISTER - fidoType: FidoType - } - | { - options: PasskeyAuthenticationRequest - action: Action.AUTHENTICATE - fidoType?: never - } - -class PasskeyService implements PasskeyServiceInterface { - // unfortunately, we don't know if the device supports passkey - // until we open browser and try to register - get isSupported(): boolean { - return true - } - - async register( - challengeOptions: FIDORegistrationRequest, - withSecurityKey: boolean - ): Promise { - const options = this.prepareRegistrationOptions(challengeOptions) - const { url, redirectUrl } = this.generateAuthUrls({ - options, - action: Action.REGISTER, - fidoType: withSecurityKey ? FidoType.YUBI_KEY : FidoType.PASS_KEY - }) - const result = await this.startAuthSession(url, redirectUrl) - return this.convertRegistrationResult(result) - } - - async authenticate( - challengeOptions: FIDOAuthenticationRequest, - _withSecurityKey: boolean - ): Promise { - const options = this.prepareAuthenticationRequest(challengeOptions) - const { url, redirectUrl } = this.generateAuthUrls({ - options, - action: Action.AUTHENTICATE - }) - const result = await this.startAuthSession(url, redirectUrl) - return this.convertAuthenticationResult(result) - } - - private prepareRegistrationOptions( - options: FIDORegistrationRequest - ): PasskeyRegistrationRequest { - return { - ...options, - challenge: bufferToBase64Url(options.challenge), - user: { - ...options.user, - id: bufferToBase64Url(options.user.id) - }, - excludeCredentials: (options.excludeCredentials ?? []).map(cred => ({ - ...cred, - id: bufferToBase64Url(cred.id) - })) - } - } - - private prepareAuthenticationRequest( - options: FIDOAuthenticationRequest - ): PasskeyAuthenticationRequest { - return { - ...options, - challenge: bufferToBase64Url(options.challenge), - allowCredentials: (options.allowCredentials ?? []).map(cred => ({ - ...cred, - id: bufferToBase64Url(cred.id) - })) - } - } - - private generateAuthUrls({ - options, - action, - fidoType - }: GenerateAuthUrlsParams): { url: string; redirectUrl: string } { - const redirectUrl = `${FIDO_CALLBACK_URL}${action}` - - const url = new URL(`${IDENTITY_URL}${action}`) - url.searchParams.set('options', encodeURIComponent(JSON.stringify(options))) - url.searchParams.set('redirectUrl', encodeURIComponent(redirectUrl)) - - if (fidoType) { - url.searchParams.set('keyType', fidoType.toLowerCase()) - } - - return { url: url.toString(), redirectUrl } - } - - private async startAuthSession( - url: string, - redirectUrl: string - ): Promise { - const response = await InAppBrowser.openAuth( - url, - redirectUrl, - BROWSER_OPTIONS - ) - - if (response.type === 'cancel') { - throw new Error('User cancelled session') - } - - if (response.type === 'dismiss') { - throw new Error('User dismissed session') - } - - if (response.type !== 'success') { - throw new Error('Something went wrong') - } - - const result = new URL(response.url).searchParams.get('result') - - if (!result) throw new Error('Invalid response') - - return result - } - - private convertRegistrationResult(result: string): FIDORegistrationResult { - const decodedResult = JSON.parse(decodeURIComponent(result)) - - return { - ...decodedResult, - id: decodedResult.id, - rawId: base64UrlToBuffer(decodedResult.rawId), - response: { - ...decodedResult.response, - clientDataJSON: base64UrlToBuffer( - decodedResult.response.clientDataJSON - ), - attestationObject: base64UrlToBuffer( - decodedResult.response.attestationObject - ) - } - } - } - - private convertAuthenticationResult( - result: string - ): FIDOAuthenticationResult { - const decodedResult = JSON.parse(decodeURIComponent(result)) - - return { - ...decodedResult, - id: decodedResult.id, - rawId: base64UrlToBuffer(decodedResult.rawId), - response: { - ...decodedResult.response, - clientDataJSON: base64UrlToBuffer( - decodedResult.response.clientDataJSON - ), - authenticatorData: base64UrlToBuffer( - decodedResult.response.authenticatorData - ), - signature: base64UrlToBuffer(decodedResult.response.signature), - userHandle: decodedResult.response.userHandle - ? base64UrlToBuffer(decodedResult.response.userHandle) - : decodedResult.response.userHandle // userHandle can be null - } - } - } -} - -export default new PasskeyService() diff --git a/packages/core-mobile/app/services/passkey/PasskeyService.ts b/packages/core-mobile/app/services/passkey/PasskeyService.ts index 60c5f9f23f..582d683b78 100644 --- a/packages/core-mobile/app/services/passkey/PasskeyService.ts +++ b/packages/core-mobile/app/services/passkey/PasskeyService.ts @@ -1,11 +1,11 @@ import { Platform } from 'react-native' -import { Passkey } from 'react-native-passkey' import { - PasskeyAuthenticationRequest, - PasskeyAuthenticationResult, - PasskeyRegistrationRequest, - PasskeyRegistrationResult -} from 'react-native-passkey/lib/typescript/Passkey' + Passkey, + PasskeyCreateRequest, + PasskeyCreateResult, + PasskeyGetRequest, + PasskeyGetResult +} from 'react-native-passkey' import { FIDOAuthenticationResult, FIDOAuthenticationRequest, @@ -13,108 +13,160 @@ import { FIDORegistrationRequest, PasskeyServiceInterface } from 'services/passkey/types' -import { base64ToBase64Url } from 'utils/data/base64' +import { + base64ToBase64Url, + base64UrlToBuffer, + bufferToBase64Url +} from 'utils/data/base64' import { FIDO_TIMEOUT, RP_ID, RP_NAME } from './consts' class PasskeyService implements PasskeyServiceInterface { get isSupported(): boolean { - return Passkey.isSupported() && Platform.OS === 'ios' + return Passkey.isSupported() } - async register( + async create( challengeOptions: FIDORegistrationRequest, withSecurityKey: boolean ): Promise { - const request = this.prepareRegistrationRequest(challengeOptions) - const result = await Passkey.register(request, { withSecurityKey }) + const request = this.prepareRegistrationRequest( + challengeOptions, + withSecurityKey + ) + + const result = withSecurityKey + ? await Passkey.createSecurityKey(request) + : await Passkey.createPlatformKey(request) return this.convertRegistrationResult(result) } - async authenticate( + async get( challengeOptions: FIDOAuthenticationRequest, withSecurityKey: boolean ): Promise { const request = this.prepareAuthenticationRequest(challengeOptions) - const result = await Passkey.authenticate(request, { - withSecurityKey - }) + const result = withSecurityKey + ? await Passkey.getSecurityKey(request) + : await Passkey.getPlatformKey(request) return this.convertAuthenticationResult(result) } private prepareRegistrationRequest( - request: FIDORegistrationRequest - ): PasskeyRegistrationRequest { + request: FIDORegistrationRequest, + withSecurityKey: boolean + ): PasskeyCreateRequest { + let authenticatorSelection = request.authenticatorSelection + if (Platform.OS === 'android') { + authenticatorSelection = { + authenticatorAttachment: withSecurityKey + ? 'cross-platform' + : 'platform', + requireResidentKey: withSecurityKey ? false : true, + residentKey: withSecurityKey ? 'discouraged' : 'required', + userVerification: withSecurityKey ? 'preferred' : 'required' + } + } + return { - ...request, - challenge: request.challenge.toString('base64'), + challenge: bufferToBase64Url(request.challenge), rp: { - ...request.rp, name: RP_NAME, id: RP_ID }, user: { - ...request.user, - id: request.user.id.toString('base64') + id: bufferToBase64Url(request.user.id), + name: request.user.name, + displayName: request.user.displayName }, timeout: FIDO_TIMEOUT, excludeCredentials: (request.excludeCredentials ?? []).map(cred => ({ - ...cred, - id: cred.id.toString('base64') - })) + transports: cred.transports as [], + id: bufferToBase64Url(cred.id), + type: cred.type + })), + pubKeyCredParams: request.pubKeyCredParams, + authenticatorSelection, + attestation: request.attestation } } private prepareAuthenticationRequest( request: FIDOAuthenticationRequest - ): PasskeyAuthenticationRequest { + ): PasskeyGetRequest { return { - ...request, - challenge: request.challenge.toString('base64'), + challenge: bufferToBase64Url(request.challenge), rpId: RP_ID, timeout: FIDO_TIMEOUT, allowCredentials: (request.allowCredentials ?? []).map(cred => ({ - ...cred, - id: cred.id.toString('base64') - })) + transports: cred.transports as [], + id: bufferToBase64Url(cred.id), + type: cred.type + })), + userVerification: request.userVerification } } private convertRegistrationResult( - result: PasskeyRegistrationResult + result: PasskeyCreateResult ): FIDORegistrationResult { + let decodedResult = result + if (Platform.OS === 'android') { + decodedResult = JSON.parse(result as unknown as string) + } return { - ...result, - id: base64ToBase64Url(result.id), - rawId: Buffer.from(result.rawId, 'base64'), + type: decodedResult.type, + id: base64ToBase64Url(decodedResult.id), + rawId: base64UrlToBuffer(decodedResult.rawId) as Buffer, response: { - ...result.response, - clientDataJSON: Buffer.from(result.response.clientDataJSON, 'base64'), - attestationObject: Buffer.from( - result.response.attestationObject, - 'base64' - ) + clientDataJSON: base64UrlToBuffer( + 'clientDataJSON' in decodedResult.response + ? decodedResult.response.clientDataJSON + : '' + ) as Buffer, + attestationObject: base64UrlToBuffer( + 'attestationObject' in decodedResult.response + ? decodedResult.response.attestationObject + : '' + ) as Buffer } } } private convertAuthenticationResult( - result: PasskeyAuthenticationResult + result: PasskeyGetResult ): FIDOAuthenticationResult { + let decodedResult = result + if (Platform.OS === 'android') { + decodedResult = JSON.parse(result as unknown as string) + } + return { - ...result, - id: base64ToBase64Url(result.id), - rawId: Buffer.from(result.rawId, 'base64'), + id: base64ToBase64Url(decodedResult.id), + type: result.type, + rawId: base64UrlToBuffer(decodedResult.rawId) as Buffer, response: { - ...result.response, - clientDataJSON: Buffer.from(result.response.clientDataJSON, 'base64'), - authenticatorData: Buffer.from( - result.response.authenticatorData, - 'base64' - ), - signature: Buffer.from(result.response.signature, 'base64'), - userHandle: Buffer.from(result.response.userHandle, 'base64') + clientDataJSON: base64UrlToBuffer( + 'clientDataJSON' in decodedResult.response + ? decodedResult.response.clientDataJSON + : '' + ) as Buffer, + authenticatorData: base64UrlToBuffer( + 'authenticatorData' in decodedResult.response + ? decodedResult.response.authenticatorData + : '' + ) as Buffer, + signature: base64UrlToBuffer( + 'signature' in decodedResult.response + ? decodedResult.response.signature + : '' + ) as Buffer, + userHandle: base64UrlToBuffer( + 'userHandle' in decodedResult.response + ? decodedResult.response.userHandle + : '' + ) as Buffer } } } diff --git a/packages/core-mobile/app/services/passkey/types.ts b/packages/core-mobile/app/services/passkey/types.ts index 65d3ef2204..f49a9ad83d 100644 --- a/packages/core-mobile/app/services/passkey/types.ts +++ b/packages/core-mobile/app/services/passkey/types.ts @@ -71,11 +71,11 @@ export enum FidoType { export interface PasskeyServiceInterface { readonly isSupported: boolean - register( + create( challengeOptions: FIDORegistrationRequest, withSecurityKey: boolean ): Promise - authenticate( + get( challengeOptions: FIDOAuthenticationRequest, withSecurityKey: boolean ): Promise diff --git a/packages/core-mobile/ios/.xcode.env b/packages/core-mobile/ios/.xcode.env index ab08f29bf3..5a6cb48387 100644 --- a/packages/core-mobile/ios/.xcode.env +++ b/packages/core-mobile/ios/.xcode.env @@ -8,7 +8,7 @@ # Customize the NODE_BINARY variable here. # For example, to use nvm with brew, add the following line # . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=node +export NODE_BINARY=$(command -v node) export SENTRY_PROPERTIES=sentry.properties export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map" export BUNDLE_COMMAND="ram-bundle" \ No newline at end of file diff --git a/packages/core-mobile/ios/Podfile.lock b/packages/core-mobile/ios/Podfile.lock index e1ee873b8b..aaff71aa82 100644 --- a/packages/core-mobile/ios/Podfile.lock +++ b/packages/core-mobile/ios/Podfile.lock @@ -1070,7 +1070,7 @@ PODS: - glog - RCT-Folly (= 2022.05.16.00) - React-Core - - react-native-passkey (2.1.1): + - react-native-passkey (3.0.0): - React-Core - react-native-quick-base64 (2.1.2): - glog @@ -1835,7 +1835,7 @@ SPEC CHECKSUMS: react-native-mmkv: 1fdc81aa70c1aba09370718e6a63a09cbbbac8d2 react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac react-native-pager-view: 7c45504fe805a954ebabd005d369937ea1c04de1 - react-native-passkey: 29ff814a83dfd4311478498e71a801dce68043ac + react-native-passkey: 1f8d18b3d0202ae5f6171cd39e3193247ab6f1af react-native-quick-base64: e1ea036b3dec44c6da2439bd62881a09de614b23 react-native-quick-crypto: 455c1b411db006dba1026a30681ececb19180187 react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 diff --git a/packages/core-mobile/package.json b/packages/core-mobile/package.json index a6b7152d4c..0d7c90c5b7 100644 --- a/packages/core-mobile/package.json +++ b/packages/core-mobile/package.json @@ -150,7 +150,7 @@ "react-native-modal-datetime-picker": "18.0.0", "react-native-os": "1.2.6", "react-native-pager-view": "6.4.1", - "react-native-passkey": "2.1.1", + "react-native-passkey": "3.0.0", "react-native-performance": "5.1.2", "react-native-permissions": "4.1.5", "react-native-popable": "0.4.3", diff --git a/packages/core-mobile/react-native.config.js b/packages/core-mobile/react-native.config.js index 5303b4a968..76214935f8 100644 --- a/packages/core-mobile/react-native.config.js +++ b/packages/core-mobile/react-native.config.js @@ -1,11 +1,6 @@ module.exports = { assets: ['./app/assets/fonts/'], dependencies: { - 'react-native-passkey': { - platforms: { - android: null // disable Android platform, other platforms will still autolink - } - }, 'react-native-performance': { platforms: { ios: null, diff --git a/yarn.lock b/yarn.lock index 2405509a03..4ba5b1da14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -358,7 +358,7 @@ __metadata: react-native-modal-datetime-picker: 18.0.0 react-native-os: 1.2.6 react-native-pager-view: 6.4.1 - react-native-passkey: 2.1.1 + react-native-passkey: 3.0.0 react-native-performance: 5.1.2 react-native-permissions: 4.1.5 react-native-popable: 0.4.3 @@ -26306,13 +26306,13 @@ __metadata: languageName: node linkType: hard -"react-native-passkey@npm:2.1.1": - version: 2.1.1 - resolution: "react-native-passkey@npm:2.1.1" +"react-native-passkey@npm:3.0.0": + version: 3.0.0 + resolution: "react-native-passkey@npm:3.0.0" peerDependencies: react: "*" react-native: "*" - checksum: 80631e1548cea8cc172a263cdd90079ff2db525c5e728d554fc8f50a61382556114eb17984371e4514e23c94adb66b4a0b9f5fe5ac5ff752501fad0adbd8cd31 + checksum: 5533de503b1911e6337c82f557e7ceb00b0760a00754988e082972fefbadab279f149617ed5b55a6512e5efe3d54a3174f90f4364e3c0bc4c1043f372ce266e3 languageName: node linkType: hard @@ -26645,7 +26645,7 @@ react-native-webview@ava-labs/react-native-webview: peerDependencies: react: "*" react-native: "*" - checksum: d396f3dea807077e8789e1d463c87024e1633481d3dff53c0650c82a08d8b7d699db97ceab4e8d2c9de85c3d5378d192c968487254c62edadff77e82a9b8c929 + checksum: 6e268fad7aa8b8e56fd28cc95f94f35a33fdac4cec0085ae71a766d092760e3f9af35218706113ff7ae99a74baabc5112d32005dce9e66bdf4fda676fad9aa4e languageName: node linkType: hard From 775454b1161360e978b07af17cdbb46e93265882 Mon Sep 17 00:00:00 2001 From: ruijialin Date: Thu, 16 Jan 2025 16:45:14 -0500 Subject: [PATCH 2/5] remove passkey patch --- .../patches/react-native-passkey+2.1.1.patch | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 packages/core-mobile/patches/react-native-passkey+2.1.1.patch diff --git a/packages/core-mobile/patches/react-native-passkey+2.1.1.patch b/packages/core-mobile/patches/react-native-passkey+2.1.1.patch deleted file mode 100644 index b16cde472e..0000000000 --- a/packages/core-mobile/patches/react-native-passkey+2.1.1.patch +++ /dev/null @@ -1,32 +0,0 @@ -diff --git a/node_modules/react-native-passkey/ios/Passkey.swift b/node_modules/react-native-passkey/ios/Passkey.swift -index bcc07c2..c2f7196 100644 ---- a/node_modules/react-native-passkey/ios/Passkey.swift -+++ b/node_modules/react-native-passkey/ios/Passkey.swift -@@ -82,18 +82,15 @@ class Passkey: NSObject { - if #available(iOS 15.0, *) { - let authController: ASAuthorizationController; - -- // Check if authentication should proceed with a security key -- if (securityKey) { -- // Create a new assertion request with security key -- let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: identifier); -- let authRequest = securityKeyProvider.createCredentialAssertionRequest(challenge: challengeData); -- authController = ASAuthorizationController(authorizationRequests: [authRequest]); -- } else { -- // Create a new assertion request without security key -- let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: identifier); -- let authRequest = platformProvider.createCredentialAssertionRequest(challenge: challengeData); -- authController = ASAuthorizationController(authorizationRequests: [authRequest]); -- } -+ // Create a new assertion request with security key -+ let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: identifier); -+ let securityKeyAuthRequest = securityKeyProvider.createCredentialAssertionRequest(challenge: challengeData); -+ -+ // Create a new assertion request without security key -+ let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: identifier); -+ let platformAuthRequest = platformProvider.createCredentialAssertionRequest(challenge: challengeData); -+ -+ authController = ASAuthorizationController(authorizationRequests: [platformAuthRequest, securityKeyAuthRequest]); - - // Set up a PasskeyDelegate instance with a callback function - self.passKeyDelegate = PasskeyDelegate { error, result in From 56e69169a76f56f9f1052eda67d9f163b9df14fe Mon Sep 17 00:00:00 2001 From: ruijialin Date: Thu, 16 Jan 2025 16:46:02 -0500 Subject: [PATCH 3/5] revert .xcode.env --- packages/core-mobile/ios/.xcode.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-mobile/ios/.xcode.env b/packages/core-mobile/ios/.xcode.env index 5a6cb48387..ab08f29bf3 100644 --- a/packages/core-mobile/ios/.xcode.env +++ b/packages/core-mobile/ios/.xcode.env @@ -8,7 +8,7 @@ # Customize the NODE_BINARY variable here. # For example, to use nvm with brew, add the following line # . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) +export NODE_BINARY=node export SENTRY_PROPERTIES=sentry.properties export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map" export BUNDLE_COMMAND="ram-bundle" \ No newline at end of file From 625c33139c6ee87ae1f041414a362c09cb47dce9 Mon Sep 17 00:00:00 2001 From: ruijialin Date: Thu, 16 Jan 2025 16:59:40 -0500 Subject: [PATCH 4/5] remove console --- .../features/onboarding/hooks/useRegisterAndAuthenticateFido.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts b/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts index 1866b3c1ab..324cdf71c9 100644 --- a/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts +++ b/packages/core-mobile/app/new/features/onboarding/hooks/useRegisterAndAuthenticateFido.ts @@ -60,7 +60,6 @@ export const useRegisterAndAuthenticateFido = (): { onAccountVerified() }) } catch (e) { - console.log('registerAndAuthenticateFido', e) Logger.error(`${fidoType} registration failed`, e) showSnackbar(`Unable to register ${fidoType}`) } finally { From d3add417e3b80940e914fd60494a6f82b4e4e016 Mon Sep 17 00:00:00 2001 From: ruijialin Date: Fri, 17 Jan 2025 10:31:59 -0500 Subject: [PATCH 5/5] move fidoNameInput file --- .../features/onboarding/components/FidoNameInput.tsx | 2 +- .../new/routes/onboarding/seedless/(fido)/_layout.tsx | 10 ++++++++++ .../onboarding/seedless/{ => (fido)}/fidoNameInput.tsx | 0 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 packages/core-mobile/app/new/routes/onboarding/seedless/(fido)/_layout.tsx rename packages/core-mobile/app/new/routes/onboarding/seedless/{ => (fido)}/fidoNameInput.tsx (100%) diff --git a/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx b/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx index 13bfa89913..e355865bc1 100644 --- a/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx +++ b/packages/core-mobile/app/new/features/onboarding/components/FidoNameInput.tsx @@ -9,7 +9,7 @@ import { TouchableOpacity } from '@avalabs/k2-alpine' import BlurredBarsContentLayout from 'common/components/BlurredBarsContentLayout' -import { FIDONameInputProps } from 'new/routes/onboarding/seedless/fidoNameInput' +import { FIDONameInputProps } from 'new/routes/onboarding/seedless/(fido)/fidoNameInput' const FidoNameInput = ({ title, diff --git a/packages/core-mobile/app/new/routes/onboarding/seedless/(fido)/_layout.tsx b/packages/core-mobile/app/new/routes/onboarding/seedless/(fido)/_layout.tsx new file mode 100644 index 0000000000..c582089094 --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/seedless/(fido)/_layout.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { Stack } from 'common/components/Stack' + +export default function FidoLayout(): JSX.Element { + return ( + + + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/seedless/fidoNameInput.tsx b/packages/core-mobile/app/new/routes/onboarding/seedless/(fido)/fidoNameInput.tsx similarity index 100% rename from packages/core-mobile/app/new/routes/onboarding/seedless/fidoNameInput.tsx rename to packages/core-mobile/app/new/routes/onboarding/seedless/(fido)/fidoNameInput.tsx