From 93f1357c0190e3cf37ae80f15dcf9c1bdaff812d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Tue, 10 Dec 2024 17:52:53 +0800 Subject: [PATCH 01/13] fix:add passcode component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- .../OneKeyWallet.xcodeproj/project.pbxproj | 2 - apps/mobile/ios/Podfile.lock | 6 +- development/spellCheckerSkipWords.js | 3 + package.json | 1 + packages/components/src/forms/Form/index.tsx | 11 +- .../src/services/ServicePassword/index.ts | 23 +- .../src/services/ServicePassword/types.ts | 5 + .../kit-bg/src/states/jotai/atoms/password.ts | 12 + .../Password/components/PassCodeInput.tsx | 100 +++++ .../Password/components/PasswordSetup.tsx | 368 ++++++++++++------ .../Password/components/PasswordVerify.tsx | 3 + .../container/PasswordSetupContainer.tsx | 67 ++-- .../container/PasswordUpdateContainer.tsx | 19 +- .../container/PasswordVerifyContainer.tsx | 6 +- yarn.lock | 16 + 15 files changed, 474 insertions(+), 168 deletions(-) create mode 100644 packages/kit/src/components/Password/components/PassCodeInput.tsx diff --git a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj index 3a6b56ebbcf..de985da7c6f 100644 --- a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj @@ -424,7 +424,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -436,7 +435,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 581406b7a54..e426930df5c 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -1688,7 +1688,7 @@ SPEC CHECKSUMS: Burnt: dde5dd245f124a4594098e3938ba71aae4ec83c3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 CocoaLumberjack: 5c7e64cdb877770859bddec4d3d5a0d7c9299df9 - DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 EXApplication: 137189a3f149b4e8e546884629392c3efc94cbd3 EXBarCodeScanner: d59fd943cebee3f913ebf4ffde0d05d344da8b78 EXConstants: 988aa430ca0f76b43cd46b66e7fae3287f9cc2fc @@ -1715,7 +1715,7 @@ SPEC CHECKSUMS: FBLazyVector: 9f533d5a4c75ca77c8ed774aced1a91a0701781e FBReactNativeSpec: 2db5940ee4b58968274eec0a4f1c736fc4caefa3 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 69ef571f3de08433d766d614c73a9838a06bf7eb + glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 39589e9c297d024e90fe68f6830ff86c4e01498a ImageColors: 88be684570585c07ae2750bff34eb7b78bfc53b4 IQKeyboardManagerSwift: c7955c0bdbf7b2eb29bb7daaa44e3d90f55a9a85 @@ -1821,7 +1821,7 @@ SPEC CHECKSUMS: SPIndicator: 93e0a4fb23de51294ac48e874c0f081a5e293e4f SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 - Yoga: 6d01ccde54c9f8b92492beb05d468dbfed1d9881 + Yoga: 07db09965bc46c4902e20d3ae6990d95e53af8a8 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 PODFILE CHECKSUM: 5b7f20a90e19262f325cab10e37056b7f3cd0ffb diff --git a/development/spellCheckerSkipWords.js b/development/spellCheckerSkipWords.js index ebfaf81d6f4..0465a38038e 100644 --- a/development/spellCheckerSkipWords.js +++ b/development/spellCheckerSkipWords.js @@ -775,4 +775,7 @@ module.exports = [ 'cacheable', 'benfen', 'bfc', + 'biometric', + 'biometrics', + 'Biometric', ]; diff --git a/package.json b/package.json index 0ba71bdc5fb..47f1ce8b0d4 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "react-dom": "18.2.0", "react-mobile-cropper": "^0.10.0", "react-native": "0.73.7", + "react-native-confirmation-code-field": "^7.4.0", "react-native-draggable-flatlist": "4.0.1", "react-native-reanimated": "3.6.1", "react-native-web": "0.18.12", diff --git a/packages/components/src/forms/Form/index.tsx b/packages/components/src/forms/Form/index.tsx index d930dad4dc2..3f22a25ad97 100644 --- a/packages/components/src/forms/Form/index.tsx +++ b/packages/components/src/forms/Form/index.tsx @@ -97,6 +97,14 @@ type IFieldProps = Omit, 'render'> & PropsWithChildren<{ testID?: string; label?: string; + display?: + | 'inherit' + | 'none' + | 'inline' + | 'block' + | 'contents' + | 'flex' + | 'inline-flex'; description?: string | ReactNode; horizontal?: boolean; optional?: boolean; @@ -107,6 +115,7 @@ function Field({ name, label, optional, + display, description, rules, children, @@ -139,7 +148,7 @@ function Field({ control={control} rules={rules} render={({ field }) => ( -
+
{ - await passwordPersistAtom.set((v) => ({ ...v, isPasswordSet: isSet })); + async setPasswordSetStatus( + isSet: boolean, + passMode?: EPasswordMode, + ): Promise { + await passwordPersistAtom.set((v) => ({ + ...v, + isPasswordSet: isSet, + ...(passMode ? { passwordMode: passMode } : {}), + })); } // password actions -------------- @backgroundMethod() - async setPassword(password: string): Promise { + async setPassword( + password: string, + passMode?: EPasswordMode, + ): Promise { ensureSensitiveTextEncoded(password); await this.validatePassword({ password, skipDBVerify: true }); try { await this.unLockApp(); await this.saveBiologyAuthPassword(password); await this.setCachedPassword(password); - await this.setPasswordSetStatus(true); + await this.setPasswordSetStatus(true, passMode); await localDb.setPassword({ password }); return password; } catch (e) { @@ -362,6 +372,7 @@ export default class ServicePassword extends ServiceBase { async updatePassword( oldPassword: string, newPassword: string, + passMode?: EPasswordMode, ): Promise { ensureSensitiveTextEncoded(oldPassword); ensureSensitiveTextEncoded(newPassword); @@ -371,7 +382,7 @@ export default class ServicePassword extends ServiceBase { await this.backgroundApi.serviceAddressBook.updateHash(newPassword); await this.saveBiologyAuthPassword(newPassword); await this.setCachedPassword(newPassword); - await this.setPasswordSetStatus(true); + await this.setPasswordSetStatus(true, passMode); // update v5 db password await localDb.updatePassword({ oldPassword, newPassword }); // update v4 db password diff --git a/packages/kit-bg/src/services/ServicePassword/types.ts b/packages/kit-bg/src/services/ServicePassword/types.ts index 274a712f70d..de7faa6b9f9 100644 --- a/packages/kit-bg/src/services/ServicePassword/types.ts +++ b/packages/kit-bg/src/services/ServicePassword/types.ts @@ -6,3 +6,8 @@ export enum EPasswordPromptType { PASSWORD_SETUP = 'setup', PASSWORD_VERIFY = 'verify', } + +export enum EPasswordMode { + PASSCODE = 'passcode', + PASSWORD = 'password', +} diff --git a/packages/kit-bg/src/states/jotai/atoms/password.ts b/packages/kit-bg/src/states/jotai/atoms/password.ts index b4f5216be8e..940a4e1db4d 100644 --- a/packages/kit-bg/src/states/jotai/atoms/password.ts +++ b/packages/kit-bg/src/states/jotai/atoms/password.ts @@ -5,6 +5,7 @@ import { isSupportWebAuth } from '@onekeyhq/shared/src/webAuth'; import { EPasswordVerifyStatus } from '@onekeyhq/shared/types/password'; import { biologyAuthUtils } from '../../../services/ServicePassword/biologyAuthUtils'; +import { EPasswordMode } from '../../../services/ServicePassword/types'; import { EAtomNames } from '../atomNames'; import { globalAtom, globalAtomComputed } from '../utils'; @@ -60,12 +61,14 @@ export type IPasswordPersistAtom = { webAuthCredentialId: string; appLockDuration: number; enableSystemIdleLock: boolean; + passwordMode: EPasswordMode; }; export const passwordAtomInitialValue: IPasswordPersistAtom = { isPasswordSet: false, webAuthCredentialId: '', appLockDuration: 240, enableSystemIdleLock: true, + passwordMode: EPasswordMode.PASSWORD, }; export const { target: passwordPersistAtom, use: usePasswordPersistAtom } = globalAtom({ @@ -74,6 +77,15 @@ export const { target: passwordPersistAtom, use: usePasswordPersistAtom } = initialValue: passwordAtomInitialValue, }); +export const { target: passwordModeAtom, use: usePasswordModeAtom } = + globalAtomComputed((get) => { + const { passwordMode, isPasswordSet } = get(passwordPersistAtom.atom()); + if (platformEnv.isNative && !isPasswordSet) { + return EPasswordMode.PASSCODE; + } + return passwordMode; + }); + export const { target: systemIdleLockSupport, use: useSystemIdleLockSupport } = globalAtomComputed>(async (get) => { const platformSupport = platformEnv.isExtension || platformEnv.isDesktop; diff --git a/packages/kit/src/components/Password/components/PassCodeInput.tsx b/packages/kit/src/components/Password/components/PassCodeInput.tsx new file mode 100644 index 00000000000..119f7e50a05 --- /dev/null +++ b/packages/kit/src/components/Password/components/PassCodeInput.tsx @@ -0,0 +1,100 @@ +import { useState } from 'react'; + +import { StyleSheet, Text } from 'react-native'; +import { + CodeField, + Cursor, + useBlurOnFulfill, + useClearByFocusCell, +} from 'react-native-confirmation-code-field'; + +import { IconButton, XStack } from '@onekeyhq/components'; + +export const PIN_CELL_COUNT = 6; + +const cellStyles = StyleSheet.create({ + cell: { + flex: 1, + fontSize: 30, + fontWeight: '700', + textAlign: 'center', + marginLeft: 8, + borderRadius: 6, + backgroundColor: '#eee', + }, + focusCell: { + borderColor: '#000', + }, +}); + +const PassCodeInput = ({ + onPinCodeChange, + disabled, +}: { + onPinCodeChange?: (pin: string) => void; + disabled?: boolean; +}) => { + const [pinValue, setPinValue] = useState(''); + + const pinInputRef = useBlurOnFulfill({ + value: pinValue, + cellCount: PIN_CELL_COUNT, + }); + const [props, getCellOnLayoutHandler] = useClearByFocusCell({ + value: pinValue, + setValue: setPinValue, + }); + const [enableMask, setEnableMask] = useState(true); + const toggleMask = () => setEnableMask((f) => !f); + const renderCell = ({ + index, + symbol, + isFocused, + }: { + index: number; + symbol: string; + isFocused: boolean; + }) => { + let textChild = null; + if (symbol) { + textChild = enableMask ? '•' : symbol; + } else if (isFocused) { + textChild = ; + } + + return ( + + {textChild} + + ); + }; + return ( + + { + setPinValue(text); + onPinCodeChange?.(text); + }} + cellCount={PIN_CELL_COUNT} + keyboardType="number-pad" + textContentType="oneTimeCode" + renderCell={renderCell} + /> + + + ); +}; + +export default PassCodeInput; diff --git a/packages/kit/src/components/Password/components/PasswordSetup.tsx b/packages/kit/src/components/Password/components/PasswordSetup.tsx index 414572923d4..87914d7e59c 100644 --- a/packages/kit/src/components/Password/components/PasswordSetup.tsx +++ b/packages/kit/src/components/Password/components/PasswordSetup.tsx @@ -1,19 +1,35 @@ -import { memo, useState } from 'react'; +import { memo, useEffect, useMemo, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; -import type { IButtonProps } from '@onekeyhq/components'; -import { Button, Form, Input, Unspaced, useForm } from '@onekeyhq/components'; +import type { IScrollViewRef } from '@onekeyhq/components'; +import { + Button, + Form, + Input, + ScrollView, + Unspaced, + XStack, + useForm, +} from '@onekeyhq/components'; +import { X } from '@onekeyhq/components/src/primitives/Icon/react/brand'; +import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import { PasswordRegex, getPasswordKeyboardType } from '../utils'; +import PassCodeInput, { PIN_CELL_COUNT } from './PassCodeInput'; + export interface IPasswordSetupForm { password: string; confirmPassword: string; + passwordMode: EPasswordMode; + passCode: string; + confirmPassCode: string; } interface IPasswordSetupProps { loading: boolean; + passwordMode: EPasswordMode; onSetupPassword: (data: IPasswordSetupForm) => void; biologyAuthSwitchContainer?: React.ReactNode; confirmBtnText?: string; @@ -21,135 +37,244 @@ interface IPasswordSetupProps { const PasswordSetup = ({ loading, + passwordMode, onSetupPassword, confirmBtnText, biologyAuthSwitchContainer, }: IPasswordSetupProps) => { const intl = useIntl(); + const [currentPasswordMode, setCurrentPasswordMode] = useState(passwordMode); + const [passCodeConfirm, setPassCodeConfirm] = useState(false); + useEffect(() => { + setCurrentPasswordMode(passwordMode); + }, [passwordMode]); const form = useForm({ mode: 'onSubmit', reValidateMode: 'onSubmit', defaultValues: { password: '', + passCode: '', confirmPassword: '', + confirmPassCode: '', + passwordMode: currentPasswordMode, }, }); const [secureEntry, setSecureEntry] = useState(true); const [secureReentry, setSecureReentry] = useState(true); + const passCodeFirstStep = useMemo( + () => currentPasswordMode === EPasswordMode.PASSCODE && !passCodeConfirm, + [currentPasswordMode, passCodeConfirm], + ); + const confirmBtnTextMemo = useMemo(() => { + if (passCodeFirstStep) { + return intl.formatMessage({ id: ETranslations.global_next }); + } + return ( + confirmBtnText ?? + intl.formatMessage({ id: ETranslations.auth_set_password }) + ); + }, [confirmBtnText, intl, passCodeFirstStep]); + const onPassCodeNext = () => { + setPassCodeConfirm(true); + }; return (
- + { - form.clearErrors(); - }, - }} - > - text.replace(PasswordRegex, '')} - secureTextEntry={secureEntry} - addOns={[ - { - iconName: secureEntry ? 'EyeOutline' : 'EyeOffOutline', - onPress: () => { - setSecureEntry(!secureEntry); + onChange: () => { + form.clearErrors(); }, - testID: `password-eye-${secureEntry ? 'off' : 'on'}`, - }, - ]} - testID="password" - /> - - { - const state = form.getFieldState('password'); - if (!state.error) { - return v !== values.password - ? intl.formatMessage({ - id: ETranslations.auth_error_password_not_match, - }) - : undefined; - } - return undefined; - }, - }, - onChange: () => { - form.clearErrors('confirmPassword'); - }, - }} - > - text.replace(PasswordRegex, '')} - secureTextEntry={secureReentry} - addOns={[ - { - iconName: secureReentry ? 'EyeOutline' : 'EyeOffOutline', - onPress: () => { - setSecureReentry(!secureReentry); + }} + > + text.replace(PasswordRegex, '')} + secureTextEntry={secureEntry} + addOns={[ + { + iconName: secureEntry ? 'EyeOutline' : 'EyeOffOutline', + onPress: () => { + setSecureEntry(!secureEntry); + }, + testID: `password-eye-${secureEntry ? 'off' : 'on'}`, + }, + ]} + testID="password" + /> + + { + const state = form.getFieldState('password'); + if (!state.error) { + return v !== values.password + ? intl.formatMessage({ + id: ETranslations.auth_error_password_not_match, + }) + : undefined; + } + return undefined; + }, + }, + onChange: () => { + form.clearErrors('confirmPassword'); }, - testID: `confirm-password-eye-${secureReentry ? 'off' : 'on'}`, - }, - ]} - testID="confirm-password" - /> - - {biologyAuthSwitchContainer} + }} + > + text.replace(PasswordRegex, '')} + secureTextEntry={secureReentry} + addOns={[ + { + iconName: secureReentry ? 'EyeOutline' : 'EyeOffOutline', + onPress: () => { + setSecureReentry(!secureReentry); + }, + testID: `confirm-password-eye-${ + secureReentry ? 'off' : 'on' + }`, + }, + ]} + testID="confirm-password" + /> + + + ) : ( + <> + + v + ? undefined + : intl.formatMessage({ + id: ETranslations.auth_error_password_empty, + }), + minLength: (v: string) => + v.length >= PIN_CELL_COUNT + ? undefined + : intl.formatMessage( + { id: ETranslations.auth_error_password_too_short }, + { + length: PIN_CELL_COUNT, + }, + ), + }, + onChange: () => { + form.clearErrors(); + }, + }} + > + { + form.setValue('passCode', pin); + form.clearErrors('passCode'); + }} + /> + + { + if (passCodeFirstStep) { + return undefined; + } + const state = form.getFieldState('passCode'); + if (!state.error) { + return v !== values.passCode + ? intl.formatMessage({ + id: ETranslations.auth_error_password_not_match, + }) + : undefined; + } + return undefined; + }, + }, + onChange: () => { + form.clearErrors('confirmPassCode'); + }, + }} + > + { + form.setValue('confirmPassCode', pin); + form.clearErrors('confirmPassCode'); + }} + /> + + + )} + {!passCodeFirstStep ? ( + {biologyAuthSwitchContainer} + ) : null} +
); diff --git a/packages/kit/src/components/Password/components/PasswordVerify.tsx b/packages/kit/src/components/Password/components/PasswordVerify.tsx index 80ed4a86acc..4059654800b 100644 --- a/packages/kit/src/components/Password/components/PasswordVerify.tsx +++ b/packages/kit/src/components/Password/components/PasswordVerify.tsx @@ -13,6 +13,7 @@ import { useIntl } from 'react-intl'; import type { IKeyOfIcons, IPropsWithTestId } from '@onekeyhq/components'; import { Form, Input, useForm } from '@onekeyhq/components'; +import type { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { usePasswordAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; @@ -24,6 +25,7 @@ import { getPasswordKeyboardType } from '../utils'; interface IPasswordVerifyProps { authType: AuthenticationType[]; isEnable: boolean; + passwordMode: EPasswordMode; onPasswordChange: (e: any) => void; onBiologyAuth: () => void; onInputPasswordAuth: (data: IPasswordVerifyForm) => void; @@ -41,6 +43,7 @@ const PasswordVerify = ({ authType, isEnable, status, + passwordMode, onBiologyAuth, onPasswordChange, onInputPasswordAuth, diff --git a/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx b/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx index 44c49f2e991..0b5d866731a 100644 --- a/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx @@ -4,9 +4,12 @@ import { useIntl } from 'react-intl'; import { SizableText, Stack, Toast, XStack } from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; +import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { useSettingsPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { usePasswordBiologyAuthInfoAtom, + usePasswordModeAtom, + usePasswordPersistAtom, usePasswordWebAuthInfoAtom, } from '@onekeyhq/kit-bg/src/states/jotai/atoms/password'; import { ETranslations } from '@onekeyhq/shared/src/locale'; @@ -69,46 +72,43 @@ const PasswordSetupContainer = ({ onSetupRes }: IPasswordSetupProps) => { const [loading, setLoading] = useState(false); const [{ isSupport }] = usePasswordWebAuthInfoAtom(); const [{ isBiologyAuthSwitchOn }] = useSettingsPersistAtom(); + const [passwordMode] = usePasswordModeAtom(); const { setWebAuthEnable } = useWebAuthActions(); const onSetupPassword = useCallback( async (data: IPasswordSetupForm) => { - if (data.confirmPassword !== data.password) { + const { confirmPassword, confirmPassCode, passwordMode: mode } = data; + const finalPassword = + mode === EPasswordMode.PASSCODE ? confirmPassCode : confirmPassword; + setLoading(true); + try { + if (isBiologyAuthSwitchOn && isSupport) { + const res = await setWebAuthEnable(true); + if (!res) return; + } + const encodePassword = + await backgroundApiProxy.servicePassword.encodeSensitiveText({ + text: finalPassword, + }); + + const setUpPasswordRes = + await backgroundApiProxy.servicePassword.setPassword( + encodePassword, + mode, + ); + onSetupRes(setUpPasswordRes); + Toast.success({ + title: intl.formatMessage({ id: ETranslations.auth_password_set }), + }); + } catch (e) { + console.log('e.stack', (e as Error)?.stack); + console.error(e); Toast.error({ title: intl.formatMessage({ - id: ETranslations.auth_error_password_not_match, + id: ETranslations.feedback_password_set_failed, }), }); - } else { - setLoading(true); - try { - if (isBiologyAuthSwitchOn && isSupport) { - const res = await setWebAuthEnable(true); - if (!res) return; - } - const encodePassword = - await backgroundApiProxy.servicePassword.encodeSensitiveText({ - text: data.password, - }); - - const setUpPasswordRes = - await backgroundApiProxy.servicePassword.setPassword( - encodePassword, - ); - onSetupRes(setUpPasswordRes); - Toast.success({ - title: intl.formatMessage({ id: ETranslations.auth_password_set }), - }); - } catch (e) { - console.log('e.stack', (e as Error)?.stack); - console.error(e); - Toast.error({ - title: intl.formatMessage({ - id: ETranslations.feedback_password_set_failed, - }), - }); - } finally { - setLoading(false); - } + } finally { + setLoading(false); } }, [intl, isBiologyAuthSwitchOn, isSupport, onSetupRes, setWebAuthEnable], @@ -117,6 +117,7 @@ const PasswordSetupContainer = ({ onSetupRes }: IPasswordSetupProps) => { return ( diff --git a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx index ef87c5d95c5..f6be406b032 100644 --- a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx @@ -4,6 +4,8 @@ import { useIntl } from 'react-intl'; import { Toast } from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; +import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; +import { usePasswordModeAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import PasswordSetup from '../components/PasswordSetup'; @@ -20,26 +22,24 @@ const PasswordUpdateContainer = ({ }: IPasswordUpdateContainerProps) => { const [loading, setLoading] = useState(false); const intl = useIntl(); + const [passwordMode] = usePasswordModeAtom(); const onUpdatePassword = useCallback( async (data: IPasswordSetupForm) => { + const { confirmPassword, confirmPassCode, passwordMode: mode } = data; + const finalPassword = + mode === EPasswordMode.PASSCODE ? confirmPassCode : confirmPassword; + setLoading(true); setLoading(true); try { - if (data.confirmPassword !== data.password) { - Toast.error({ - title: intl.formatMessage({ - id: ETranslations.auth_error_password_not_match, - }), - }); - return; - } const encodeNewPassword = await backgroundApiProxy.servicePassword.encodeSensitiveText({ - text: data.password, + text: finalPassword, }); const updatedPassword = await backgroundApiProxy.servicePassword.updatePassword( oldEncodedPassword, encodeNewPassword, + mode, ); onUpdateRes(updatedPassword); Toast.success({ @@ -60,6 +60,7 @@ const PasswordUpdateContainer = ({ return ( diff --git a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx index e95ffd92f01..d02ad43f124 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { AuthenticationType } from 'expo-local-authentication'; import { useIntl } from 'react-intl'; @@ -10,6 +10,7 @@ import { useSettingsPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms' import { usePasswordAtom, usePasswordBiologyAuthInfoAtom, + usePasswordModeAtom, usePasswordPersistAtom, } from '@onekeyhq/kit-bg/src/states/jotai/atoms/password'; import { dismissKeyboard } from '@onekeyhq/shared/src/keyboard'; @@ -45,7 +46,7 @@ const PasswordVerifyContainer = ({ const [{ isBiologyAuthSwitchOn }] = useSettingsPersistAtom(); const [hasCachedPassword, setHasCachedPassword] = useState(false); const [hasSecurePassword, setHasSecurePassword] = useState(false); - + const [passwordMode] = usePasswordModeAtom(); const isExtLockAndNoCachePassword = Boolean( platformEnv.isExtension && name === 'lock' && !hasCachedPassword, ); @@ -268,6 +269,7 @@ const PasswordVerifyContainer = ({ return ( { setPasswordAtom((v) => ({ ...v, diff --git a/yarn.lock b/yarn.lock index 6f616b51686..233b87f6700 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6746,6 +6746,7 @@ __metadata: react-dom: "npm:18.2.0" react-mobile-cropper: "npm:^0.10.0" react-native: "npm:0.73.7" + react-native-confirmation-code-field: "npm:^7.4.0" react-native-copy-asset: "npm:^3.0.2" react-native-draggable-flatlist: "npm:4.0.1" react-native-flipper: "npm:0.201.0" @@ -33752,6 +33753,21 @@ __metadata: languageName: node linkType: hard +"react-native-confirmation-code-field@npm:^7.4.0": + version: 7.4.0 + resolution: "react-native-confirmation-code-field@npm:7.4.0" + peerDependencies: + react: ">=16.4.0" + react-native: ">=0.64.0" + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + checksum: 10/ababeeb36067574f57c907f15d6c9ac6831e06076ed2882f382053ccf7c9912377787a6a56876cc6b63b2e8bab9ba179845046e8eed2cd9e2bf59bc00347ade5 + languageName: node + linkType: hard + "react-native-copy-asset@npm:^3.0.2": version: 3.0.2 resolution: "react-native-copy-asset@npm:3.0.2" From 3e4d68baf8677f38bf524eb6a8718a939c8e9c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Tue, 10 Dec 2024 20:36:17 +0800 Subject: [PATCH 02/13] fix: code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- .../Password/components/PassCodeInput.tsx | 15 +- .../Password/components/PasswordSetup.tsx | 67 ++++--- .../Password/components/PasswordVerify.tsx | 176 ++++++++++++------ .../container/PasswordVerifyContainer.tsx | 18 +- packages/kit/src/components/Password/utils.ts | 2 + 5 files changed, 183 insertions(+), 95 deletions(-) diff --git a/packages/kit/src/components/Password/components/PassCodeInput.tsx b/packages/kit/src/components/Password/components/PassCodeInput.tsx index 119f7e50a05..5d96b74506c 100644 --- a/packages/kit/src/components/Password/components/PassCodeInput.tsx +++ b/packages/kit/src/components/Password/components/PassCodeInput.tsx @@ -29,10 +29,14 @@ const cellStyles = StyleSheet.create({ const PassCodeInput = ({ onPinCodeChange, + showMask, disabled, + testId, }: { onPinCodeChange?: (pin: string) => void; disabled?: boolean; + testId?: string; + showMask?: boolean; }) => { const [pinValue, setPinValue] = useState(''); @@ -76,6 +80,7 @@ const PassCodeInput = ({ - + {showMask ? ( + + ) : null} ); }; diff --git a/packages/kit/src/components/Password/components/PasswordSetup.tsx b/packages/kit/src/components/Password/components/PasswordSetup.tsx index 87914d7e59c..d82708af5de 100644 --- a/packages/kit/src/components/Password/components/PasswordSetup.tsx +++ b/packages/kit/src/components/Password/components/PasswordSetup.tsx @@ -1,22 +1,17 @@ -import { memo, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; -import type { IScrollViewRef } from '@onekeyhq/components'; -import { - Button, - Form, - Input, - ScrollView, - Unspaced, - XStack, - useForm, -} from '@onekeyhq/components'; -import { X } from '@onekeyhq/components/src/primitives/Icon/react/brand'; +import { Button, Form, Input, Unspaced, useForm } from '@onekeyhq/components'; import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { ETranslations } from '@onekeyhq/shared/src/locale'; +import platformEnv from '@onekeyhq/shared/src/platformEnv'; -import { PasswordRegex, getPasswordKeyboardType } from '../utils'; +import { + PassCodeRegex, + PasswordRegex, + getPasswordKeyboardType, +} from '../utils'; import PassCodeInput, { PIN_CELL_COUNT } from './PassCodeInput'; @@ -220,6 +215,12 @@ const PasswordSetup = ({ length: PIN_CELL_COUNT, }, ), + regexCheck: (v: string) => + v.replace(PassCodeRegex, '') === v + ? undefined + : intl.formatMessage({ + id: ETranslations.global_hex_data_error, + }), }, onChange: () => { form.clearErrors(); @@ -232,6 +233,8 @@ const PasswordSetup = ({ form.setValue('passCode', pin); form.clearErrors('passCode'); }} + testId="pass-code" + showMask /> @@ -291,24 +296,26 @@ const PasswordSetup = ({ > {confirmBtnTextMemo} - + ? 'Passcode' + : 'Password' + }`} + + ) : null} ); }; diff --git a/packages/kit/src/components/Password/components/PasswordVerify.tsx b/packages/kit/src/components/Password/components/PasswordVerify.tsx index 4059654800b..65fcb93fa60 100644 --- a/packages/kit/src/components/Password/components/PasswordVerify.tsx +++ b/packages/kit/src/components/Password/components/PasswordVerify.tsx @@ -12,8 +12,8 @@ import { AuthenticationType } from 'expo-local-authentication'; import { useIntl } from 'react-intl'; import type { IKeyOfIcons, IPropsWithTestId } from '@onekeyhq/components'; -import { Form, Input, useForm } from '@onekeyhq/components'; -import type { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; +import { Button, Form, Input, useForm } from '@onekeyhq/components'; +import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { usePasswordAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; @@ -22,6 +22,8 @@ import { EPasswordVerifyStatus } from '@onekeyhq/shared/types/password'; import { useHandleAppStateActive } from '../../../hooks/useHandleAppStateActive'; import { getPasswordKeyboardType } from '../utils'; +import PassCodeInput from './PassCodeInput'; + interface IPasswordVerifyProps { authType: AuthenticationType[]; isEnable: boolean; @@ -35,8 +37,9 @@ interface IPasswordVerifyProps { }; } -interface IPasswordVerifyForm { +export interface IPasswordVerifyForm { password: string; + passCode: string; } const PasswordVerify = ({ @@ -52,7 +55,7 @@ const PasswordVerify = ({ const form = useForm({ mode: 'onSubmit', reValidateMode: 'onSubmit', - defaultValues: { password: '' }, + defaultValues: { password: '', passCode: '' }, }); const timeOutRef = useRef(null); const isEnableRef = useRef(isEnable); @@ -63,7 +66,9 @@ const PasswordVerify = ({ // enable first false should wait some logic to get final value timeOutRef.current = setTimeout(() => { if (!isEnableRef.current) { - form.setFocus('password'); + form.setFocus( + passwordMode === EPasswordMode.PASSWORD ? 'password' : 'passCode', + ); } }, 500); return () => { @@ -75,8 +80,24 @@ const PasswordVerify = ({ }, []); const [secureEntry, setSecureEntry] = useState(true); const lastTime = useRef(0); - const passwordInput = form.watch('password'); + const passwordInput = form.watch( + passwordMode === EPasswordMode.PASSWORD ? 'password' : 'passCode', + ); const [{ manualLocking }] = usePasswordAtom(); + const biologyAuthIconName = useMemo(() => { + let iconName: IKeyOfIcons = + authType && + (authType.includes(AuthenticationType.FACIAL_RECOGNITION) || + authType.includes(AuthenticationType.IRIS)) + ? 'FaceIdOutline' + : 'TouchId2Outline'; + if (platformEnv.isDesktopWin) { + iconName = 'WindowsHelloSolid'; + } else if (platformEnv.isExtension) { + iconName = 'PassKeySolid'; + } + return iconName; + }, [authType]); const rightActions = useMemo(() => { const actions: IPropsWithTestId<{ iconName?: IKeyOfIcons; @@ -84,19 +105,8 @@ const PasswordVerify = ({ loading?: boolean; }>[] = []; if (isEnable && !passwordInput) { - let iconName: IKeyOfIcons = - authType && - (authType.includes(AuthenticationType.FACIAL_RECOGNITION) || - authType.includes(AuthenticationType.IRIS)) - ? 'FaceIdOutline' - : 'TouchId2Outline'; - if (platformEnv.isDesktopWin) { - iconName = 'WindowsHelloSolid'; - } else if (platformEnv.isExtension) { - iconName = 'PassKeySolid'; - } actions.push({ - iconName, + iconName: biologyAuthIconName, onPress: onBiologyAuth, loading: status.value === EPasswordVerifyStatus.VERIFYING, }); @@ -119,7 +129,7 @@ const PasswordVerify = ({ }, [ isEnable, passwordInput, - authType, + biologyAuthIconName, onBiologyAuth, status.value, secureEntry, @@ -128,13 +138,15 @@ const PasswordVerify = ({ ]); useEffect(() => { + const fieldName = + passwordMode === EPasswordMode.PASSWORD ? 'password' : 'passCode'; if (status.value === EPasswordVerifyStatus.ERROR) { - form.setError('password', { message: status.message }); - form.setFocus('password'); + form.setError(fieldName, { message: status.message }); + form.setFocus(fieldName); } else { - form.clearErrors('password'); + form.clearErrors(fieldName); } - }, [form, status]); + }, [form, passwordMode, status]); useLayoutEffect(() => { if ( @@ -168,38 +180,92 @@ const PasswordVerify = ({ return (
- - text.replace(PasswordRegex, '')} - onChangeText={(text) => text} - keyboardType={getPasswordKeyboardType(!secureEntry)} - secureTextEntry={secureEntry} - // fix Keyboard Flickering on TextInput with secureTextEntry #39411 - // https://github.com/facebook/react-native/issues/39411 - textContentType="oneTimeCode" - onSubmitEditing={form.handleSubmit(onInputPasswordAuth)} - addOns={rightActions} - testID="password-input" - /> - + {passwordMode === EPasswordMode.PASSWORD ? ( + + text.replace(PasswordRegex, '')} + onChangeText={(text) => text} + keyboardType={getPasswordKeyboardType(!secureEntry)} + secureTextEntry={secureEntry} + // fix Keyboard Flickering on TextInput with secureTextEntry #39411 + // https://github.com/facebook/react-native/issues/39411 + textContentType="oneTimeCode" + onSubmitEditing={form.handleSubmit(onInputPasswordAuth)} + addOns={rightActions} + testID="password-input" + /> + + ) : ( + <> + + v + ? undefined + : intl.formatMessage({ + id: ETranslations.auth_error_password_empty, + }), + }, + onChange: onPasswordChange, + }} + > + { + form.setValue('passCode', pin); + form.clearErrors('passCode'); + }} + testId="pass-code-input" + /> + + {isEnable && !passwordInput ? ( + + ) : ( + + )} + + )}
); }; diff --git a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx index d02ad43f124..2d1b633086c 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx @@ -6,6 +6,7 @@ import { useIntl } from 'react-intl'; import { Stack } from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; import { biologyAuthUtils } from '@onekeyhq/kit-bg/src/services/ServicePassword/biologyAuthUtils'; +import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { useSettingsPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { usePasswordAtom, @@ -22,6 +23,7 @@ import { EPasswordVerifyStatus } from '@onekeyhq/shared/types/password'; import { useWebAuthActions } from '../../BiologyAuthComponent/hooks/useWebAuthActions'; import PasswordVerify from '../components/PasswordVerify'; +import type { IPasswordVerifyForm } from '../components/PasswordVerify'; import type { LayoutChangeEvent } from 'react-native'; interface IPasswordVerifyProps { @@ -30,10 +32,6 @@ interface IPasswordVerifyProps { name?: 'lock'; } -interface IPasswordVerifyForm { - password: string; -} - const PasswordVerifyContainer = ({ onVerifyRes, onLayout, @@ -233,10 +231,12 @@ const PasswordVerifyContainer = ({ ...v, passwordVerifyStatus: { value: EPasswordVerifyStatus.VERIFYING }, })); + const finalPassword = + passwordMode === EPasswordMode.PASSWORD ? data.password : data.passCode; try { const encodePassword = await backgroundApiProxy.servicePassword.encodeSensitiveText({ - text: data.password, + text: finalPassword, }); const verifiedPassword = await backgroundApiProxy.servicePassword.verifyPassword({ @@ -263,7 +263,13 @@ const PasswordVerifyContainer = ({ })); } }, - [intl, onVerifyRes, passwordVerifyStatus.value, setPasswordAtom], + [ + intl, + onVerifyRes, + passwordMode, + passwordVerifyStatus.value, + setPasswordAtom, + ], ); return ( diff --git a/packages/kit/src/components/Password/utils.ts b/packages/kit/src/components/Password/utils.ts index 51e8dbfee13..2d6d22af493 100644 --- a/packages/kit/src/components/Password/utils.ts +++ b/packages/kit/src/components/Password/utils.ts @@ -5,6 +5,8 @@ import platformEnv from '@onekeyhq/shared/src/platformEnv'; export const PasswordRegex = /[^\x20-\x7E]/gm; +export const PassCodeRegex = /[^\d.]/gm; + export const getPasswordKeyboardType = (visible?: boolean) => { let keyboardType: ComponentProps['keyboardType'] = 'default'; if (platformEnv.isNativeIOS) { From cc33bfcd17fea8577f54d1eafa3f6036482d2f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Wed, 11 Dec 2024 18:10:01 +0800 Subject: [PATCH 03/13] fix: code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- packages/components/src/forms/Form/index.tsx | 7 +- .../src/services/ServicePassword/index.ts | 52 ++++-- .../src/services/ServicePassword/types.ts | 20 +++ .../kit-bg/src/states/jotai/atoms/password.ts | 6 + .../Password/components/PassCodeInput.tsx | 11 +- .../PassCodeProtectionDialogContent.tsx | 20 +++ .../Password/components/PasswordSetup.tsx | 2 - .../Password/components/PasswordVerify.tsx | 120 +++++++------ .../container/PassCodeProtectionSwitch.tsx | 20 +++ .../container/PasswordSetupContainer.tsx | 39 ++++- .../container/PasswordVerifyContainer.tsx | 163 +++++++++++++++--- .../Container/AppStateLockContainer/index.tsx | 5 +- .../views/Setting/pages/Protection/index.tsx | 9 + 13 files changed, 370 insertions(+), 104 deletions(-) create mode 100644 packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx create mode 100644 packages/kit/src/components/Password/container/PassCodeProtectionSwitch.tsx diff --git a/packages/components/src/forms/Form/index.tsx b/packages/components/src/forms/Form/index.tsx index 3f22a25ad97..2f110d49457 100644 --- a/packages/components/src/forms/Form/index.tsx +++ b/packages/components/src/forms/Form/index.tsx @@ -148,7 +148,12 @@ function Field({ control={control} rules={rules} render={({ field }) => ( -
+
128) { + if ( + passwordMode === EPasswordMode.PASSWORD && + (realPassword.length < PASSWORD_MIN_LENGTH || + realPassword.length > PASSWORD_MAX_LENGTH) + ) { throw new OneKeyErrors.PasswordStrengthValidationFailed(); } + if (passwordMode === EPasswordMode.PASSCODE) { + if (realPassword.length !== PASSCODE_LENGTH) { + throw new OneKeyErrors.PasswordStrengthValidationFailed(); + } + } // **** other rules .... } @@ -296,10 +314,12 @@ export default class ServicePassword extends ServiceBase { async validatePassword({ password, + passwordMode, newPassword, skipDBVerify, }: { password: string; + passwordMode: EPasswordMode; newPassword?: string; skipDBVerify?: boolean; }): Promise { @@ -307,9 +327,9 @@ export default class ServicePassword extends ServiceBase { if (newPassword) { ensureSensitiveTextEncoded(newPassword); } - this.validatePasswordValidRules(password); + this.validatePasswordValidRules(password, passwordMode); if (newPassword) { - this.validatePasswordValidRules(newPassword); + this.validatePasswordValidRules(newPassword, passwordMode); this.validatePasswordSame(password, newPassword); } if (!skipDBVerify) { @@ -351,15 +371,15 @@ export default class ServicePassword extends ServiceBase { @backgroundMethod() async setPassword( password: string, - passMode?: EPasswordMode, + passwordMode: EPasswordMode, ): Promise { ensureSensitiveTextEncoded(password); - await this.validatePassword({ password, skipDBVerify: true }); + await this.validatePassword({ password, passwordMode, skipDBVerify: true }); try { await this.unLockApp(); await this.saveBiologyAuthPassword(password); await this.setCachedPassword(password); - await this.setPasswordSetStatus(true, passMode); + await this.setPasswordSetStatus(true, passwordMode); await localDb.setPassword({ password }); return password; } catch (e) { @@ -372,17 +392,21 @@ export default class ServicePassword extends ServiceBase { async updatePassword( oldPassword: string, newPassword: string, - passMode?: EPasswordMode, + passwordMode: EPasswordMode, ): Promise { ensureSensitiveTextEncoded(oldPassword); ensureSensitiveTextEncoded(newPassword); - await this.validatePassword({ password: oldPassword, newPassword }); + await this.validatePassword({ + password: oldPassword, + newPassword, + passwordMode, + }); try { await this.backgroundApi.serviceAddressBook.updateHash(newPassword); await this.saveBiologyAuthPassword(newPassword); await this.setCachedPassword(newPassword); - await this.setPasswordSetStatus(true, passMode); + await this.setPasswordSetStatus(true, passwordMode); // update v5 db password await localDb.updatePassword({ oldPassword, newPassword }); // update v4 db password @@ -402,9 +426,11 @@ export default class ServicePassword extends ServiceBase { @backgroundMethod() async verifyPassword({ password, + passwordMode, isBiologyAuth, }: { password: string; + passwordMode: EPasswordMode; isBiologyAuth?: boolean; }): Promise { let verifyingPassword = password; @@ -412,7 +438,7 @@ export default class ServicePassword extends ServiceBase { verifyingPassword = await this.getBiologyAuthPassword(); } ensureSensitiveTextEncoded(verifyingPassword); - await this.validatePassword({ password: verifyingPassword }); + await this.validatePassword({ password: verifyingPassword, passwordMode }); await this.setCachedPassword(verifyingPassword); return verifyingPassword; } diff --git a/packages/kit-bg/src/services/ServicePassword/types.ts b/packages/kit-bg/src/services/ServicePassword/types.ts index de7faa6b9f9..4f7ba5b4ccc 100644 --- a/packages/kit-bg/src/services/ServicePassword/types.ts +++ b/packages/kit-bg/src/services/ServicePassword/types.ts @@ -11,3 +11,23 @@ export enum EPasswordMode { PASSCODE = 'passcode', PASSWORD = 'password', } + +export const PASSCODE_LENGTH = 6; +export const PASSCODE_PROTECTION_ATTEMPTS = 10; +export const PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX = 5; +export const PASSCODE_PROTECTION_ATTEMPTS_PER_MINUTE_MAP: Record< + string, + number +> = { + '5': 2, + '6': 10, + '7': 30, + '8': 60, + '9': 180, +}; + +export const BIOLOGY_AUTH_ATTEMPTS_FACE = 1; +export const BIOLOGY_AUTH_ATTEMPTS_FINGERPRINT = 2; + +export const PASSWORD_MIN_LENGTH = 8; +export const PASSWORD_MAX_LENGTH = 128; diff --git a/packages/kit-bg/src/states/jotai/atoms/password.ts b/packages/kit-bg/src/states/jotai/atoms/password.ts index 940a4e1db4d..f9ac2e29651 100644 --- a/packages/kit-bg/src/states/jotai/atoms/password.ts +++ b/packages/kit-bg/src/states/jotai/atoms/password.ts @@ -62,6 +62,9 @@ export type IPasswordPersistAtom = { appLockDuration: number; enableSystemIdleLock: boolean; passwordMode: EPasswordMode; + enablePasswordErrorProtection: boolean; + passwordErrorAttempts: number; + passwordErrorProtectionTime: number; }; export const passwordAtomInitialValue: IPasswordPersistAtom = { isPasswordSet: false, @@ -69,6 +72,9 @@ export const passwordAtomInitialValue: IPasswordPersistAtom = { appLockDuration: 240, enableSystemIdleLock: true, passwordMode: EPasswordMode.PASSWORD, + enablePasswordErrorProtection: false, + passwordErrorAttempts: 0, + passwordErrorProtectionTime: 0, }; export const { target: passwordPersistAtom, use: usePasswordPersistAtom } = globalAtom({ diff --git a/packages/kit/src/components/Password/components/PassCodeInput.tsx b/packages/kit/src/components/Password/components/PassCodeInput.tsx index 5d96b74506c..d8068356a51 100644 --- a/packages/kit/src/components/Password/components/PassCodeInput.tsx +++ b/packages/kit/src/components/Password/components/PassCodeInput.tsx @@ -29,12 +29,14 @@ const cellStyles = StyleSheet.create({ const PassCodeInput = ({ onPinCodeChange, + onComplete, + disabledComplete, showMask, - disabled, testId, }: { onPinCodeChange?: (pin: string) => void; - disabled?: boolean; + onComplete?: () => void; + disabledComplete?: boolean; testId?: string; showMask?: boolean; }) => { @@ -77,7 +79,7 @@ const PassCodeInput = ({ ); }; return ( - + { setPinValue(text); onPinCodeChange?.(text); + if (text.length === PIN_CELL_COUNT && !disabledComplete) { + onComplete?.(); + } }} cellCount={PIN_CELL_COUNT} keyboardType="number-pad" diff --git a/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx b/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx new file mode 100644 index 00000000000..7c03e8cf4ed --- /dev/null +++ b/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx @@ -0,0 +1,20 @@ +import { SizableText, XStack, YStack } from '@onekeyhq/components'; + +import PassCodeProtectionSwitch from '../container/PassCodeProtectionSwitch'; + +const PassCodeProtectionDialogContent = () => ( + + + 10 failed passcode attempts will reset app, you can change it anytime on + setting - protection. + + + + Reset App after 10 failed attempts + + + + +); + +export default PassCodeProtectionDialogContent; diff --git a/packages/kit/src/components/Password/components/PasswordSetup.tsx b/packages/kit/src/components/Password/components/PasswordSetup.tsx index d82708af5de..0276208ba4f 100644 --- a/packages/kit/src/components/Password/components/PasswordSetup.tsx +++ b/packages/kit/src/components/Password/components/PasswordSetup.tsx @@ -228,7 +228,6 @@ const PasswordSetup = ({ }} > { form.setValue('passCode', pin); form.clearErrors('passCode'); @@ -266,7 +265,6 @@ const PasswordSetup = ({ }} > { form.setValue('confirmPassCode', pin); form.clearErrors('confirmPassCode'); diff --git a/packages/kit/src/components/Password/components/PasswordVerify.tsx b/packages/kit/src/components/Password/components/PasswordVerify.tsx index 65fcb93fa60..b813b7ee1dc 100644 --- a/packages/kit/src/components/Password/components/PasswordVerify.tsx +++ b/packages/kit/src/components/Password/components/PasswordVerify.tsx @@ -12,7 +12,14 @@ import { AuthenticationType } from 'expo-local-authentication'; import { useIntl } from 'react-intl'; import type { IKeyOfIcons, IPropsWithTestId } from '@onekeyhq/components'; -import { Button, Form, Input, useForm } from '@onekeyhq/components'; +import { + Form, + IconButton, + Input, + SizableText, + XStack, + useForm, +} from '@onekeyhq/components'; import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { usePasswordAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { ETranslations } from '@onekeyhq/shared/src/locale'; @@ -35,6 +42,8 @@ interface IPasswordVerifyProps { value: EPasswordVerifyStatus; message?: string; }; + alertText?: string; + confirmBtnDisabled?: boolean; } export interface IPasswordVerifyForm { @@ -45,6 +54,8 @@ export interface IPasswordVerifyForm { const PasswordVerify = ({ authType, isEnable, + alertText, + confirmBtnDisabled, status, passwordMode, onBiologyAuth, @@ -103,6 +114,7 @@ const PasswordVerify = ({ iconName?: IKeyOfIcons; onPress?: () => void; loading?: boolean; + disabled?: boolean; }>[] = []; if (isEnable && !passwordInput) { actions.push({ @@ -121,6 +133,7 @@ const PasswordVerify = ({ iconName: 'ArrowRightOutline', onPress: form.handleSubmit(onInputPasswordAuth), loading: status.value === EPasswordVerifyStatus.VERIFYING, + disabled: confirmBtnDisabled, testID: 'verifying-password', }); } @@ -135,6 +148,7 @@ const PasswordVerify = ({ secureEntry, form, onInputPasswordAuth, + confirmBtnDisabled, ]); useEffect(() => { @@ -181,38 +195,47 @@ const PasswordVerify = ({ return (
{passwordMode === EPasswordMode.PASSWORD ? ( - - text.replace(PasswordRegex, '')} - onChangeText={(text) => text} - keyboardType={getPasswordKeyboardType(!secureEntry)} - secureTextEntry={secureEntry} - // fix Keyboard Flickering on TextInput with secureTextEntry #39411 - // https://github.com/facebook/react-native/issues/39411 - textContentType="oneTimeCode" - onSubmitEditing={form.handleSubmit(onInputPasswordAuth)} - addOns={rightActions} - testID="password-input" - /> - + <> + + text.replace(PasswordRegex, '')} + onChangeText={(text) => text} + keyboardType={getPasswordKeyboardType(!secureEntry)} + secureTextEntry={secureEntry} + // fix Keyboard Flickering on TextInput with secureTextEntry #39411 + // https://github.com/facebook/react-native/issues/39411 + textContentType="oneTimeCode" + onSubmitEditing={form.handleSubmit(onInputPasswordAuth)} + addOns={rightActions} + testID="password-input" + /> + + {alertText ? ( + + + {alertText} + + + ) : null} + ) : ( <> { form.setValue('passCode', pin); form.clearErrors('passCode'); }} + onComplete={form.handleSubmit(onInputPasswordAuth)} + disabledComplete={confirmBtnDisabled} testId="pass-code-input" /> {isEnable && !passwordInput ? ( - + /> ) : ( - + <> + {alertText ? ( + + + {alertText} + + + ) : null} + )} )} diff --git a/packages/kit/src/components/Password/container/PassCodeProtectionSwitch.tsx b/packages/kit/src/components/Password/container/PassCodeProtectionSwitch.tsx new file mode 100644 index 00000000000..a5f87af78d1 --- /dev/null +++ b/packages/kit/src/components/Password/container/PassCodeProtectionSwitch.tsx @@ -0,0 +1,20 @@ +import { Switch } from '@onekeyhq/components'; +import { usePasswordPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; + +const PassCodeProtectionSwitch = () => { + const [{ enablePasswordErrorProtection }, setPasswordPersist] = + usePasswordPersistAtom(); + return ( + { + setPasswordPersist((v) => ({ + ...v, + enablePasswordErrorProtection: value, + })); + }} + /> + ); +}; + +export default PassCodeProtectionSwitch; diff --git a/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx b/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx index 0b5d866731a..a05b87f4563 100644 --- a/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordSetupContainer.tsx @@ -2,14 +2,20 @@ import { Suspense, memo, useCallback, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; -import { SizableText, Stack, Toast, XStack } from '@onekeyhq/components'; +import { + Dialog, + Icon, + SizableText, + Stack, + Toast, + XStack, +} from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { useSettingsPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { usePasswordBiologyAuthInfoAtom, usePasswordModeAtom, - usePasswordPersistAtom, usePasswordWebAuthInfoAtom, } from '@onekeyhq/kit-bg/src/states/jotai/atoms/password'; import { ETranslations } from '@onekeyhq/shared/src/locale'; @@ -18,6 +24,7 @@ import platformEnv from '@onekeyhq/shared/src/platformEnv'; import { useBiometricAuthInfo } from '../../../hooks/useBiometricAuthInfo'; import { UniversalContainerWithSuspense } from '../../BiologyAuthComponent/container/UniversalContainer'; import { useWebAuthActions } from '../../BiologyAuthComponent/hooks/useWebAuthActions'; +import PassCodeProtectionDialogContent from '../components/PassCodeProtectionDialogContent'; import PasswordSetup from '../components/PasswordSetup'; import type { IPasswordSetupForm } from '../components/PasswordSetup'; @@ -89,16 +96,40 @@ const PasswordSetupContainer = ({ onSetupRes }: IPasswordSetupProps) => { await backgroundApiProxy.servicePassword.encodeSensitiveText({ text: finalPassword, }); - const setUpPasswordRes = await backgroundApiProxy.servicePassword.setPassword( encodePassword, mode, ); - onSetupRes(setUpPasswordRes); Toast.success({ title: intl.formatMessage({ id: ETranslations.auth_password_set }), }); + onSetupRes(setUpPasswordRes); + Dialog.show({ + title: 'PassCode Protection', + renderIcon: ( + + + + ), + renderContent: , + onConfirmText: intl.formatMessage({ + id: ETranslations.global_ok, + }), + showCancelButton: false, + }); } catch (e) { console.log('e.stack', (e as Error)?.stack); console.error(e); diff --git a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx index 2d1b633086c..423dbdc5932 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AuthenticationType } from 'expo-local-authentication'; import { useIntl } from 'react-intl'; @@ -6,7 +6,14 @@ import { useIntl } from 'react-intl'; import { Stack } from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; import { biologyAuthUtils } from '@onekeyhq/kit-bg/src/services/ServicePassword/biologyAuthUtils'; -import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; +import { + BIOLOGY_AUTH_ATTEMPTS_FACE, + BIOLOGY_AUTH_ATTEMPTS_FINGERPRINT, + EPasswordMode, + PASSCODE_PROTECTION_ATTEMPTS, + PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX, + PASSCODE_PROTECTION_ATTEMPTS_PER_MINUTE_MAP, +} from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { useSettingsPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import { usePasswordAtom, @@ -44,9 +51,34 @@ const PasswordVerifyContainer = ({ const [{ isBiologyAuthSwitchOn }] = useSettingsPersistAtom(); const [hasCachedPassword, setHasCachedPassword] = useState(false); const [hasSecurePassword, setHasSecurePassword] = useState(false); + const [unlockPeriodPasswordArray, setUnlockPeriodPasswordArray] = useState< + string[] + >([]); + const [passwordErrorProtectionTimeOver, setPasswordErrorProtectionTimeOver] = + useState(false); + const [verifyPeriodBiologyAuthAttempts, setVerifyPeriodBiologyAuthAttempts] = + useState(0); + const [verifyPeriodBiologyEnable, setVerifyPeriodBiologyEnable] = + useState(true); const [passwordMode] = usePasswordModeAtom(); + const [ + { + passwordErrorAttempts, + enablePasswordErrorProtection, + passwordErrorProtectionTime, + }, + setPasswordPersist, + ] = usePasswordPersistAtom(); + const biologyAuthAttempts = useMemo( + () => + authType.includes(AuthenticationType.FACIAL_RECOGNITION) + ? BIOLOGY_AUTH_ATTEMPTS_FACE + : BIOLOGY_AUTH_ATTEMPTS_FINGERPRINT, + [authType], + ); + const isLock = useMemo(() => name === 'lock', [name]); const isExtLockAndNoCachePassword = Boolean( - platformEnv.isExtension && name === 'lock' && !hasCachedPassword, + platformEnv.isExtension && isLock && !hasCachedPassword, ); useEffect(() => { @@ -76,10 +108,15 @@ const PasswordVerifyContainer = ({ // both webAuth or biologyAuth are enabled () => { if (isExtLockAndNoCachePassword) { - return isBiologyAuthSwitchOn && !!webAuthCredentialId; + return ( + isBiologyAuthSwitchOn && + !!webAuthCredentialId && + verifyPeriodBiologyEnable + ); } return ( isBiologyAuthSwitchOn && + verifyPeriodBiologyEnable && ((isEnable && hasSecurePassword) || (!!webAuthCredentialId && !!hasCachedPassword)) ); @@ -91,6 +128,7 @@ const PasswordVerifyContainer = ({ webAuthCredentialId, isBiologyAuthSwitchOn, isExtLockAndNoCachePassword, + verifyPeriodBiologyEnable, ], ); const [{ passwordVerifyStatus }, setPasswordAtom] = usePasswordAtom(); @@ -128,17 +166,14 @@ const PasswordVerifyContainer = ({ })); onVerifyRes(''); } else { - setPasswordAtom((v) => ({ - ...v, - passwordVerifyStatus: { - value: EPasswordVerifyStatus.ERROR, - message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, - }), - }, - })); + throw new Error('biology auth verify error'); } } catch { + if (verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts) { + setVerifyPeriodBiologyEnable(false); + } else { + setVerifyPeriodBiologyAuthAttempts((v) => v + 1); + } setPasswordAtom((v) => ({ ...v, passwordVerifyStatus: { @@ -150,11 +185,13 @@ const PasswordVerifyContainer = ({ })); } }, [ + passwordVerifyStatus.value, + setPasswordAtom, checkWebAuth, - passwordVerifyStatus, onVerifyRes, intl, - setPasswordAtom, + verifyPeriodBiologyAuthAttempts, + biologyAuthAttempts, ]); const onBiologyAuthenticate = useCallback(async () => { @@ -178,6 +215,7 @@ const PasswordVerifyContainer = ({ await backgroundApiProxy.servicePassword.verifyPassword({ password: '', isBiologyAuth: true, + passwordMode, }); } if (biologyAuthRes) { @@ -187,18 +225,14 @@ const PasswordVerifyContainer = ({ })); onVerifyRes(biologyAuthRes); } else { - setPasswordAtom((v) => ({ - ...v, - passwordVerifyStatus: { - value: EPasswordVerifyStatus.ERROR, - message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, - }), - }, - })); throw new Error('biology auth verify error'); } } catch (e) { + if (verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts) { + setVerifyPeriodBiologyEnable(false); + } else { + setVerifyPeriodBiologyAuthAttempts((v) => v + 1); + } setPasswordAtom((v) => ({ ...v, passwordVerifyStatus: { @@ -210,13 +244,16 @@ const PasswordVerifyContainer = ({ })); } }, [ + biologyAuthAttempts, intl, isBiologyAuthEnable, isEnable, onVerifyRes, + passwordMode, passwordVerifyStatus.value, setPasswordAtom, verifiedPasswordWebAuth, + verifyPeriodBiologyAuthAttempts, ]); const onInputPasswordAuthenticate = useCallback( @@ -241,6 +278,7 @@ const PasswordVerifyContainer = ({ const verifiedPassword = await backgroundApiProxy.servicePassword.verifyPassword({ password: encodePassword, + passwordMode, }); setPasswordAtom((v) => ({ ...v, @@ -252,30 +290,101 @@ const PasswordVerifyContainer = ({ } onVerifyRes(verifiedPassword); } catch (e) { + let message = intl.formatMessage({ + id: ETranslations.auth_error_password_incorrect, + }); + if (isLock && enablePasswordErrorProtection) { + let nextAttempts = passwordErrorAttempts + 1; + if (!unlockPeriodPasswordArray.includes(finalPassword)) { + setPasswordPersist((v) => ({ + ...v, + passwordErrorAttempts: nextAttempts, + })); + setUnlockPeriodPasswordArray((v) => [...v, finalPassword]); + } else { + nextAttempts = passwordErrorAttempts; + } + if (nextAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX) { + const timeMinutes = + PASSCODE_PROTECTION_ATTEMPTS_PER_MINUTE_MAP[ + nextAttempts.toString() + ]; + message = `${ + PASSCODE_PROTECTION_ATTEMPTS - nextAttempts + } more failed attempts will reset the device`; + setPasswordPersist((v) => ({ + ...v, + passwordErrorProtectionTime: Date.now() + timeMinutes * 60 * 1000, + })); + } + } setPasswordAtom((v) => ({ ...v, passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, - message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, - }), + message, }, })); } }, [ + enablePasswordErrorProtection, intl, + isLock, onVerifyRes, + passwordErrorAttempts, passwordMode, passwordVerifyStatus.value, setPasswordAtom, + setPasswordPersist, + unlockPeriodPasswordArray, ], ); + const alertText = useMemo(() => { + if ( + isLock && + enablePasswordErrorProtection && + passwordErrorAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX && + passwordErrorProtectionTime > Date.now() && + !passwordErrorProtectionTimeOver + ) { + return `Try again in ${Math.floor( + (passwordErrorProtectionTime - Date.now()) / 60_000, + )} minutes`; + } + return ''; + }, [ + isLock, + enablePasswordErrorProtection, + passwordErrorAttempts, + passwordErrorProtectionTime, + passwordErrorProtectionTimeOver, + ]); + const intervalRef = useRef>(); + useEffect(() => { + if (alertText) { + intervalRef.current = setInterval(() => { + if (passwordErrorProtectionTime < Date.now()) { + setPasswordErrorProtectionTimeOver(true); + } + }, 1000 * 60); + } else if (intervalRef.current) { + clearInterval(intervalRef.current); + } + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [alertText, passwordErrorProtectionTime]); + return ( { setPasswordAtom((v) => ({ ...v, diff --git a/packages/kit/src/provider/Container/AppStateLockContainer/index.tsx b/packages/kit/src/provider/Container/AppStateLockContainer/index.tsx index 049533a9bdc..4efe66fa15f 100644 --- a/packages/kit/src/provider/Container/AppStateLockContainer/index.tsx +++ b/packages/kit/src/provider/Container/AppStateLockContainer/index.tsx @@ -1,11 +1,10 @@ import type { PropsWithChildren } from 'react'; import { Suspense, useCallback, useEffect, useRef, useState } from 'react'; -import { AnimatePresence } from '@onekeyhq/components'; +import { AnimatePresence, Spinner } from '@onekeyhq/components'; import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/backgroundApiProxy'; import { useAppIsLockedAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; -import extUtils, { EXT_HTML_FILES } from '@onekeyhq/shared/src/utils/extUtils'; import PasswordVerifyContainer from '../../../components/Password/container/PasswordVerifyContainer'; @@ -86,7 +85,7 @@ export function AppStateLockContainer({ opacity: 0, }} passwordVerifyContainer={ - + }> { id: ETranslations.settings_password_bypass_desc, })} + + + + + + + Reset app on this device after 10 failed passcode attempts. + From 1ffa60b3c41726fdb246ae37f7163cca6d7daa81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Thu, 12 Dec 2024 11:37:25 +0800 Subject: [PATCH 04/13] fix: code logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- .../OneKeyWallet.xcodeproj/project.pbxproj | 4 + apps/mobile/ios/Podfile.lock | 24 ++-- .../src/services/ServicePassword/index.ts | 8 +- .../Password/components/PasswordSetup.tsx | 7 +- .../container/PasswordVerifyContainer.tsx | 118 +++++++----------- .../Password/hooks/usePasswordProtection.ts | 91 ++++++++++++++ 6 files changed, 164 insertions(+), 88 deletions(-) create mode 100644 packages/kit/src/components/Password/hooks/usePasswordProtection.ts diff --git a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj index de985da7c6f..06259d217e1 100644 --- a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj @@ -419,6 +419,8 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/JXCategoryView/JXCategoryView.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagerView.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", @@ -430,6 +432,8 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JXCategoryView.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JXPagerView.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 72b576ef975..99b35e37ea0 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -111,6 +111,9 @@ PODS: - MMKVCore (~> 1.3.1) - MMKVCore (1.3.1) - MultiplatformBleAdapter (0.1.9) + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) - RCT-Folly (2022.05.16.00): - boost - DoubleConversion @@ -1016,21 +1019,18 @@ PODS: - React-Core - react-native-slider (4.4.3): - React-Core - - react-native-tab-page-view (1.0.18): + - react-native-tab-page-view (1.0.17): - JXCategoryView (~> 1.6.1) - JXPagingView/Pager (~> 2.1.2) - React - react-native-tcp-socket (6.0.6): - CocoaAsyncSocket - React-Core - - react-native-video (6.2.0): - - glog - - RCT-Folly (= 2022.05.16.00) + - react-native-video (6.0.0-beta.6): - React-Core - - react-native-video/Video (= 6.2.0) - - react-native-video/Video (6.2.0): - - glog - - RCT-Folly (= 2022.05.16.00) + - react-native-video/Video (= 6.0.0-beta.6) + - react-native-video/Video (6.0.0-beta.6): + - PromisesSwift - React-Core - react-native-view-shot (3.1.2): - React @@ -1437,6 +1437,8 @@ SPEC REPOS: - MMKV - MMKVCore - MultiplatformBleAdapter + - PromisesObjC + - PromisesSwift - ScreenshotPreventing - SDWebImage - SDWebImageWebPCoder @@ -1728,6 +1730,8 @@ SPEC CHECKSUMS: MMKV: 5a07930c70c70b86cd87761a42c8f3836fb681d7 MMKVCore: e50135dbd33235b6ab390635991bab437ab873c0 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: 77f73950d15b8c1a2b48ba5b79020c3003d1c9b5 RCTTypeSafety: ede1e2576424d89471ef553b2aed09fbbcc038e3 @@ -1762,9 +1766,9 @@ SPEC CHECKSUMS: react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284 react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4 - react-native-tab-page-view: 85c179e539d8a096f13a057ffe97aa3995479dc2 + react-native-tab-page-view: c1f9217177b7043a7e3a9bc779d49d9f2c9eb0d7 react-native-tcp-socket: e724380c910c2e704816ec817ed28f1342246ff7 - react-native-video: 12a68a3e9a6d93e8bdfa8e352917261c49ac35f7 + react-native-video: dc3118548cf8864a83f57df4345cf6c692402e8f react-native-view-shot: 4475fde003fe8a210053d1f98fb9e06c1d834e1c react-native-webview: ab8587e1e43e274471ec8dc9342ec1711fa8bf61 react-native-webview-cleaner: 93c8c73a302c90fcd83a227438b2a53bdb47f7cc diff --git a/packages/kit-bg/src/services/ServicePassword/index.ts b/packages/kit-bg/src/services/ServicePassword/index.ts index 4c27814a8c7..954b630afbc 100644 --- a/packages/kit-bg/src/services/ServicePassword/index.ts +++ b/packages/kit-bg/src/services/ServicePassword/index.ts @@ -304,7 +304,8 @@ export default class ServicePassword extends ServiceBase { validatePasswordSame(password: string, newPassword: string) { ensureSensitiveTextEncoded(password); ensureSensitiveTextEncoded(newPassword); - + console.log('same__password', password); + console.log('same__newPassword', newPassword); const realPassword = decodePassword({ password }); const realNewPassword = decodePassword({ password: newPassword }); if (realPassword === realNewPassword) { @@ -327,8 +328,9 @@ export default class ServicePassword extends ServiceBase { if (newPassword) { ensureSensitiveTextEncoded(newPassword); } - this.validatePasswordValidRules(password, passwordMode); - if (newPassword) { + if (!newPassword) { + this.validatePasswordValidRules(password, passwordMode); + } else { this.validatePasswordValidRules(newPassword, passwordMode); this.validatePasswordSame(password, newPassword); } diff --git a/packages/kit/src/components/Password/components/PasswordSetup.tsx b/packages/kit/src/components/Password/components/PasswordSetup.tsx index 0276208ba4f..fdf1f9acd9a 100644 --- a/packages/kit/src/components/Password/components/PasswordSetup.tsx +++ b/packages/kit/src/components/Password/components/PasswordSetup.tsx @@ -300,11 +300,12 @@ const PasswordSetup = ({ variant="secondary" onPress={() => { form.reset(); - setCurrentPasswordMode( + const newPasswordMode = currentPasswordMode === EPasswordMode.PASSWORD ? EPasswordMode.PASSCODE - : EPasswordMode.PASSWORD, - ); + : EPasswordMode.PASSWORD; + setCurrentPasswordMode(newPasswordMode); + form.setValue('passwordMode', newPasswordMode); }} > {`切换成${ diff --git a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx index 423dbdc5932..da212850304 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { AuthenticationType } from 'expo-local-authentication'; import { useIntl } from 'react-intl'; @@ -29,6 +29,7 @@ import { EPasswordVerifyStatus } from '@onekeyhq/shared/types/password'; import { useWebAuthActions } from '../../BiologyAuthComponent/hooks/useWebAuthActions'; import PasswordVerify from '../components/PasswordVerify'; +import usePasswordProtection from '../hooks/usePasswordProtection'; import type { IPasswordVerifyForm } from '../components/PasswordVerify'; import type { LayoutChangeEvent } from 'react-native'; @@ -51,24 +52,7 @@ const PasswordVerifyContainer = ({ const [{ isBiologyAuthSwitchOn }] = useSettingsPersistAtom(); const [hasCachedPassword, setHasCachedPassword] = useState(false); const [hasSecurePassword, setHasSecurePassword] = useState(false); - const [unlockPeriodPasswordArray, setUnlockPeriodPasswordArray] = useState< - string[] - >([]); - const [passwordErrorProtectionTimeOver, setPasswordErrorProtectionTimeOver] = - useState(false); - const [verifyPeriodBiologyAuthAttempts, setVerifyPeriodBiologyAuthAttempts] = - useState(0); - const [verifyPeriodBiologyEnable, setVerifyPeriodBiologyEnable] = - useState(true); const [passwordMode] = usePasswordModeAtom(); - const [ - { - passwordErrorAttempts, - enablePasswordErrorProtection, - passwordErrorProtectionTime, - }, - setPasswordPersist, - ] = usePasswordPersistAtom(); const biologyAuthAttempts = useMemo( () => authType.includes(AuthenticationType.FACIAL_RECOGNITION) @@ -80,7 +64,10 @@ const PasswordVerifyContainer = ({ const isExtLockAndNoCachePassword = Boolean( platformEnv.isExtension && isLock && !hasCachedPassword, ); - + const [{ passwordVerifyStatus }, setPasswordAtom] = usePasswordAtom(); + const resetPasswordStatus = useCallback(() => { + void backgroundApiProxy.servicePassword.resetPasswordStatus(); + }, []); useEffect(() => { if (webAuthCredentialId && isBiologyAuthSwitchOn) { void (async () => { @@ -104,6 +91,30 @@ const PasswordVerifyContainer = ({ } }, [isEnable, isBiologyAuthSwitchOn]); + useEffect(() => { + setPasswordAtom((v) => ({ + ...v, + passwordVerifyStatus: { value: EPasswordVerifyStatus.DEFAULT }, + })); + return () => { + resetPasswordStatus(); + }; + }, [setPasswordAtom, resetPasswordStatus]); + + const { + verifyPeriodBiologyEnable, + verifyPeriodBiologyAuthAttempts, + unlockPeriodPasswordArray, + passwordErrorAttempts, + setVerifyPeriodBiologyEnable, + setVerifyPeriodBiologyAuthAttempts, + setPasswordErrorProtectionTimeMinutesSurplus, + setUnlockPeriodPasswordArray, + alertText, + setPasswordPersist, + enablePasswordErrorProtection, + } = usePasswordProtection(isLock); + const isBiologyAuthEnable = useMemo( // both webAuth or biologyAuth are enabled () => { @@ -131,19 +142,6 @@ const PasswordVerifyContainer = ({ verifyPeriodBiologyEnable, ], ); - const [{ passwordVerifyStatus }, setPasswordAtom] = usePasswordAtom(); - const resetPasswordStatus = useCallback(() => { - void backgroundApiProxy.servicePassword.resetPasswordStatus(); - }, []); - useEffect(() => { - setPasswordAtom((v) => ({ - ...v, - passwordVerifyStatus: { value: EPasswordVerifyStatus.DEFAULT }, - })); - return () => { - resetPasswordStatus(); - }; - }, [setPasswordAtom, resetPasswordStatus]); const onBiologyAuthenticateExtLockAndNoCachePassword = useCallback(async () => { @@ -189,9 +187,11 @@ const PasswordVerifyContainer = ({ setPasswordAtom, checkWebAuth, onVerifyRes, - intl, verifyPeriodBiologyAuthAttempts, biologyAuthAttempts, + setVerifyPeriodBiologyEnable, + setVerifyPeriodBiologyAuthAttempts, + intl, ]); const onBiologyAuthenticate = useCallback(async () => { @@ -252,6 +252,8 @@ const PasswordVerifyContainer = ({ passwordMode, passwordVerifyStatus.value, setPasswordAtom, + setVerifyPeriodBiologyAuthAttempts, + setVerifyPeriodBiologyEnable, verifiedPasswordWebAuth, verifyPeriodBiologyAuthAttempts, ]); @@ -289,6 +291,13 @@ const PasswordVerifyContainer = ({ await timerUtils.wait(0); } onVerifyRes(verifiedPassword); + if (isLock && enablePasswordErrorProtection) { + setPasswordPersist((v) => ({ + ...v, + passwordErrorAttempts: 0, + passwordErrorProtectionTime: 0, + })); + } } catch (e) { let message = intl.formatMessage({ id: ETranslations.auth_error_password_incorrect, @@ -314,8 +323,9 @@ const PasswordVerifyContainer = ({ } more failed attempts will reset the device`; setPasswordPersist((v) => ({ ...v, - passwordErrorProtectionTime: Date.now() + timeMinutes * 60 * 1000, + passwordErrorProtectionTime: Date.now() + timeMinutes * 60 * 1000, // 2s for animation })); + setPasswordErrorProtectionTimeMinutesSurplus(timeMinutes); } } setPasswordAtom((v) => ({ @@ -336,49 +346,13 @@ const PasswordVerifyContainer = ({ passwordMode, passwordVerifyStatus.value, setPasswordAtom, + setPasswordErrorProtectionTimeMinutesSurplus, setPasswordPersist, + setUnlockPeriodPasswordArray, unlockPeriodPasswordArray, ], ); - const alertText = useMemo(() => { - if ( - isLock && - enablePasswordErrorProtection && - passwordErrorAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX && - passwordErrorProtectionTime > Date.now() && - !passwordErrorProtectionTimeOver - ) { - return `Try again in ${Math.floor( - (passwordErrorProtectionTime - Date.now()) / 60_000, - )} minutes`; - } - return ''; - }, [ - isLock, - enablePasswordErrorProtection, - passwordErrorAttempts, - passwordErrorProtectionTime, - passwordErrorProtectionTimeOver, - ]); - const intervalRef = useRef>(); - useEffect(() => { - if (alertText) { - intervalRef.current = setInterval(() => { - if (passwordErrorProtectionTime < Date.now()) { - setPasswordErrorProtectionTimeOver(true); - } - }, 1000 * 60); - } else if (intervalRef.current) { - clearInterval(intervalRef.current); - } - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - } - }; - }, [alertText, passwordErrorProtectionTime]); - return ( { + const [unlockPeriodPasswordArray, setUnlockPeriodPasswordArray] = useState< + string[] + >([]); + const [passwordErrorProtectionTimeOver, setPasswordErrorProtectionTimeOver] = + useState(false); + const [ + passwordErrorProtectionTimeMinutesSurplus, + setPasswordErrorProtectionTimeMinutesSurplus, + ] = useState(0); + const [verifyPeriodBiologyAuthAttempts, setVerifyPeriodBiologyAuthAttempts] = + useState(0); + const [verifyPeriodBiologyEnable, setVerifyPeriodBiologyEnable] = + useState(true); + const [ + { + passwordErrorAttempts, + enablePasswordErrorProtection, + passwordErrorProtectionTime, + }, + setPasswordPersist, + ] = usePasswordPersistAtom(); + const alertText = useMemo(() => { + if ( + isLock && + enablePasswordErrorProtection && + passwordErrorAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX && + passwordErrorProtectionTime > Date.now() && + passwordErrorProtectionTimeMinutesSurplus > 0 && + !passwordErrorProtectionTimeOver + ) { + return `Try again in ${Math.floor( + passwordErrorProtectionTimeMinutesSurplus, + )} minutes`; + } + return ''; + }, [ + isLock, + enablePasswordErrorProtection, + passwordErrorAttempts, + passwordErrorProtectionTime, + passwordErrorProtectionTimeOver, + passwordErrorProtectionTimeMinutesSurplus, + ]); + const intervalRef = useRef>(); + useEffect(() => { + if (alertText) { + intervalRef.current = setInterval(() => { + if (passwordErrorProtectionTime < Date.now()) { + setPasswordErrorProtectionTimeOver(true); + setPasswordErrorProtectionTimeMinutesSurplus(0); + } else { + const timeMinutes = + (passwordErrorProtectionTime - Date.now()) / 60_000 + 1; + setPasswordErrorProtectionTimeMinutesSurplus(timeMinutes); + } + }, 1000 * 50); + } else if (intervalRef.current) { + clearInterval(intervalRef.current); + } + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [alertText, passwordErrorProtectionTime]); + + return { + unlockPeriodPasswordArray, + passwordErrorProtectionTimeOver, + passwordErrorProtectionTimeMinutesSurplus, + verifyPeriodBiologyAuthAttempts, + verifyPeriodBiologyEnable, + passwordErrorAttempts, + alertText, + setPasswordPersist, + setVerifyPeriodBiologyEnable, + setVerifyPeriodBiologyAuthAttempts, + setUnlockPeriodPasswordArray, + setPasswordErrorProtectionTimeOver, + setPasswordErrorProtectionTimeMinutesSurplus, + enablePasswordErrorProtection, + }; +}; + +export default usePasswordProtection; From bb4359fcaee70519d55e38b0c6e5fa773cc1298a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Thu, 12 Dec 2024 11:42:33 +0800 Subject: [PATCH 05/13] fix: lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- .../OneKeyWallet.xcodeproj/project.pbxproj | 6 ++--- apps/mobile/ios/Podfile.lock | 22 +++++++++---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj index 06259d217e1..3a6b56ebbcf 100644 --- a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj @@ -419,26 +419,24 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/JXCategoryView/JXCategoryView.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagerView.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JXCategoryView.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/JXPagerView.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 99b35e37ea0..9d3866929f1 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -111,9 +111,6 @@ PODS: - MMKVCore (~> 1.3.1) - MMKVCore (1.3.1) - MultiplatformBleAdapter (0.1.9) - - PromisesObjC (2.4.0) - - PromisesSwift (2.4.0): - - PromisesObjC (= 2.4.0) - RCT-Folly (2022.05.16.00): - boost - DoubleConversion @@ -1019,18 +1016,21 @@ PODS: - React-Core - react-native-slider (4.4.3): - React-Core - - react-native-tab-page-view (1.0.17): + - react-native-tab-page-view (1.0.18): - JXCategoryView (~> 1.6.1) - JXPagingView/Pager (~> 2.1.2) - React - react-native-tcp-socket (6.0.6): - CocoaAsyncSocket - React-Core - - react-native-video (6.0.0-beta.6): + - react-native-video (6.2.0): + - glog + - RCT-Folly (= 2022.05.16.00) - React-Core - - react-native-video/Video (= 6.0.0-beta.6) - - react-native-video/Video (6.0.0-beta.6): - - PromisesSwift + - react-native-video/Video (= 6.2.0) + - react-native-video/Video (6.2.0): + - glog + - RCT-Folly (= 2022.05.16.00) - React-Core - react-native-view-shot (3.1.2): - React @@ -1437,8 +1437,6 @@ SPEC REPOS: - MMKV - MMKVCore - MultiplatformBleAdapter - - PromisesObjC - - PromisesSwift - ScreenshotPreventing - SDWebImage - SDWebImageWebPCoder @@ -1688,7 +1686,7 @@ SPEC CHECKSUMS: Burnt: dde5dd245f124a4594098e3938ba71aae4ec83c3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 CocoaLumberjack: 5c7e64cdb877770859bddec4d3d5a0d7c9299df9 - DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 + DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 EXApplication: 137189a3f149b4e8e546884629392c3efc94cbd3 EXBarCodeScanner: d59fd943cebee3f913ebf4ffde0d05d344da8b78 EXConstants: 988aa430ca0f76b43cd46b66e7fae3287f9cc2fc @@ -1715,7 +1713,7 @@ SPEC CHECKSUMS: FBLazyVector: 9f533d5a4c75ca77c8ed774aced1a91a0701781e FBReactNativeSpec: 2db5940ee4b58968274eec0a4f1c736fc4caefa3 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 + glog: 69ef571f3de08433d766d614c73a9838a06bf7eb hermes-engine: 39589e9c297d024e90fe68f6830ff86c4e01498a ImageColors: 88be684570585c07ae2750bff34eb7b78bfc53b4 IQKeyboardManagerSwift: c7955c0bdbf7b2eb29bb7daaa44e3d90f55a9a85 From 0bc102584d8ed33f82645a7434d41e59c1edf980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Thu, 12 Dec 2024 11:43:31 +0800 Subject: [PATCH 06/13] fix: ios pod revocer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- apps/mobile/ios/Podfile.lock | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 9d3866929f1..99639bcfa00 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -1728,8 +1728,6 @@ SPEC CHECKSUMS: MMKV: 5a07930c70c70b86cd87761a42c8f3836fb681d7 MMKVCore: e50135dbd33235b6ab390635991bab437ab873c0 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: 77f73950d15b8c1a2b48ba5b79020c3003d1c9b5 RCTTypeSafety: ede1e2576424d89471ef553b2aed09fbbcc038e3 @@ -1764,9 +1762,9 @@ SPEC CHECKSUMS: react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284 react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4 - react-native-tab-page-view: c1f9217177b7043a7e3a9bc779d49d9f2c9eb0d7 + react-native-tab-page-view: 85c179e539d8a096f13a057ffe97aa3995479dc2 react-native-tcp-socket: e724380c910c2e704816ec817ed28f1342246ff7 - react-native-video: dc3118548cf8864a83f57df4345cf6c692402e8f + react-native-video: 12a68a3e9a6d93e8bdfa8e352917261c49ac35f7 react-native-view-shot: 4475fde003fe8a210053d1f98fb9e06c1d834e1c react-native-webview: ab8587e1e43e274471ec8dc9342ec1711fa8bf61 react-native-webview-cleaner: 93c8c73a302c90fcd83a227438b2a53bdb47f7cc @@ -1819,7 +1817,7 @@ SPEC CHECKSUMS: SPIndicator: 93e0a4fb23de51294ac48e874c0f081a5e293e4f SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 - Yoga: 07db09965bc46c4902e20d3ae6990d95e53af8a8 + Yoga: 6d01ccde54c9f8b92492beb05d468dbfed1d9881 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 PODFILE CHECKSUM: 5b7f20a90e19262f325cab10e37056b7f3cd0ffb From afa8d8dea07a3be9e32e12f36feea163fc1f6271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Thu, 19 Dec 2024 11:36:04 +0800 Subject: [PATCH 07/13] fix:i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- apps/ext/src/ui/renderPassKeyPage.tsx | 4 +- .../PassCodeProtectionDialogContent.tsx | 35 +++++++++------ .../Password/components/PasswordSetup.tsx | 38 ++++++++-------- .../Password/components/PasswordVerify.tsx | 8 ++-- .../container/PasswordSetupContainer.tsx | 8 ++-- .../container/PasswordUpdateContainer.tsx | 2 +- .../container/PasswordVerifyContainer.tsx | 45 ++++++++++++++++--- .../container/PasswordVerifyPromptMount.tsx | 4 +- .../Password/hooks/usePasswordProtection.ts | 18 ++++++-- .../components/AppStateLock.tsx | 2 +- .../components/useResotrePasswordVerify.tsx | 6 +-- .../views/CloudBackup/pages/Detail/index.tsx | 2 +- .../CreateWalet/BeforeShowRecoveryPhrase.tsx | 2 +- .../pages/List/SecuritySection/index.tsx | 6 +-- .../views/Setting/pages/Protection/index.tsx | 14 ++++-- .../shared/src/errors/errors/appErrors.ts | 12 ++--- 16 files changed, 133 insertions(+), 73 deletions(-) diff --git a/apps/ext/src/ui/renderPassKeyPage.tsx b/apps/ext/src/ui/renderPassKeyPage.tsx index b343c8e8f71..390e3286a47 100644 --- a/apps/ext/src/ui/renderPassKeyPage.tsx +++ b/apps/ext/src/ui/renderPassKeyPage.tsx @@ -79,7 +79,7 @@ const usePassKeyOperations = () => { passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, + id: ETranslations.auth_error_passcode_incorrect, }), }, })); @@ -90,7 +90,7 @@ const usePassKeyOperations = () => { passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, + id: ETranslations.auth_error_passcode_incorrect, }), }, })); diff --git a/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx b/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx index 7c03e8cf4ed..c70e344cf37 100644 --- a/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx +++ b/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx @@ -1,20 +1,29 @@ +import { useIntl } from 'react-intl'; + import { SizableText, XStack, YStack } from '@onekeyhq/components'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; import PassCodeProtectionSwitch from '../container/PassCodeProtectionSwitch'; -const PassCodeProtectionDialogContent = () => ( - - - 10 failed passcode attempts will reset app, you can change it anytime on - setting - protection. - - - - Reset App after 10 failed attempts +const PassCodeProtectionDialogContent = () => { + const intl = useIntl(); + return ( + + + {intl.formatMessage({ + id: ETranslations.auth_Passcode_protection_description, + })} - - - -); + + + {intl.formatMessage({ + id: ETranslations.Setting_Reset_app_description, + })} + + + + + ); +}; export default PassCodeProtectionDialogContent; diff --git a/packages/kit/src/components/Password/components/PasswordSetup.tsx b/packages/kit/src/components/Password/components/PasswordSetup.tsx index fdf1f9acd9a..77a9e402090 100644 --- a/packages/kit/src/components/Password/components/PasswordSetup.tsx +++ b/packages/kit/src/components/Password/components/PasswordSetup.tsx @@ -66,7 +66,7 @@ const PasswordSetup = ({ } return ( confirmBtnText ?? - intl.formatMessage({ id: ETranslations.auth_set_password }) + intl.formatMessage({ id: ETranslations.auth_set_passcode }) ); }, [confirmBtnText, intl, passCodeFirstStep]); const onPassCodeNext = () => { @@ -79,20 +79,20 @@ const PasswordSetup = ({ <> v.length >= PIN_CELL_COUNT ? undefined : intl.formatMessage( - { id: ETranslations.auth_error_password_too_short }, + { id: ETranslations.auth_error_passwcode_too_short }, { length: PIN_CELL_COUNT, }, @@ -238,7 +238,7 @@ const PasswordSetup = ({ - {`切换成${ - currentPasswordMode === EPasswordMode.PASSWORD - ? 'Passcode' - : 'Password' - }`} + {currentPasswordMode === EPasswordMode.PASSWORD + ? intl.formatMessage({ id: ETranslations.auth_Numeric_Passcode }) + : intl.formatMessage({ + id: ETranslations.auth_alphanumeric_passcode, + })} ) : null} diff --git a/packages/kit/src/components/Password/components/PasswordVerify.tsx b/packages/kit/src/components/Password/components/PasswordVerify.tsx index b813b7ee1dc..6bf25da373f 100644 --- a/packages/kit/src/components/Password/components/PasswordVerify.tsx +++ b/packages/kit/src/components/Password/components/PasswordVerify.tsx @@ -202,7 +202,7 @@ const PasswordVerify = ({ required: { value: true, message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, + id: ETranslations.auth_error_passcode_incorrect, }), }, onChange: onPasswordChange, @@ -213,7 +213,7 @@ const PasswordVerify = ({ size="large" editable={status.value !== EPasswordVerifyStatus.VERIFYING} placeholder={intl.formatMessage({ - id: ETranslations.auth_enter_your_password, + id: ETranslations.auth_enter_your_passcode, })} flex={1} // onChangeText={(text) => text.replace(PasswordRegex, '')} @@ -240,7 +240,7 @@ const PasswordVerify = ({ <> { mode, ); Toast.success({ - title: intl.formatMessage({ id: ETranslations.auth_password_set }), + title: intl.formatMessage({ id: ETranslations.auth_passcode_set }), }); onSetupRes(setUpPasswordRes); Dialog.show({ - title: 'PassCode Protection', + title: intl.formatMessage({ + id: ETranslations.auth_Passcode_protection, + }), renderIcon: ( { console.error(e); Toast.error({ title: intl.formatMessage({ - id: ETranslations.feedback_password_set_failed, + id: ETranslations.feedback_passcode_set_failed, }), }); } finally { diff --git a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx index f6be406b032..b1021c68e12 100644 --- a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx @@ -43,7 +43,7 @@ const PasswordUpdateContainer = ({ ); onUpdateRes(updatedPassword); Toast.success({ - title: intl.formatMessage({ id: ETranslations.auth_password_set }), + title: intl.formatMessage({ id: ETranslations.auth_passcode_set }), }); } catch (e) { console.error(e); diff --git a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx index da212850304..7c67a108910 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx @@ -24,6 +24,7 @@ import { import { dismissKeyboard } from '@onekeyhq/shared/src/keyboard'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; +import resetUtils from '@onekeyhq/shared/src/utils/resetUtils'; import timerUtils from '@onekeyhq/shared/src/utils/timerUtils'; import { EPasswordVerifyStatus } from '@onekeyhq/shared/types/password'; @@ -177,7 +178,10 @@ const PasswordVerifyContainer = ({ passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, + id: + verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts + ? ETranslations.auth_biometric_failed + : ETranslations.auth_error_passcode_incorrect, }), }, })); @@ -238,7 +242,10 @@ const PasswordVerifyContainer = ({ passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, message: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, + id: + verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts + ? ETranslations.auth_biometric_failed + : ETranslations.auth_error_passcode_incorrect, }), }, })); @@ -313,14 +320,40 @@ const PasswordVerifyContainer = ({ } else { nextAttempts = passwordErrorAttempts; } - if (nextAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX) { + if (nextAttempts >= PASSCODE_PROTECTION_ATTEMPTS) { + // reset app + try { + // disable setInterval on ext popup + if (platformEnv.isExtensionUiPopup) { + resetUtils.startResetting(); + } + await backgroundApiProxy.serviceApp.resetApp(); + } catch (error) { + console.error('failed to reset app with error', error); + } finally { + // able setInterval on ext popup + if (platformEnv.isExtensionUiPopup) { + resetUtils.endResetting(); + } + } + } else if ( + nextAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX + ) { const timeMinutes = PASSCODE_PROTECTION_ATTEMPTS_PER_MINUTE_MAP[ nextAttempts.toString() ]; - message = `${ - PASSCODE_PROTECTION_ATTEMPTS - nextAttempts - } more failed attempts will reset the device`; + // message = `${ + // PASSCODE_PROTECTION_ATTEMPTS - nextAttempts + // } more failed attempts will reset the device`; + message = intl.formatMessage( + { + id: ETranslations.auth_passcode_failed_alert, + }, + { + count: PASSCODE_PROTECTION_ATTEMPTS - nextAttempts, + }, + ); setPasswordPersist((v) => ({ ...v, passwordErrorProtectionTime: Date.now() + timeMinutes * 60 * 1000, // 2s for animation diff --git a/packages/kit/src/components/Password/container/PasswordVerifyPromptMount.tsx b/packages/kit/src/components/Password/container/PasswordVerifyPromptMount.tsx index 4d1a43c52d4..5f0f0028aea 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyPromptMount.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyPromptMount.tsx @@ -27,7 +27,7 @@ const PasswordVerifyPromptMount = () => { const showPasswordSetupPrompt = useCallback( (id: number) => { dialogRef.current = Dialog.show({ - title: intl.formatMessage({ id: ETranslations.global_set_password }), + title: intl.formatMessage({ id: ETranslations.global_set_passcode }), onClose() { onClose(id); }, @@ -55,7 +55,7 @@ const PasswordVerifyPromptMount = () => { dialogRef.current = Dialog.show({ ...dialogProps, title: intl.formatMessage({ - id: ETranslations.enter_password, + id: ETranslations.enter_passcode, }), onClose() { onClose(id); diff --git a/packages/kit/src/components/Password/hooks/usePasswordProtection.ts b/packages/kit/src/components/Password/hooks/usePasswordProtection.ts index 25c9d783627..daa6cb24be2 100644 --- a/packages/kit/src/components/Password/hooks/usePasswordProtection.ts +++ b/packages/kit/src/components/Password/hooks/usePasswordProtection.ts @@ -1,12 +1,16 @@ import { useEffect, useMemo, useRef, useState } from 'react'; +import { useIntl } from 'react-intl'; + import { PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { usePasswordPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms'; +import { ETranslations } from '@onekeyhq/shared/src/locale'; const usePasswordProtection = (isLock: boolean) => { const [unlockPeriodPasswordArray, setUnlockPeriodPasswordArray] = useState< string[] >([]); + const intl = useIntl(); const [passwordErrorProtectionTimeOver, setPasswordErrorProtectionTimeOver] = useState(false); const [ @@ -34,9 +38,14 @@ const usePasswordProtection = (isLock: boolean) => { passwordErrorProtectionTimeMinutesSurplus > 0 && !passwordErrorProtectionTimeOver ) { - return `Try again in ${Math.floor( - passwordErrorProtectionTimeMinutesSurplus, - )} minutes`; + return intl.formatMessage( + { + id: ETranslations.auth_passcode_cooldown, + }, + { + cooldowntime: Math.floor(passwordErrorProtectionTimeMinutesSurplus), + }, + ); } return ''; }, [ @@ -44,8 +53,9 @@ const usePasswordProtection = (isLock: boolean) => { enablePasswordErrorProtection, passwordErrorAttempts, passwordErrorProtectionTime, - passwordErrorProtectionTimeOver, passwordErrorProtectionTimeMinutesSurplus, + passwordErrorProtectionTimeOver, + intl, ]); const intervalRef = useRef>(); useEffect(() => { diff --git a/packages/kit/src/provider/Container/AppStateLockContainer/components/AppStateLock.tsx b/packages/kit/src/provider/Container/AppStateLockContainer/components/AppStateLock.tsx index 8ea6e158cca..4cb71b5a7c6 100644 --- a/packages/kit/src/provider/Container/AppStateLockContainer/components/AppStateLock.tsx +++ b/packages/kit/src/provider/Container/AppStateLockContainer/components/AppStateLock.tsx @@ -117,7 +117,7 @@ const AppStateLock = ({ v4migrationData?.isProcessing ? null : ( )} diff --git a/packages/kit/src/views/CloudBackup/components/useResotrePasswordVerify.tsx b/packages/kit/src/views/CloudBackup/components/useResotrePasswordVerify.tsx index fc6bbc5b35f..14942f8c1ee 100644 --- a/packages/kit/src/views/CloudBackup/components/useResotrePasswordVerify.tsx +++ b/packages/kit/src/views/CloudBackup/components/useResotrePasswordVerify.tsx @@ -17,7 +17,7 @@ function RestorePasswordVerify() { required: { value: true, message: intl.formatMessage({ - id: ETranslations.auth_enter_your_password, + id: ETranslations.auth_enter_your_passcode, }), }, onChange: () => {}, @@ -27,7 +27,7 @@ function RestorePasswordVerify() { autoFocus size="large" placeholder={intl.formatMessage({ - id: ETranslations.auth_enter_your_password, + id: ETranslations.auth_enter_your_passcode, })} flex={1} keyboardType={getPasswordKeyboardType(!secureEntry)} @@ -56,7 +56,7 @@ export function useRestorePasswordVerifyDialog() { icon: 'InfoCircleOutline', title: intl.formatMessage({ id: ETranslations.backup_import_data }), description: intl.formatMessage({ - id: ETranslations.backup_verify_app_password_to_import_data, + id: ETranslations.backup_verify_app_passcode_to_import_data, }), renderContent: , onConfirmText: intl.formatMessage({ diff --git a/packages/kit/src/views/CloudBackup/pages/Detail/index.tsx b/packages/kit/src/views/CloudBackup/pages/Detail/index.tsx index 1f680e63d9d..66326189b7f 100644 --- a/packages/kit/src/views/CloudBackup/pages/Detail/index.tsx +++ b/packages/kit/src/views/CloudBackup/pages/Detail/index.tsx @@ -280,7 +280,7 @@ export default function Detail() { if (result === ERestoreResult.WRONG_PASSWORD) { Toast.error({ title: intl.formatMessage({ - id: ETranslations.auth_error_password_incorrect, + id: ETranslations.auth_error_passcode_incorrect, }), }); } else if (result === ERestoreResult.UNKNOWN_ERROR) { diff --git a/packages/kit/src/views/Onboarding/pages/CreateWalet/BeforeShowRecoveryPhrase.tsx b/packages/kit/src/views/Onboarding/pages/CreateWalet/BeforeShowRecoveryPhrase.tsx index 6dd59394b79..49c57c8d367 100644 --- a/packages/kit/src/views/Onboarding/pages/CreateWalet/BeforeShowRecoveryPhrase.tsx +++ b/packages/kit/src/views/Onboarding/pages/CreateWalet/BeforeShowRecoveryPhrase.tsx @@ -52,7 +52,7 @@ export function BeforeShowRecoveryPhrase() { iconColor: '$iconSuccess', iconContainerColor: '$bgSuccess', message: intl.formatMessage({ - id: ETranslations.onboarding_bullet_forgot_password_use_recovery, + id: ETranslations.onboarding_bullet_forgot_passcode_use_recovery, }), }, { diff --git a/packages/kit/src/views/Setting/pages/List/SecuritySection/index.tsx b/packages/kit/src/views/Setting/pages/List/SecuritySection/index.tsx index a6b1b7c6a37..3ff6daf3f5b 100644 --- a/packages/kit/src/views/Setting/pages/List/SecuritySection/index.tsx +++ b/packages/kit/src/views/Setting/pages/List/SecuritySection/index.tsx @@ -66,7 +66,7 @@ const SetPasswordItem = () => { void backgroundApiProxy.servicePassword.promptPasswordVerify(); }} icon="KeyOutline" - title={intl.formatMessage({ id: ETranslations.global_set_password })} + title={intl.formatMessage({ id: ETranslations.global_set_passcode })} drillIn /> ); @@ -80,7 +80,7 @@ const ChangePasswordItem = () => { reason: EReasonForNeedPassword.Security, }); const dialog = Dialog.show({ - title: intl.formatMessage({ id: ETranslations.global_change_password }), + title: intl.formatMessage({ id: ETranslations.global_change_passcode }), renderContent: ( { ); diff --git a/packages/kit/src/views/Setting/pages/Protection/index.tsx b/packages/kit/src/views/Setting/pages/Protection/index.tsx index ca62ba99818..da592528998 100644 --- a/packages/kit/src/views/Setting/pages/Protection/index.tsx +++ b/packages/kit/src/views/Setting/pages/Protection/index.tsx @@ -51,7 +51,7 @@ const SettingProtectionModal = () => { { {intl.formatMessage({ - id: ETranslations.settings_password_bypass_desc, + id: ETranslations.settings_passcode_bypass_desc, })} - + - Reset app on this device after 10 failed passcode attempts. + {intl.formatMessage({ + id: ETranslations.Setting_Reset_app_description, + })} diff --git a/packages/shared/src/errors/errors/appErrors.ts b/packages/shared/src/errors/errors/appErrors.ts index 908c0efd503..a118d63b579 100644 --- a/packages/shared/src/errors/errors/appErrors.ts +++ b/packages/shared/src/errors/errors/appErrors.ts @@ -24,7 +24,7 @@ export class IncorrectPassword extends OneKeyError { super( normalizeErrorProps(props, { defaultMessage: 'OneKeyError: IncorrectPassword', - defaultKey: ETranslations.auth_error_password_incorrect, + defaultKey: ETranslations.auth_error_passcode_incorrect, }), ); } @@ -169,7 +169,7 @@ export class WrongPassword extends OneKeyError { super( normalizeErrorProps(props, { defaultMessage: 'WrongPassword', - defaultKey: ETranslations.send_engine_incorrect_password, + defaultKey: ETranslations.send_engine_incorrect_passcode, defaultAutoToast: true, }), ); @@ -196,7 +196,7 @@ export class PasswordNotSet extends OneKeyError { super( normalizeErrorProps(props, { defaultMessage: 'PasswordNotSet', - defaultKey: ETranslations.send_engine_password_not_set, + defaultKey: ETranslations.send_engine_passcode_not_set, defaultAutoToast: true, }), ); @@ -208,7 +208,7 @@ export class PasswordStrengthValidationFailed extends OneKeyError { super( normalizeErrorProps(props, { defaultMessage: 'PasswordStrengthValidationFailed', - defaultKey: ETranslations.send_password_validation, + defaultKey: ETranslations.send_passcode_validation, }), ); } @@ -219,7 +219,7 @@ export class PasswordUpdateSameFailed extends OneKeyError { super( normalizeErrorProps(props, { defaultMessage: 'PasswordUpdateSameFailed', - defaultKey: ETranslations.auth_error_password_incorrect, + defaultKey: ETranslations.auth_error_passcode_incorrect, }), ); } @@ -241,7 +241,7 @@ export class PasswordAlreadySetFailed extends OneKeyError { super( normalizeErrorProps(props, { defaultMessage: 'PasswordAlreadySetFaield', - defaultKey: ETranslations.auth_error_password_incorrect, + defaultKey: ETranslations.auth_error_passcode_incorrect, }), ); } From 3421bae240c776e08bf106dcac40992b021da447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Thu, 19 Dec 2024 11:48:33 +0800 Subject: [PATCH 08/13] fix: lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- .../components/Password/container/PasswordUpdateContainer.tsx | 2 +- packages/shared/src/locale/enum/translations.ts | 1 + packages/shared/src/locale/json/bn.json | 1 + packages/shared/src/locale/json/de.json | 1 + packages/shared/src/locale/json/en.json | 1 + packages/shared/src/locale/json/en_US.json | 1 + packages/shared/src/locale/json/es.json | 1 + packages/shared/src/locale/json/fr_FR.json | 1 + packages/shared/src/locale/json/hi_IN.json | 1 + packages/shared/src/locale/json/id.json | 1 + packages/shared/src/locale/json/it_IT.json | 1 + packages/shared/src/locale/json/ja_JP.json | 1 + packages/shared/src/locale/json/ko_KR.json | 1 + packages/shared/src/locale/json/pt.json | 1 + packages/shared/src/locale/json/pt_BR.json | 1 + packages/shared/src/locale/json/ru.json | 1 + packages/shared/src/locale/json/th_TH.json | 1 + packages/shared/src/locale/json/uk_UA.json | 1 + packages/shared/src/locale/json/vi.json | 1 + packages/shared/src/locale/json/zh_CN.json | 1 + packages/shared/src/locale/json/zh_HK.json | 1 + packages/shared/src/locale/json/zh_TW.json | 1 + 22 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx index b1021c68e12..93709e0d652 100644 --- a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx @@ -49,7 +49,7 @@ const PasswordUpdateContainer = ({ console.error(e); Toast.error({ title: intl.formatMessage({ - id: ETranslations.auth_new_password_same_as_old, + id: ETranslations.auth_new_passcode_same_as_old, }), }); } diff --git a/packages/shared/src/locale/enum/translations.ts b/packages/shared/src/locale/enum/translations.ts index 45bb615c25a..f531fbaae49 100644 --- a/packages/shared/src/locale/enum/translations.ts +++ b/packages/shared/src/locale/enum/translations.ts @@ -88,6 +88,7 @@ auth_error_password_not_match = 'auth.error_password_not_match', auth_error_password_too_short = 'auth.error_password_too_short', auth_new_passcode_form_label = 'auth.new_passcode_form_label', + auth_new_passcode_same_as_old = 'auth.new_passcode_same_as_old', auth_new_passwcode_form_placeholder = 'auth.new_passwcode_form_placeholder', auth_new_password_form_label = 'auth.new_password_form_label', auth_new_password_form_placeholder = 'auth.new_password_form_placeholder', diff --git a/packages/shared/src/locale/json/bn.json b/packages/shared/src/locale/json/bn.json index 3264ecb2684..be31d7fae33 100644 --- a/packages/shared/src/locale/json/bn.json +++ b/packages/shared/src/locale/json/bn.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "পাসওয়ার্ডগুলি মিলছে না", "auth.error_password_too_short": "পাসওয়ার্ডটি অবশ্যই কমপক্ষে {length} অক্ষরের হতে হবে", "auth.new_passcode_form_label": "নতুন পাসকোড", + "auth.new_passcode_same_as_old": "নতুন পাসকোডটি পুরোনোটির থেকে ভিন্ন হতে হবে", "auth.new_passwcode_form_placeholder": "একটি শক্তিশালী পাসকোড তৈরি করুন", "auth.new_password_form_label": "নতুন পাসওয়ার্ড", "auth.new_password_form_placeholder": "একটি শক্তিশালী পাসওয়ার্ড তৈরি করুন", diff --git a/packages/shared/src/locale/json/de.json b/packages/shared/src/locale/json/de.json index e44f21e6c7f..94bb14992d5 100644 --- a/packages/shared/src/locale/json/de.json +++ b/packages/shared/src/locale/json/de.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Passwörter stimmen nicht überein", "auth.error_password_too_short": "Das Passwort muss mindestens {length} Zeichen lang sein", "auth.new_passcode_form_label": "Neuer Zugangscode", + "auth.new_passcode_same_as_old": "Der neue Zugangscode muss sich vom alten unterscheiden", "auth.new_passwcode_form_placeholder": "Erstellen Sie ein starkes Passwort", "auth.new_password_form_label": "Neues Passwort", "auth.new_password_form_placeholder": "Erstellen Sie ein starkes Passwort", diff --git a/packages/shared/src/locale/json/en.json b/packages/shared/src/locale/json/en.json index 488866f09bd..75621e8c8c0 100644 --- a/packages/shared/src/locale/json/en.json +++ b/packages/shared/src/locale/json/en.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Passwords do not match", "auth.error_password_too_short": "Password must be at least {length} characters", "auth.new_passcode_form_label": "New passcode", + "auth.new_passcode_same_as_old": "New passcode must differ from the old one", "auth.new_passwcode_form_placeholder": "Create a robust password", "auth.new_password_form_label": "New password", "auth.new_password_form_placeholder": "Create a strong password", diff --git a/packages/shared/src/locale/json/en_US.json b/packages/shared/src/locale/json/en_US.json index 0116157a3e7..8e69a6b6e6f 100644 --- a/packages/shared/src/locale/json/en_US.json +++ b/packages/shared/src/locale/json/en_US.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Passwords do not match", "auth.error_password_too_short": "Password must be at least {length} characters", "auth.new_passcode_form_label": "New passcode", + "auth.new_passcode_same_as_old": "New passcode must differ from the old one", "auth.new_passwcode_form_placeholder": "Create a strong passcode", "auth.new_password_form_label": "New password", "auth.new_password_form_placeholder": "Create a strong password", diff --git a/packages/shared/src/locale/json/es.json b/packages/shared/src/locale/json/es.json index c02427869dd..b2debf53d05 100644 --- a/packages/shared/src/locale/json/es.json +++ b/packages/shared/src/locale/json/es.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Las contraseñas no coinciden", "auth.error_password_too_short": "La contraseña debe tener al menos {length} caracteres", "auth.new_passcode_form_label": "Nuevo código de acceso", + "auth.new_passcode_same_as_old": "El nuevo código de acceso debe ser diferente del anterior", "auth.new_passwcode_form_placeholder": "Crea un código de acceso fuerte", "auth.new_password_form_label": "Nueva contraseña", "auth.new_password_form_placeholder": "Crea una contraseña fuerte", diff --git a/packages/shared/src/locale/json/fr_FR.json b/packages/shared/src/locale/json/fr_FR.json index 654647182b6..d7947f3e23f 100644 --- a/packages/shared/src/locale/json/fr_FR.json +++ b/packages/shared/src/locale/json/fr_FR.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Les mots de passe ne correspondent pas", "auth.error_password_too_short": "Le mot de passe doit comporter au moins {length} caractères", "auth.new_passcode_form_label": "Nouveau code d'accès", + "auth.new_passcode_same_as_old": "Le nouveau code d'accès doit être différent de l'ancien", "auth.new_passwcode_form_placeholder": "Créez un code d'accès fort", "auth.new_password_form_label": "Nouveau mot de passe", "auth.new_password_form_placeholder": "Créez un mot de passe fort", diff --git a/packages/shared/src/locale/json/hi_IN.json b/packages/shared/src/locale/json/hi_IN.json index 02569d0db1c..739d3cabbe1 100644 --- a/packages/shared/src/locale/json/hi_IN.json +++ b/packages/shared/src/locale/json/hi_IN.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "पासवर्ड मेल नहीं खाते", "auth.error_password_too_short": "पासवर्ड कम से कम {length} वर्णों का होना चाहिए", "auth.new_passcode_form_label": "नया पासकोड", + "auth.new_passcode_same_as_old": "नया पासकोड पुराने से अलग होना चाहिए", "auth.new_passwcode_form_placeholder": "मजबूत पासकोड बनाएं", "auth.new_password_form_label": "नया पासवर्ड", "auth.new_password_form_placeholder": "एक मजबूत पासवर्ड बनाएं", diff --git a/packages/shared/src/locale/json/id.json b/packages/shared/src/locale/json/id.json index 33a8a7bbdc4..7aa4b4cd946 100644 --- a/packages/shared/src/locale/json/id.json +++ b/packages/shared/src/locale/json/id.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Kata sandi tidak cocok", "auth.error_password_too_short": "Kata sandi harus setidaknya {length} karakter", "auth.new_passcode_form_label": "Kode sandi baru", + "auth.new_passcode_same_as_old": "Kode sandi baru harus berbeda dari yang lama", "auth.new_passwcode_form_placeholder": "Buat kata sandi yang kuat", "auth.new_password_form_label": "Kata sandi baru", "auth.new_password_form_placeholder": "Buat kata sandi yang kuat", diff --git a/packages/shared/src/locale/json/it_IT.json b/packages/shared/src/locale/json/it_IT.json index faec4af9dea..786e4fe5f62 100644 --- a/packages/shared/src/locale/json/it_IT.json +++ b/packages/shared/src/locale/json/it_IT.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Le password non corrispondono", "auth.error_password_too_short": "La password deve essere di almeno {length} caratteri", "auth.new_passcode_form_label": "Nuovo codice di accesso", + "auth.new_passcode_same_as_old": "Il nuovo codice di accesso deve essere diverso da quello vecchio", "auth.new_passwcode_form_placeholder": "Crea un codice di accesso sicuro", "auth.new_password_form_label": "Nuova password", "auth.new_password_form_placeholder": "Crea una password forte", diff --git a/packages/shared/src/locale/json/ja_JP.json b/packages/shared/src/locale/json/ja_JP.json index 8dd0f8b453d..d615932f658 100644 --- a/packages/shared/src/locale/json/ja_JP.json +++ b/packages/shared/src/locale/json/ja_JP.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "パスワードが一致しません", "auth.error_password_too_short": "パスワードは少なくとも{length}文字でなければなりません", "auth.new_passcode_form_label": "新しいパスコード", + "auth.new_passcode_same_as_old": "新しいパスコードは古いものと異なる必要があります", "auth.new_passwcode_form_placeholder": "強力なパスコードを作成する", "auth.new_password_form_label": "新しいパスワード", "auth.new_password_form_placeholder": "強力なパスワードを作成する", diff --git a/packages/shared/src/locale/json/ko_KR.json b/packages/shared/src/locale/json/ko_KR.json index 1adb599d3f2..d6c8bc96c37 100644 --- a/packages/shared/src/locale/json/ko_KR.json +++ b/packages/shared/src/locale/json/ko_KR.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "비밀번호가 일치하지 않습니다", "auth.error_password_too_short": "비밀번호는 최소 {length}자 이상이어야 합니다", "auth.new_passcode_form_label": "새 비밀번호", + "auth.new_passcode_same_as_old": "새 비밀번호는 이전 비밀번호와 달라야 합니다", "auth.new_passwcode_form_placeholder": "강력한 비밀번호를 생성하세요", "auth.new_password_form_label": "새 비밀번호", "auth.new_password_form_placeholder": "강력한 비밀번호를 생성하세요", diff --git a/packages/shared/src/locale/json/pt.json b/packages/shared/src/locale/json/pt.json index 8c848cf363a..72377023c75 100644 --- a/packages/shared/src/locale/json/pt.json +++ b/packages/shared/src/locale/json/pt.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "As senhas não coincidem", "auth.error_password_too_short": "A senha deve ter pelo menos {length} caracteres", "auth.new_passcode_form_label": "Novo código de acesso", + "auth.new_passcode_same_as_old": "O novo código de acesso deve ser diferente do antigo", "auth.new_passwcode_form_placeholder": "Crie um código de acesso forte", "auth.new_password_form_label": "Nova senha", "auth.new_password_form_placeholder": "Crie uma senha forte", diff --git a/packages/shared/src/locale/json/pt_BR.json b/packages/shared/src/locale/json/pt_BR.json index 694a63a20d9..7756d49e821 100644 --- a/packages/shared/src/locale/json/pt_BR.json +++ b/packages/shared/src/locale/json/pt_BR.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "As senhas não coincidem", "auth.error_password_too_short": "A senha deve ter pelo menos {length} caracteres", "auth.new_passcode_form_label": "Novo código de acesso", + "auth.new_passcode_same_as_old": "O novo código de acesso deve ser diferente do antigo", "auth.new_passwcode_form_placeholder": "Crie um código de acesso forte", "auth.new_password_form_label": "Nova senha", "auth.new_password_form_placeholder": "Crie uma senha forte", diff --git a/packages/shared/src/locale/json/ru.json b/packages/shared/src/locale/json/ru.json index 89919e4798f..41bd04bd446 100644 --- a/packages/shared/src/locale/json/ru.json +++ b/packages/shared/src/locale/json/ru.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Пароли не совпадают", "auth.error_password_too_short": "Пароль должен содержать не менее {length} символов", "auth.new_passcode_form_label": "Новый код доступа", + "auth.new_passcode_same_as_old": "Новый код доступа должен отличаться от старого", "auth.new_passwcode_form_placeholder": "Создайте надежный пароль", "auth.new_password_form_label": "Новый пароль", "auth.new_password_form_placeholder": "Создайте надежный пароль", diff --git a/packages/shared/src/locale/json/th_TH.json b/packages/shared/src/locale/json/th_TH.json index 0a68d9f35b7..2ba855de18d 100644 --- a/packages/shared/src/locale/json/th_TH.json +++ b/packages/shared/src/locale/json/th_TH.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "รหัสผ่านไม่ตรงกัน", "auth.error_password_too_short": "รหัสผ่านต้องมีอย่างน้อย {length} ตัวอักษร", "auth.new_passcode_form_label": "รหัสผ่านใหม่", + "auth.new_passcode_same_as_old": "รหัสผ่านใหม่ต้องแตกต่างจากรหัสผ่านเก่า", "auth.new_passwcode_form_placeholder": "สร้างรหัสผ่านที่แข็งแกร่ง", "auth.new_password_form_label": "รหัสผ่านใหม่", "auth.new_password_form_placeholder": "สร้างรหัสผ่านที่มั่นคง", diff --git a/packages/shared/src/locale/json/uk_UA.json b/packages/shared/src/locale/json/uk_UA.json index 003ce88a9a7..d8ea5ce202d 100644 --- a/packages/shared/src/locale/json/uk_UA.json +++ b/packages/shared/src/locale/json/uk_UA.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Паролі не співпадають", "auth.error_password_too_short": "Пароль повинен містити принаймні {length} символів", "auth.new_passcode_form_label": "Новий код доступу", + "auth.new_passcode_same_as_old": "Новий код доступу повинен відрізнятися від старого", "auth.new_passwcode_form_placeholder": "Створіть надійний пароль", "auth.new_password_form_label": "Новий пароль", "auth.new_password_form_placeholder": "Створіть надійний пароль", diff --git a/packages/shared/src/locale/json/vi.json b/packages/shared/src/locale/json/vi.json index da780daf12a..447dcb8ca9b 100644 --- a/packages/shared/src/locale/json/vi.json +++ b/packages/shared/src/locale/json/vi.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "Mật khẩu không khớp", "auth.error_password_too_short": "Mật khẩu phải có ít nhất {length} ký tự", "auth.new_passcode_form_label": "Mã bảo mật mới", + "auth.new_passcode_same_as_old": "Mã bảo mật mới phải khác với mã cũ", "auth.new_passwcode_form_placeholder": "Tạo một mật mã mạnh", "auth.new_password_form_label": "Mật khẩu mới", "auth.new_password_form_placeholder": "Tạo một mật khẩu mạnh", diff --git a/packages/shared/src/locale/json/zh_CN.json b/packages/shared/src/locale/json/zh_CN.json index ebed0d09e7e..593521a9806 100644 --- a/packages/shared/src/locale/json/zh_CN.json +++ b/packages/shared/src/locale/json/zh_CN.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "密码不匹配", "auth.error_password_too_short": "密码必须至少包含 {length} 个字符", "auth.new_passcode_form_label": "新密码", + "auth.new_passcode_same_as_old": "新密码必须与旧密码不同", "auth.new_passwcode_form_placeholder": "创建一个强密码", "auth.new_password_form_label": "新密码", "auth.new_password_form_placeholder": "创建一个强密码", diff --git a/packages/shared/src/locale/json/zh_HK.json b/packages/shared/src/locale/json/zh_HK.json index 574415354db..2bb74de582d 100644 --- a/packages/shared/src/locale/json/zh_HK.json +++ b/packages/shared/src/locale/json/zh_HK.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "密碼不符合", "auth.error_password_too_short": "密碼必須至少包含 {length} 個字符", "auth.new_passcode_form_label": "新密碼", + "auth.new_passcode_same_as_old": "新密碼必須與舊密碼不同", "auth.new_passwcode_form_placeholder": "創建一個強密碼", "auth.new_password_form_label": "新密碼", "auth.new_password_form_placeholder": "建立一個強密碼", diff --git a/packages/shared/src/locale/json/zh_TW.json b/packages/shared/src/locale/json/zh_TW.json index 62a1409b90f..c11340aabac 100644 --- a/packages/shared/src/locale/json/zh_TW.json +++ b/packages/shared/src/locale/json/zh_TW.json @@ -83,6 +83,7 @@ "auth.error_password_not_match": "密碼不相符", "auth.error_password_too_short": "密碼必須至少有 {length} 個字符", "auth.new_passcode_form_label": "新密碼", + "auth.new_passcode_same_as_old": "新密碼必須與舊密碼不同", "auth.new_passwcode_form_placeholder": "創建一個強大的密碼", "auth.new_password_form_label": "新密碼", "auth.new_password_form_placeholder": "創建一個強大的密碼", From 35dfd33608fa629076ccc78eecf70aad3380dfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Wed, 25 Dec 2024 16:02:07 +0800 Subject: [PATCH 09/13] fix: opt code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- .../OneKeyWallet.xcodeproj/project.pbxproj | 2 -- apps/mobile/ios/Podfile.lock | 6 ++--- .../Password/components/PassCodeInput.tsx | 27 ++++++++++++++----- .../Password/components/PasswordSetup.tsx | 3 +++ .../Password/components/PasswordVerify.tsx | 4 +-- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj index 3a6b56ebbcf..de985da7c6f 100644 --- a/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/OneKeyWallet.xcodeproj/project.pbxproj @@ -424,7 +424,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -436,7 +435,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 6002da0ad5c..ea5db7ac513 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -1686,7 +1686,7 @@ SPEC CHECKSUMS: Burnt: dde5dd245f124a4594098e3938ba71aae4ec83c3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 CocoaLumberjack: 5c7e64cdb877770859bddec4d3d5a0d7c9299df9 - DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 EXApplication: 137189a3f149b4e8e546884629392c3efc94cbd3 EXBarCodeScanner: d59fd943cebee3f913ebf4ffde0d05d344da8b78 EXConstants: 988aa430ca0f76b43cd46b66e7fae3287f9cc2fc @@ -1713,7 +1713,7 @@ SPEC CHECKSUMS: FBLazyVector: 9f533d5a4c75ca77c8ed774aced1a91a0701781e FBReactNativeSpec: 2db5940ee4b58968274eec0a4f1c736fc4caefa3 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 69ef571f3de08433d766d614c73a9838a06bf7eb + glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 39589e9c297d024e90fe68f6830ff86c4e01498a ImageColors: 88be684570585c07ae2750bff34eb7b78bfc53b4 IQKeyboardManagerSwift: c7955c0bdbf7b2eb29bb7daaa44e3d90f55a9a85 @@ -1817,7 +1817,7 @@ SPEC CHECKSUMS: SPIndicator: 93e0a4fb23de51294ac48e874c0f081a5e293e4f SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 - Yoga: 6d01ccde54c9f8b92492beb05d468dbfed1d9881 + Yoga: 07db09965bc46c4902e20d3ae6990d95e53af8a8 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 PODFILE CHECKSUM: 5b7f20a90e19262f325cab10e37056b7f3cd0ffb diff --git a/packages/kit/src/components/Password/components/PassCodeInput.tsx b/packages/kit/src/components/Password/components/PassCodeInput.tsx index d8068356a51..07ff22acdea 100644 --- a/packages/kit/src/components/Password/components/PassCodeInput.tsx +++ b/packages/kit/src/components/Password/components/PassCodeInput.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { StyleSheet, Text } from 'react-native'; import { @@ -18,9 +18,10 @@ const cellStyles = StyleSheet.create({ fontSize: 30, fontWeight: '700', textAlign: 'center', - marginLeft: 8, - borderRadius: 6, - backgroundColor: '#eee', + marginRight: 8, + borderRadius: 20, + borderWidth: 1, + borderColor: '#eee', }, focusCell: { borderColor: '#000', @@ -31,12 +32,16 @@ const PassCodeInput = ({ onPinCodeChange, onComplete, disabledComplete, + pinCodeFocus, + enableAutoFocus, showMask, testId, }: { onPinCodeChange?: (pin: string) => void; onComplete?: () => void; disabledComplete?: boolean; + pinCodeFocus?: boolean; + enableAutoFocus?: boolean; testId?: string; showMask?: boolean; }) => { @@ -71,17 +76,27 @@ const PassCodeInput = ({ return ( {textChild} ); }; + + useEffect(() => { + if (pinCodeFocus) { + pinInputRef.current?.focus(); + } + }, [pinCodeFocus, pinInputRef]); + return ( @@ -269,6 +270,8 @@ const PasswordSetup = ({ form.setValue('confirmPassCode', pin); form.clearErrors('confirmPassCode'); }} + enableAutoFocus={false} + pinCodeFocus={passCodeConfirm} testId="confirm-pass-code" showMask /> diff --git a/packages/kit/src/components/Password/components/PasswordVerify.tsx b/packages/kit/src/components/Password/components/PasswordVerify.tsx index 6bf25da373f..4607667bf8e 100644 --- a/packages/kit/src/components/Password/components/PasswordVerify.tsx +++ b/packages/kit/src/components/Password/components/PasswordVerify.tsx @@ -239,9 +239,6 @@ const PasswordVerify = ({ ) : ( <> From a8a9262fe214c22a324103a8b30de732c41f3398 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 27 Dec 2024 09:59:52 +0800 Subject: [PATCH 10/13] ui --- packages/components/src/forms/index.ts | 1 + .../Password/components/PassCodeInput.tsx | 134 +++++++++--------- .../PassCodeProtectionDialogContent.tsx | 19 +-- .../Password/components/PasswordSetup.tsx | 20 +-- .../Password/components/PasswordVerify.tsx | 15 +- .../container/PasswordSetupContainer.tsx | 3 + 6 files changed, 99 insertions(+), 93 deletions(-) diff --git a/packages/components/src/forms/index.ts b/packages/components/src/forms/index.ts index 4a7686ba37d..3cc60514688 100644 --- a/packages/components/src/forms/index.ts +++ b/packages/components/src/forms/index.ts @@ -6,4 +6,5 @@ export * from './Select'; export * from './Slider'; export * from './Switch'; export * from './TextArea'; +export * from './PinCodeValidator'; export * from './types'; diff --git a/packages/kit/src/components/Password/components/PassCodeInput.tsx b/packages/kit/src/components/Password/components/PassCodeInput.tsx index 07ff22acdea..13b9f4b686b 100644 --- a/packages/kit/src/components/Password/components/PassCodeInput.tsx +++ b/packages/kit/src/components/Password/components/PassCodeInput.tsx @@ -3,31 +3,14 @@ import { useEffect, useState } from 'react'; import { StyleSheet, Text } from 'react-native'; import { CodeField, - Cursor, useBlurOnFulfill, useClearByFocusCell, } from 'react-native-confirmation-code-field'; -import { IconButton, XStack } from '@onekeyhq/components'; +import { YStack } from '@onekeyhq/components'; export const PIN_CELL_COUNT = 6; -const cellStyles = StyleSheet.create({ - cell: { - flex: 1, - fontSize: 30, - fontWeight: '700', - textAlign: 'center', - marginRight: 8, - borderRadius: 20, - borderWidth: 1, - borderColor: '#eee', - }, - focusCell: { - borderColor: '#000', - }, -}); - const PassCodeInput = ({ onPinCodeChange, onComplete, @@ -57,6 +40,14 @@ const PassCodeInput = ({ }); const [enableMask, setEnableMask] = useState(true); const toggleMask = () => setEnableMask((f) => !f); + + const cellStyles = StyleSheet.create({ + cell: { + width: 16, + height: 16, + }, + }); + const renderCell = ({ index, symbol, @@ -65,28 +56,33 @@ const PassCodeInput = ({ index: number; symbol: string; isFocused: boolean; - }) => { - let textChild = null; - if (symbol) { - textChild = enableMask ? '•' : symbol; - } else if (isFocused) { - textChild = ; - } - - return ( - - {textChild} - - ); - }; + }) => ( + // let textChild = null; + // if (symbol) { + // textChild = enableMask ? '•' : symbol; + // } + + {/* {symbol ? ( + + ) : ( + '' + )} */} + + + ); useEffect(() => { if (pinCodeFocus) { pinInputRef.current?.focus(); @@ -94,33 +90,39 @@ const PassCodeInput = ({ }, [pinCodeFocus, pinInputRef]); return ( - - { - setPinValue(text); - onPinCodeChange?.(text); - if (text.length === PIN_CELL_COUNT && !disabledComplete) { - onComplete?.(); - } - }} - cellCount={PIN_CELL_COUNT} - keyboardType="number-pad" - textContentType="oneTimeCode" - renderCell={renderCell} - /> - {showMask ? ( - - ) : null} - + { + setPinValue(text); + onPinCodeChange?.(text); + if (text.length === PIN_CELL_COUNT && !disabledComplete) { + onComplete?.(); + } + }} + cellCount={PIN_CELL_COUNT} + keyboardType="number-pad" + textContentType="oneTimeCode" + renderCell={renderCell} + {...props} + /> + + // + // {showMask ? ( + // + // ) : null} + // ); }; diff --git a/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx b/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx index c70e344cf37..7fa0f4e4846 100644 --- a/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx +++ b/packages/kit/src/components/Password/components/PassCodeProtectionDialogContent.tsx @@ -1,6 +1,6 @@ import { useIntl } from 'react-intl'; -import { SizableText, XStack, YStack } from '@onekeyhq/components'; +import { SizableText, XStack } from '@onekeyhq/components'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import PassCodeProtectionSwitch from '../container/PassCodeProtectionSwitch'; @@ -8,21 +8,14 @@ import PassCodeProtectionSwitch from '../container/PassCodeProtectionSwitch'; const PassCodeProtectionDialogContent = () => { const intl = useIntl(); return ( - - + + {intl.formatMessage({ - id: ETranslations.auth_Passcode_protection_description, + id: ETranslations.Setting_Reset_app_description, })} - - - {intl.formatMessage({ - id: ETranslations.Setting_Reset_app_description, - })} - - - - + + ); }; diff --git a/packages/kit/src/components/Password/components/PasswordSetup.tsx b/packages/kit/src/components/Password/components/PasswordSetup.tsx index 02209b11f20..8307c8c57e2 100644 --- a/packages/kit/src/components/Password/components/PasswordSetup.tsx +++ b/packages/kit/src/components/Password/components/PasswordSetup.tsx @@ -2,7 +2,14 @@ import { memo, useEffect, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; -import { Button, Form, Input, Unspaced, useForm } from '@onekeyhq/components'; +import { + Button, + Divider, + Form, + Input, + Unspaced, + useForm, +} from '@onekeyhq/components'; import { EPasswordMode } from '@onekeyhq/kit-bg/src/services/ServicePassword/types'; import { ETranslations } from '@onekeyhq/shared/src/locale'; import platformEnv from '@onekeyhq/shared/src/platformEnv'; @@ -193,9 +200,6 @@ const PasswordSetup = ({ ) : ( <> + )} @@ -299,8 +301,8 @@ const PasswordSetup = ({ {platformEnv.isNative ? ( - {platformEnv.isNative ? ( + {!passCodeFirstStep ? ( + {biologyAuthSwitchContainer} + ) : null} - ) : null} - + {platformEnv.isNative ? ( + + ) : null} + + ); }; diff --git a/packages/kit/src/components/Password/components/PasswordVerify.tsx b/packages/kit/src/components/Password/components/PasswordVerify.tsx index d8ab6f0a0b6..fb1315227a9 100644 --- a/packages/kit/src/components/Password/components/PasswordVerify.tsx +++ b/packages/kit/src/components/Password/components/PasswordVerify.tsx @@ -153,20 +153,21 @@ const PasswordVerify = ({ onInputPasswordAuth, confirmBtnDisabled, ]); - + const [passCodeClear, setPassCodeClear] = useState(false); useEffect(() => { const fieldName = passwordMode === EPasswordMode.PASSWORD ? 'password' : 'passCode'; if (status.value === EPasswordVerifyStatus.ERROR) { + form.setError(fieldName, { message: status.message }); if (passwordMode === EPasswordMode.PASSCODE) { - form.setValue(fieldName, ''); + setPassCodeClear(true); + } else { + form.setFocus(fieldName); } - form.setError(fieldName, { message: status.message }); - form.setFocus(fieldName); } else { form.clearErrors(fieldName); } - }, [form, passwordMode, status]); + }, [form, passwordMode, status, disableInput]); useLayoutEffect(() => { if ( @@ -266,17 +267,26 @@ const PasswordVerify = ({ onPinCodeChange={(pin) => { form.setValue('passCode', pin); form.clearErrors('passCode'); + setPassCodeClear(false); }} editable={Boolean( status.value !== EPasswordVerifyStatus.VERIFYING && !disableInput, )} onComplete={form.handleSubmit(onInputPasswordAuth)} + clearCode={passCodeClear} disabledComplete={confirmBtnDisabled} enableAutoFocus testId="pass-code-input" /> + {alertText ? ( + + + {alertText} + + + ) : null} {isEnable ? ( - ) : ( - <> - {alertText ? ( - - - {alertText} - - - ) : null} - - )} + ) : null} )} diff --git a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx index 93709e0d652..30116c25ddb 100644 --- a/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordUpdateContainer.tsx @@ -29,7 +29,6 @@ const PasswordUpdateContainer = ({ const finalPassword = mode === EPasswordMode.PASSCODE ? confirmPassCode : confirmPassword; setLoading(true); - setLoading(true); try { const encodeNewPassword = await backgroundApiProxy.servicePassword.encodeSensitiveText({ diff --git a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx index 7c67a108910..d67b2f87674 100644 --- a/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx +++ b/packages/kit/src/components/Password/container/PasswordVerifyContainer.tsx @@ -28,6 +28,7 @@ import resetUtils from '@onekeyhq/shared/src/utils/resetUtils'; import timerUtils from '@onekeyhq/shared/src/utils/timerUtils'; import { EPasswordVerifyStatus } from '@onekeyhq/shared/types/password'; +import { useBiometricAuthInfo } from '../../../hooks/useBiometricAuthInfo'; import { useWebAuthActions } from '../../BiologyAuthComponent/hooks/useWebAuthActions'; import PasswordVerify from '../components/PasswordVerify'; import usePasswordProtection from '../hooks/usePasswordProtection'; @@ -54,6 +55,7 @@ const PasswordVerifyContainer = ({ const [hasCachedPassword, setHasCachedPassword] = useState(false); const [hasSecurePassword, setHasSecurePassword] = useState(false); const [passwordMode] = usePasswordModeAtom(); + const { title } = useBiometricAuthInfo(); const biologyAuthAttempts = useMemo( () => authType.includes(AuthenticationType.FACIAL_RECOGNITION) @@ -113,6 +115,7 @@ const PasswordVerifyContainer = ({ setUnlockPeriodPasswordArray, alertText, setPasswordPersist, + isProtectionTime, enablePasswordErrorProtection, } = usePasswordProtection(isLock); @@ -134,13 +137,13 @@ const PasswordVerifyContainer = ({ ); }, [ - hasCachedPassword, - hasSecurePassword, - isEnable, - webAuthCredentialId, - isBiologyAuthSwitchOn, isExtLockAndNoCachePassword, + isBiologyAuthSwitchOn, verifyPeriodBiologyEnable, + isEnable, + hasSecurePassword, + webAuthCredentialId, + hasCachedPassword, ], ); @@ -177,12 +180,20 @@ const PasswordVerifyContainer = ({ ...v, passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, - message: intl.formatMessage({ - id: - verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts - ? ETranslations.auth_biometric_failed - : ETranslations.auth_error_passcode_incorrect, - }), + message: intl.formatMessage( + { + id: + verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts + ? ETranslations.auth_biometric_failed + : ETranslations.auth_error_passcode_incorrect, + }, + { + biometric: + verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts + ? title + : undefined, + }, + ), }, })); } @@ -196,6 +207,7 @@ const PasswordVerifyContainer = ({ setVerifyPeriodBiologyEnable, setVerifyPeriodBiologyAuthAttempts, intl, + title, ]); const onBiologyAuthenticate = useCallback(async () => { @@ -241,12 +253,20 @@ const PasswordVerifyContainer = ({ ...v, passwordVerifyStatus: { value: EPasswordVerifyStatus.ERROR, - message: intl.formatMessage({ - id: - verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts - ? ETranslations.auth_biometric_failed - : ETranslations.auth_error_passcode_incorrect, - }), + message: intl.formatMessage( + { + id: + verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts + ? ETranslations.auth_biometric_failed + : ETranslations.auth_error_passcode_incorrect, + }, + { + biometric: + verifyPeriodBiologyAuthAttempts >= biologyAuthAttempts + ? title + : undefined, + }, + ), }, })); } @@ -261,6 +281,7 @@ const PasswordVerifyContainer = ({ setPasswordAtom, setVerifyPeriodBiologyAuthAttempts, setVerifyPeriodBiologyEnable, + title, verifiedPasswordWebAuth, verifyPeriodBiologyAuthAttempts, ]); @@ -385,13 +406,12 @@ const PasswordVerifyContainer = ({ unlockPeriodPasswordArray, ], ); - return ( { setPasswordAtom((v) => ({ ...v, diff --git a/packages/kit/src/components/Password/hooks/usePasswordProtection.ts b/packages/kit/src/components/Password/hooks/usePasswordProtection.ts index dea95f471bd..00f4bd9bc31 100644 --- a/packages/kit/src/components/Password/hooks/usePasswordProtection.ts +++ b/packages/kit/src/components/Password/hooks/usePasswordProtection.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; @@ -11,8 +11,6 @@ const usePasswordProtection = (isLock: boolean) => { string[] >([]); const intl = useIntl(); - const [passwordErrorProtectionTimeOver, setPasswordErrorProtectionTimeOver] = - useState(false); const [ passwordErrorProtectionTimeMinutesSurplus, setPasswordErrorProtectionTimeMinutesSurplus, @@ -29,15 +27,25 @@ const usePasswordProtection = (isLock: boolean) => { }, setPasswordPersist, ] = usePasswordPersistAtom(); - const alertText = useMemo(() => { - if ( + + const isProtectionTime = useMemo( + () => isLock && enablePasswordErrorProtection && passwordErrorAttempts >= PASSCODE_PROTECTION_ATTEMPTS_MESSAGE_SHOW_MAX && - passwordErrorProtectionTime > Date.now() && - passwordErrorProtectionTimeMinutesSurplus > 0 && - !passwordErrorProtectionTimeOver - ) { + passwordErrorProtectionTime > Date.now(), + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + isLock, + enablePasswordErrorProtection, + passwordErrorAttempts, + passwordErrorProtectionTime, + passwordErrorProtectionTimeMinutesSurplus, + ], + ); + + const alertText = useMemo(() => { + if (isProtectionTime && passwordErrorProtectionTimeMinutesSurplus > 0) { return intl.formatMessage( { id: ETranslations.auth_passcode_cooldown, @@ -50,28 +58,26 @@ const usePasswordProtection = (isLock: boolean) => { ); } return ''; - }, [ - isLock, - enablePasswordErrorProtection, - passwordErrorAttempts, - passwordErrorProtectionTime, - passwordErrorProtectionTimeMinutesSurplus, - passwordErrorProtectionTimeOver, - intl, - ]); + }, [isProtectionTime, passwordErrorProtectionTimeMinutesSurplus, intl]); + const intervalRef = useRef>(); + + const protectionTimeRun = useCallback(() => { + if (passwordErrorProtectionTime < Date.now()) { + setPasswordErrorProtectionTimeMinutesSurplus(0); + } else { + const timeMinutes = + (passwordErrorProtectionTime - Date.now()) / 60_000 + 1; + setPasswordErrorProtectionTimeMinutesSurplus(timeMinutes); + } + }, [passwordErrorProtectionTime]); + useEffect(() => { - if (alertText) { + if (isProtectionTime) { + protectionTimeRun(); intervalRef.current = setInterval(() => { - if (passwordErrorProtectionTime < Date.now()) { - setPasswordErrorProtectionTimeOver(true); - setPasswordErrorProtectionTimeMinutesSurplus(0); - } else { - const timeMinutes = - (passwordErrorProtectionTime - Date.now()) / 60_000 + 1; - setPasswordErrorProtectionTimeMinutesSurplus(timeMinutes); - } - }, 1000 * 50); + protectionTimeRun(); + }, 1000 * 5); } else if (intervalRef.current) { clearInterval(intervalRef.current); } @@ -80,11 +86,15 @@ const usePasswordProtection = (isLock: boolean) => { clearInterval(intervalRef.current); } }; - }, [alertText, passwordErrorProtectionTime]); + }, [ + alertText, + isProtectionTime, + passwordErrorProtectionTime, + protectionTimeRun, + ]); return { unlockPeriodPasswordArray, - passwordErrorProtectionTimeOver, passwordErrorProtectionTimeMinutesSurplus, verifyPeriodBiologyAuthAttempts, verifyPeriodBiologyEnable, @@ -94,9 +104,9 @@ const usePasswordProtection = (isLock: boolean) => { setVerifyPeriodBiologyEnable, setVerifyPeriodBiologyAuthAttempts, setUnlockPeriodPasswordArray, - setPasswordErrorProtectionTimeOver, setPasswordErrorProtectionTimeMinutesSurplus, enablePasswordErrorProtection, + isProtectionTime, }; }; From 9ef34adefcac5d1732cfd4b264aaf994c177904f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=B1=B1=E6=A0=8B?= Date: Fri, 27 Dec 2024 16:31:47 +0800 Subject: [PATCH 13/13] fix: lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 王山栋 --- packages/kit-bg/src/services/ServicePassword/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kit-bg/src/services/ServicePassword/index.ts b/packages/kit-bg/src/services/ServicePassword/index.ts index 954b630afbc..ace60dc3b38 100644 --- a/packages/kit-bg/src/services/ServicePassword/index.ts +++ b/packages/kit-bg/src/services/ServicePassword/index.ts @@ -304,8 +304,6 @@ export default class ServicePassword extends ServiceBase { validatePasswordSame(password: string, newPassword: string) { ensureSensitiveTextEncoded(password); ensureSensitiveTextEncoded(newPassword); - console.log('same__password', password); - console.log('same__newPassword', newPassword); const realPassword = decodePassword({ password }); const realNewPassword = decodePassword({ password: newPassword }); if (realPassword === realNewPassword) {