From d0b1ae08d7358da7ef139fe069c7fd2bf62b9f16 Mon Sep 17 00:00:00 2001 From: jinoosss Date: Thu, 6 Mar 2025 23:18:32 +0900 Subject: [PATCH] fix: clear sensitive data in memory --- .../src/common/utils/encoding-util.ts | 12 ++++ .../src/common/utils/rand-utils.ts | 5 ++ .../molecules/web-private-key-box/index.tsx | 36 +++++++++-- .../molecules/web-seed-box/index.tsx | 19 ++++-- .../web-seed-box/web-seed-box.spec.tsx | 4 +- .../web-seed-box/web-seed-box.stories.tsx | 2 +- .../src/hooks/certify/use-create-password.ts | 16 +++-- .../web/common/use-create-password-screen.ts | 28 +++++--- .../hooks/web/use-account-import-screen.ts | 2 + .../src/hooks/web/use-wallet-create-screen.ts | 15 ++++- .../src/hooks/web/use-wallet-import-screen.ts | 22 +++++-- .../wallet-export/use-wallet-export-screen.ts | 33 ++++++---- .../popup/certify/change-password/index.tsx | 8 ++- .../popup/certify/create-password/index.tsx | 15 +++-- .../popup/wallet/approve-login/index.tsx | 2 + .../web/create-password-screen/index.tsx | 34 ++++++---- .../get-mnemonic-step.tsx | 6 +- .../wallet-export-screen/check-password.tsx | 4 +- .../pages/web/wallet-export-screen/result.tsx | 64 +++++++++++++------ 19 files changed, 236 insertions(+), 91 deletions(-) create mode 100644 packages/adena-extension/src/common/utils/rand-utils.ts diff --git a/packages/adena-extension/src/common/utils/encoding-util.ts b/packages/adena-extension/src/common/utils/encoding-util.ts index b2d3dfc44..d2c2c1bfb 100644 --- a/packages/adena-extension/src/common/utils/encoding-util.ts +++ b/packages/adena-extension/src/common/utils/encoding-util.ts @@ -1,3 +1,15 @@ export function bytesToBase64(bytes: number[]): string { return Buffer.from(bytes).toString('base64'); } + +export function base64ToBytes(base64: string): number[] { + return Array.from(Buffer.from(base64, 'base64')); +} + +export function stringFromBase64(base64: string): string { + return Buffer.from(base64, 'base64').toString('utf-8'); +} + +export function stringToBase64(str: string): string { + return Buffer.from(str).toString('base64'); +} diff --git a/packages/adena-extension/src/common/utils/rand-utils.ts b/packages/adena-extension/src/common/utils/rand-utils.ts new file mode 100644 index 000000000..f9c525dad --- /dev/null +++ b/packages/adena-extension/src/common/utils/rand-utils.ts @@ -0,0 +1,5 @@ +import crypto from 'crypto'; + +export function generateRandomHex(): string { + return crypto.randomBytes(32).toString('hex'); +} diff --git a/packages/adena-extension/src/components/molecules/web-private-key-box/index.tsx b/packages/adena-extension/src/components/molecules/web-private-key-box/index.tsx index f7461a5c4..7a80e9432 100644 --- a/packages/adena-extension/src/components/molecules/web-private-key-box/index.tsx +++ b/packages/adena-extension/src/components/molecules/web-private-key-box/index.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { WebTextarea } from '@components/atoms/web-textarea'; +import { useEffect, useState } from 'react'; import styled from 'styled-components'; +import { stringFromBase64 } from '@common/utils/encoding-util'; +import { generateRandomHex } from '@common/utils/rand-utils'; import { View } from '../../atoms'; interface WebPrivateKeyBoxProps { @@ -11,7 +13,7 @@ interface WebPrivateKeyBoxProps { error?: boolean; } -const StyledContainer = styled(View) <{ showBlur: boolean }>` +const StyledContainer = styled(View)<{ showBlur: boolean }>` position: relative; overflow: hidden; height: 80px; @@ -27,17 +29,41 @@ const StyledBlurScreen = styled(View)` border-radius: 10px; `; -export const WebPrivateKeyBox = ({ privateKey, showBlur = true, readOnly = false, error = false }: WebPrivateKeyBoxProps): JSX.Element => { +export const WebPrivateKeyBox = ({ + privateKey, + showBlur = true, + readOnly = false, + error = false, +}: WebPrivateKeyBoxProps): JSX.Element => { + const randomHexString = generateRandomHex(); + const [displayPrivateKey, setDisplayPrivateKey] = useState(randomHexString); + + useEffect(() => { + if (!showBlur) { + setDisplayPrivateKey(stringFromBase64(privateKey)); + return; + } + + setDisplayPrivateKey(randomHexString); + }, [showBlur, privateKey]); + + useEffect(() => { + return () => { + setDisplayPrivateKey(''); + }; + }, []); return ( { return; }} + onChange={(): void => { + return; + }} spellCheck={false} /> {showBlur && } diff --git a/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx b/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx index 07c9653ba..ebd0a83dc 100644 --- a/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx +++ b/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx @@ -1,8 +1,9 @@ -import { useEffect, useRef } from 'react'; +import { stringFromBase64 } from '@common/utils/encoding-util'; +import { useEffect, useMemo, useRef } from 'react'; import styled from 'styled-components'; interface WebSeedBoxProps { - seeds: string[]; + seedString: string; showBlur?: boolean; } @@ -31,12 +32,20 @@ const BOX_WIDTH = 185; const BOX_HEIGHT = 40; const NUMBER_BOX_WIDTH = 40; -export const WebSeedBox = ({ seeds, showBlur = true }: WebSeedBoxProps): JSX.Element => { +export const WebSeedBox = ({ seedString, showBlur = true }: WebSeedBoxProps): JSX.Element => { const canvasRefs = useRef<(HTMLCanvasElement | null)[]>([]); + const seedSize = useMemo(() => { + return `${stringFromBase64(seedString)}`.split(' ').length; + }, [seedString]); + useEffect(() => { + if (seedSize === 0) return; + const dpr = window?.devicePixelRatio || 1; + const seeds = `${stringFromBase64(seedString)}`.split(' '); + seeds.forEach((seed, index) => { const canvas = canvasRefs.current[index]; if (!canvas) return; @@ -87,11 +96,11 @@ export const WebSeedBox = ({ seeds, showBlur = true }: WebSeedBoxProps): JSX.Ele ctx.scale(dpr, dpr); }); - }, [seeds, showBlur, window?.devicePixelRatio]); + }, [seedSize, seedString, showBlur, window?.devicePixelRatio]); return ( - {seeds.map((_, index) => ( + {Array.from({ length: seedSize }).map((_, index) => ( (canvasRefs.current[index] = el)} diff --git a/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.spec.tsx b/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.spec.tsx index 51481760b..740d11b4e 100644 --- a/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.spec.tsx +++ b/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.spec.tsx @@ -6,8 +6,6 @@ import theme from '@styles/theme'; import { GlobalWebStyle } from '@styles/global-style'; import { WebSeedBox } from '.'; -const seeds = ['seed', 'seed', 'seed', 'seed']; - describe('WebSeedBox Component', () => { it('WebSeedBox render', () => { @@ -15,7 +13,7 @@ describe('WebSeedBox Component', () => { - + , ); diff --git a/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.stories.tsx b/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.stories.tsx index 39fde0dfd..9b7085e8e 100644 --- a/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.stories.tsx +++ b/packages/adena-extension/src/components/molecules/web-seed-box/web-seed-box.stories.tsx @@ -8,7 +8,7 @@ export default { export const Default: StoryObj = { args: { - seeds: ['seed', 'seed', 'seed', 'seed'], + seedString: '', showBlur: true, }, }; \ No newline at end of file diff --git a/packages/adena-extension/src/hooks/certify/use-create-password.ts b/packages/adena-extension/src/hooks/certify/use-create-password.ts index ddf99d33d..61314448f 100644 --- a/packages/adena-extension/src/hooks/certify/use-create-password.ts +++ b/packages/adena-extension/src/hooks/certify/use-create-password.ts @@ -1,15 +1,15 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { PasswordValidationError } from '@common/errors'; -import { useAdenaContext } from '@hooks/use-context'; +import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils'; import { validateEmptyPassword, validateNotMatchConfirmPassword, validatePasswordComplexity, } from '@common/validation'; -import { AdenaWallet } from 'adena-module'; import useAppNavigate from '@hooks/use-app-navigate'; -import { CreateAccountState, GoogleState, LedgerState, SeedState, RoutePath } from '@types'; -import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils'; +import { useAdenaContext } from '@hooks/use-context'; +import { CreateAccountState, GoogleState, LedgerState, RoutePath, SeedState } from '@types'; +import { AdenaWallet } from 'adena-module'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; export type UseCreatePasswordReturn = { pwdState: { @@ -169,6 +169,7 @@ export const useCreatePassword = (): UseCreatePasswordReturn => { }); await accountService.changeCurrentAccount(wallet.currentAccount); await walletService.changePassword(pwd); + clearPassword(); } catch (error) { console.error(error); return 'FAIL'; @@ -183,6 +184,7 @@ export const useCreatePassword = (): UseCreatePasswordReturn => { const wallet = await AdenaWallet.createByWeb3Auth(googleState.privateKey); await accountService.changeCurrentAccount(wallet.currentAccount); await walletService.saveWallet(wallet, pwd); + clearPassword(); } catch (error) { console.error(error); return 'FAIL'; @@ -213,6 +215,10 @@ export const useCreatePassword = (): UseCreatePasswordReturn => { setActivated(true); }; + const clearPassword = (): void => { + setInputs({ pwd: '', confirmPwd: '' }); + }; + return { pwdState: { value: pwd, diff --git a/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts b/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts index 08ecc4d54..834f6eae5 100644 --- a/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts +++ b/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts @@ -1,8 +1,8 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AdenaWallet } from 'adena-module'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { RoutePath } from '@types'; import { PasswordValidationError } from '@common/errors'; +import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils'; import { validateEmptyPassword, validateNotMatchConfirmPassword, @@ -13,7 +13,7 @@ import { useAdenaContext } from '@hooks/use-context'; import useIndicatorStep, { UseIndicatorStepReturn, } from '@hooks/wallet/broadcast-transaction/use-indicator-step'; -import { evaluatePassword, EvaluatePasswordResult } from '@common/utils/password-utils'; +import { RoutePath } from '@types'; export type UseCreatePasswordScreenReturn = { passwordState: { @@ -41,6 +41,7 @@ export type UseCreatePasswordScreenReturn = { }; indicatorInfo: UseIndicatorStepReturn; onKeyDown: (e: React.KeyboardEvent) => void; + clearPassword: () => void; }; export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { @@ -126,11 +127,16 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { return false; }; + const clearPassword = (): void => { + setInputs({ password: '', confirmPassword: '' }); + }; + const _saveWalletByPassword = async (password: string): Promise => { const { serializedWallet } = params; const wallet = await AdenaWallet.deserialize(serializedWallet, ''); await walletService.saveWallet(wallet, password); await accountService.changeCurrentAccount(wallet.currentAccount); + await setInputs({ password: '', confirmPassword: '' }); }; const onChangePassword = useCallback( @@ -157,14 +163,15 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { }; const onKeyDownInput = useCallback( - () => (e: React.KeyboardEvent): void => { - if (e.key === 'Enter') { - if (disabledCreateButton) { - return; + () => + (e: React.KeyboardEvent): void => { + if (e.key === 'Enter') { + if (disabledCreateButton) { + return; + } + onClickCreateButton(); } - onClickCreateButton(); - } - }, + }, [disabledCreateButton, onClickCreateButton], ); @@ -207,5 +214,6 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { disabled: disabledCreateButton, }, onKeyDown: onKeyDownInput, + clearPassword, }; }; diff --git a/packages/adena-extension/src/hooks/web/use-account-import-screen.ts b/packages/adena-extension/src/hooks/web/use-account-import-screen.ts index d866f751a..0fed6c540 100644 --- a/packages/adena-extension/src/hooks/web/use-account-import-screen.ts +++ b/packages/adena-extension/src/hooks/web/use-account-import-screen.ts @@ -189,6 +189,7 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor const { account, keyring } = result; const resultWallet = await addAccountWith(wallet.clone(), keyring, account); await updateWallet(resultWallet); + setInputValue(''); }).then(() => navigate(RoutePath.WebAccountAddedComplete)); return; } else { @@ -224,6 +225,7 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor } await updateWallet(resultWallet); + setInputValue(''); navigate(RoutePath.WebAccountAddedComplete); } }, [ diff --git a/packages/adena-extension/src/hooks/web/use-wallet-create-screen.ts b/packages/adena-extension/src/hooks/web/use-wallet-create-screen.ts index 1f4c35d44..ac4ff7d45 100644 --- a/packages/adena-extension/src/hooks/web/use-wallet-create-screen.ts +++ b/packages/adena-extension/src/hooks/web/use-wallet-create-screen.ts @@ -1,6 +1,7 @@ import { AdenaWallet, HDWalletKeyring, SeedAccount } from 'adena-module'; import { useCallback, useMemo, useState } from 'react'; +import { stringFromBase64, stringToBase64 } from '@common/utils/encoding-util'; import useAppNavigate from '@hooks/use-app-navigate'; import { useWalletContext } from '@hooks/use-context'; import { useCurrentAccount } from '@hooks/use-current-account'; @@ -42,7 +43,9 @@ const useWalletCreateScreen = (): UseWalletCreateReturn => { hasQuestionnaire: true, }); - const seeds = useMemo(() => AdenaWallet.generateMnemonic(), []); + const seeds = useMemo(() => { + return stringToBase64(AdenaWallet.generateMnemonic()); + }, []); const onClickGoBack = useCallback(() => { if (step === 'INIT') { @@ -65,7 +68,10 @@ const useWalletCreateScreen = (): UseWalletCreateReturn => { } } else if (step === 'GET_SEED_PHRASE') { if (wallet) { - const keyring = await HDWalletKeyring.fromMnemonic(seeds); + let rawSeeds = stringFromBase64(seeds); + const keyring = await HDWalletKeyring.fromMnemonic(rawSeeds); + rawSeeds = ''; + const account = await SeedAccount.createBy( keyring, `Account ${wallet.lastAccountIndex + 1}`, @@ -85,7 +91,10 @@ const useWalletCreateScreen = (): UseWalletCreateReturn => { await updateWallet(clone); navigate(RoutePath.WebAccountAddedComplete); } else { - const createdWallet = await AdenaWallet.createByMnemonic(seeds); + let rawSeeds = stringFromBase64(seeds); + const createdWallet = await AdenaWallet.createByMnemonic(rawSeeds); + rawSeeds = ''; + const serializedWallet = await createdWallet.serialize(''); navigate(RoutePath.WebCreatePassword, { diff --git a/packages/adena-extension/src/hooks/web/use-wallet-import-screen.ts b/packages/adena-extension/src/hooks/web/use-wallet-import-screen.ts index b65012d75..1194efd65 100644 --- a/packages/adena-extension/src/hooks/web/use-wallet-import-screen.ts +++ b/packages/adena-extension/src/hooks/web/use-wallet-import-screen.ts @@ -15,6 +15,7 @@ import useIndicatorStep, { } from '@hooks/wallet/broadcast-transaction/use-indicator-step'; import { ImportWalletType, RoutePath } from '@types'; +import { stringFromBase64, stringToBase64 } from '@common/utils/encoding-util'; import useQuestionnaire from './use-questionnaire'; export type UseWalletImportReturn = { @@ -74,17 +75,21 @@ const useWalletImportScreen = (): UseWalletImportReturn => { const [inputType, setInputType] = useState('12seeds'); const [errMsg, setErrMsg] = useState(''); + const decodedInputValue = useMemo(() => { + return stringFromBase64(inputValue); + }, [inputValue]); + const updateInputValue = useCallback((value: string) => { - setInputValue(value); + setInputValue(stringToBase64(value)); setErrMsg(''); }, []); const isValidForm = useMemo(() => { let validInput = false; if (inputType === '12seeds') { - validInput = isSeedPhraseString(inputValue, 12); + validInput = isSeedPhraseString(decodedInputValue, 12); } else if (inputType === '24seeds') { - validInput = isSeedPhraseString(inputValue, 24); + validInput = isSeedPhraseString(decodedInputValue, 24); } else { validInput = !!inputValue; } @@ -132,15 +137,18 @@ const useWalletImportScreen = (): UseWalletImportReturn => { const isSeed = inputType === '12seeds' || inputType === '24seeds'; if (isSeed) { - if (!isValidMnemonic(inputValue)) { + if (!isValidMnemonic(decodedInputValue)) { setErrMsg('Invalid seed phrase'); return; } setStep('LOADING'); - serializedWallet = await createSerializedWalletWithMnemonic(inputValue); + serializedWallet = await createSerializedWalletWithMnemonic(decodedInputValue); + setInputValue(''); } else { - const keyring = await PrivateKeyKeyring.fromPrivateKeyStr(inputValue).catch(() => null); + let keyring = await PrivateKeyKeyring.fromPrivateKeyStr(decodedInputValue).catch( + () => null, + ); if (keyring === null) { setErrMsg('Invalid private key'); return; @@ -148,6 +156,8 @@ const useWalletImportScreen = (): UseWalletImportReturn => { setStep('LOADING'); serializedWallet = await createSerializedWalletWithPrivateKeyKeyring(keyring); + keyring = null; + setInputValue(''); } if (!serializedWallet) { diff --git a/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts b/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts index f518d6963..4f70aca31 100644 --- a/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts +++ b/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts @@ -1,4 +1,4 @@ -import { Account } from 'adena-module'; +import { Account, Wallet } from 'adena-module'; import { useCallback, useEffect, useState } from 'react'; import { @@ -6,6 +6,7 @@ import { WALLET_EXPORT_TYPE_STORAGE_KEY, } from '@common/constants/storage.constant'; import { AdenaStorage } from '@common/storage'; +import { encryptWalletPassword } from '@common/utils/crypto-utils'; import useAppNavigate from '@hooks/use-app-navigate'; import { useAdenaContext, useWalletContext } from '@hooks/use-context'; import { useCurrentAccount } from '@hooks/use-current-account'; @@ -20,7 +21,7 @@ export type UseWalletExportReturn = { currentAccount: Account | null; exportType: ExportType; walletExportState: WalletExportStateType; - exportData: string | null; + exportData: Wallet | null; indicatorInfo: UseIndicatorStepReturn; initWalletExport: () => void; backStep: () => void; @@ -69,7 +70,7 @@ const useWalletExportScreen = (): UseWalletExportReturn => { const [walletExportState, setWalletExportState] = useState( params?.doneQuestionnaire ? 'CHECK_PASSWORD' : 'INIT', ); - const [exportData, setExportData] = useState(null); + const [exportData, setExportData] = useState(null); const [exportAccountId, setExportAccountId] = useState(null); const indicatorInfo = useIndicatorStep({ stepMap: walletExportStepNo, @@ -78,9 +79,9 @@ const useWalletExportScreen = (): UseWalletExportReturn => { }); const { data: account = null } = useQuery( - ['walletExportScreen/account', exportData, wallet, exportAccountId, currentAccount], + ['walletExportScreen/account', exportType, wallet, exportAccountId, currentAccount], async () => { - if (exportData === 'SEED_PHRASE') { + if (exportType === 'SEED_PHRASE') { return currentAccount; } return wallet?.accounts.find((account) => account.id === exportAccountId) || currentAccount; @@ -133,9 +134,10 @@ const useWalletExportScreen = (): UseWalletExportReturn => { if (exportType === 'NONE') { return false; } + return walletService .loadWalletPassword() - .then((storedPassword) => storedPassword === password) + .then((storedPassword) => storedPassword === encryptWalletPassword(password)) .catch(() => false); }, [exportType, walletService], @@ -146,15 +148,24 @@ const useWalletExportScreen = (): UseWalletExportReturn => { if (exportType === 'NONE' || !account) { return; } - const wallet = await walletService.loadWalletWithPassword(password); + const encryptedWalletPassword = encryptWalletPassword(password); + const wallet = await walletService + .loadWalletWithPassword(encryptedWalletPassword) + .catch((e) => { + console.warn(e); + return null; + }); + if (!wallet) { + return; + } + const instance = wallet.clone(); instance.currentAccountId = account.id; + if (exportType === 'PRIVATE_KEY') { - const privateKey = await instance.getPrivateKeyStr(); - setExportData(privateKey); + setExportData(instance); } else { - const seedPhrase = instance.getMnemonic(); - setExportData(seedPhrase); + setExportData(instance); } setWalletExportState('RESULT'); }, diff --git a/packages/adena-extension/src/pages/popup/certify/change-password/index.tsx b/packages/adena-extension/src/pages/popup/certify/change-password/index.tsx index ef7905b92..aa8892344 100644 --- a/packages/adena-extension/src/pages/popup/certify/change-password/index.tsx +++ b/packages/adena-extension/src/pages/popup/certify/change-password/index.tsx @@ -1,12 +1,11 @@ -import React from 'react'; import styled from 'styled-components'; -import { Text, DefaultInput, ErrorText } from '@components/atoms'; +import { DefaultInput, ErrorText, Text } from '@components/atoms'; import { CancelAndConfirmButton } from '@components/molecules'; +import { PasswordInput } from '@components/atoms/password-input'; import { useChangePassword } from '@hooks/certify/use-change-password'; import mixins from '@styles/mixins'; -import { PasswordInput } from '@components/atoms/password-input'; const Wrapper = styled.main` ${mixins.flex({ align: 'flex-start', justify: 'flex-start' })}; @@ -47,6 +46,7 @@ export const ChangePassword = (): JSX.Element => { type='password' name='currPwd' placeholder='Current Password' + value={currPwdState.value} onChange={currPwdState.onChange} onKeyDown={onKeyDown} error={currPwdState.error} @@ -56,6 +56,7 @@ export const ChangePassword = (): JSX.Element => { type='password' name='newPwd' placeholder='New Password' + value={newPwdState.value} onChange={newPwdState.onChange} onKeyDown={onKeyDown} error={newPwdState.error} @@ -65,6 +66,7 @@ export const ChangePassword = (): JSX.Element => { type='password' name='confirmPwd' placeholder='Confirm New Password' + value={confirmPwdState.value} onChange={confirmPwdState.onChange} onKeyDown={onKeyDown} error={confirmPwdState.error} diff --git a/packages/adena-extension/src/pages/popup/certify/create-password/index.tsx b/packages/adena-extension/src/pages/popup/certify/create-password/index.tsx index 232bf9cf7..b5ad3568f 100644 --- a/packages/adena-extension/src/pages/popup/certify/create-password/index.tsx +++ b/packages/adena-extension/src/pages/popup/certify/create-password/index.tsx @@ -1,14 +1,13 @@ -import React from 'react'; -import styled, { CSSProp, css } from 'styled-components'; +import styled, { css, CSSProp } from 'styled-components'; -import { Text, DefaultInput, ErrorText, Button, View } from '@components/atoms'; -import { TitleWithDesc, TermsCheckbox } from '@components/molecules'; +import { Button, DefaultInput, ErrorText, Text, View } from '@components/atoms'; +import { PasswordInput } from '@components/atoms/password-input'; +import { TermsCheckbox, TitleWithDesc } from '@components/molecules'; import { useCreatePassword } from '@hooks/certify/use-create-password'; -import mixins from '@styles/mixins'; import useAppNavigate from '@hooks/use-app-navigate'; -import { RoutePath } from '@types'; import useLink from '@hooks/use-link'; -import { PasswordInput } from '@components/atoms/password-input'; +import mixins from '@styles/mixins'; +import { RoutePath } from '@types'; const text = { title: 'Create\na Password', @@ -68,6 +67,7 @@ export const CreatePassword = (): JSX.Element => { name='pwd' placeholder='Password' evaluationResult={pwdState.evaluationResult} + value={pwdState.value} onChange={pwdState.onChange} onKeyDown={onKeyDown} error={pwdState.error} @@ -77,6 +77,7 @@ export const CreatePassword = (): JSX.Element => { type='password' name='confirmPwd' placeholder='Confirm Password' + value={confirmPwdState.value} onChange={confirmPwdState.onChange} onKeyDown={onKeyDown} error={confirmPwdState.error} diff --git a/packages/adena-extension/src/pages/popup/wallet/approve-login/index.tsx b/packages/adena-extension/src/pages/popup/wallet/approve-login/index.tsx index 286bea0dc..8d7628a20 100644 --- a/packages/adena-extension/src/pages/popup/wallet/approve-login/index.tsx +++ b/packages/adena-extension/src/pages/popup/wallet/approve-login/index.tsx @@ -92,6 +92,7 @@ export const ApproveLogin = (): JSX.Element => { if (equalPassword) { await walletService.updatePassword(encryptedPassword); await initWallet(); + setPassword(''); setState('FINISH'); } } catch (error) { @@ -166,6 +167,7 @@ export const ApproveLogin = (): JSX.Element => { { errorMessage, buttonState, onKeyDown, + clearPassword, } = useCreatePasswordScreen(); const { goBack } = useAppNavigate(); @@ -69,6 +70,15 @@ const CreatePasswordScreen = (): JSX.Element => { openLink(ADENA_TERMS_PAGE); }, [openLink]); + const onClickNext = (): void => { + if (buttonState.disabled) { + return; + } + + clearPassword(); + buttonState.onClick(); + }; + return ( { name='password' placeholder='Password' style={{ width: '100%', flexShrink: 0 }} + value={passwordState.value} onChange={passwordState.onChange} onKeyDown={onKeyDown} error={passwordState.error} @@ -111,6 +122,7 @@ const CreatePasswordScreen = (): JSX.Element => { name='confirmPassword' placeholder='Confirm Password' style={{ width: '100%' }} + value={confirmPasswordState.value} onChange={confirmPasswordState.onChange} onKeyDown={onKeyDown} error={confirmPasswordState.error} @@ -137,7 +149,7 @@ const CreatePasswordScreen = (): JSX.Element => { figure='primary' size='small' disabled={buttonState.disabled} - onClick={buttonState.onClick} + onClick={onClickNext} tabIndex={5} text='Save' rightIcon='chevronRight' diff --git a/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx b/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx index 4f5691934..1729738f1 100644 --- a/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx +++ b/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx @@ -3,6 +3,7 @@ import styled, { useTheme } from 'styled-components'; import IconWarning from '@assets/web/warning.svg'; +import { stringFromBase64 } from '@common/utils/encoding-util'; import { Row, View, WebButton, WebCheckBox, WebImg, WebText } from '@components/atoms'; import { WebCopyButton } from '@components/atoms/web-copy-button'; import { WebHoldButton } from '@components/atoms/web-hold-button'; @@ -47,6 +48,7 @@ const GetMnemonicStep = ({ const onCopy = (): void => { setCopied(true); + navigator.clipboard.writeText(stringFromBase64(seeds)); }; return ( @@ -71,13 +73,13 @@ const GetMnemonicStep = ({ - + {ableToReveal ? ( <> setShowBlur(!response)} /> - + = ({ } const checkedPassword = await checkPassword(password); if (checkedPassword) { + setPassword(''); moveExport(password); } else { setErrorMessage('Invalid password'); @@ -134,6 +135,7 @@ const WalletExportCheckPassword: React.FC = ({ name='password' width='100%' placeholder='Password' + value={password} onChange={onChangePassword} error={hasError} /> diff --git a/packages/adena-extension/src/pages/web/wallet-export-screen/result.tsx b/packages/adena-extension/src/pages/web/wallet-export-screen/result.tsx index 3fc9535ab..e278ad83e 100644 --- a/packages/adena-extension/src/pages/web/wallet-export-screen/result.tsx +++ b/packages/adena-extension/src/pages/web/wallet-export-screen/result.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled, { useTheme } from 'styled-components'; import IconWarning from '@assets/web/warning.svg'; import { WALLET_EXPORT_TYPE_STORAGE_KEY } from '@common/constants/storage.constant'; import { AdenaStorage } from '@common/storage'; +import { stringFromBase64, stringToBase64 } from '@common/utils/encoding-util'; import { Row, View, WebButton, WebImg, WebText } from '@components/atoms'; import { WebCopyButton } from '@components/atoms/web-copy-button'; import { WebHoldButton } from '@components/atoms/web-hold-button'; @@ -11,6 +12,7 @@ import { WebSeedBox } from '@components/molecules'; import { WebPrivateKeyBox } from '@components/molecules/web-private-key-box'; import { ExportType } from '@hooks/web/wallet-export/use-wallet-export-screen'; import { getTheme } from '@styles/theme'; +import { Wallet } from 'adena-module'; const StyledContainer = styled(View)` width: 100%; @@ -41,7 +43,7 @@ const StyledInputBox = styled(View)` interface WalletExportResultProps { exportType: ExportType; - exportData: string | null; + exportData: Wallet | null; } const WalletExportResult: React.FC = ({ exportType, exportData }) => { @@ -49,6 +51,8 @@ const WalletExportResult: React.FC = ({ exportType, exp const [blur, setBlur] = useState(true); const [initializedDone, setInitializedDone] = useState(false); const [copied, setCopied] = useState(false); + const [seeds, setSeeds] = useState(''); + const [privateKey, setPrivateKey] = useState(''); const title = useMemo(() => { if (exportType === 'PRIVATE_KEY') { @@ -71,20 +75,6 @@ const WalletExportResult: React.FC = ({ exportType, exp return 'You have copied sensitive info. Make sure you do not paste it in public or shared environments, and clear your clipboard as soon as you’ve used it.'; }, [copied]); - const seeds = useMemo((): string[] => { - if (exportType !== 'SEED_PHRASE' || !exportData) { - return []; - } - return exportData.split(' '); - }, [exportType, exportData]); - - const privateKey = useMemo((): string => { - if (exportType !== 'PRIVATE_KEY' || !exportData) { - return ''; - } - return exportData; - }, [exportType, exportData]); - const onMouseDownDone = (): void => { setInitializedDone(true); }; @@ -107,8 +97,46 @@ const WalletExportResult: React.FC = ({ exportType, exp const onCopy = (): void => { setCopied(true); + + if (exportType === 'SEED_PHRASE') { + navigator.clipboard.writeText(stringFromBase64(seeds)); + } else { + exportData?.getPrivateKeyStr().then((result) => { + navigator.clipboard.writeText(result); + }); + } }; + useEffect(() => { + if (!exportType || !exportData) { + return; + } + + switch (exportType) { + case 'SEED_PHRASE': { + let mnemonicStr = exportData.getMnemonic(); + + if (mnemonicStr) { + mnemonicStr = mnemonicStr.trim(); + setSeeds(stringToBase64(mnemonicStr)); + mnemonicStr = ''; + } + break; + } + case 'PRIVATE_KEY': { + exportData.getPrivateKeyStr().then((result) => { + setPrivateKey(stringToBase64(result)); + }); + break; + } + } + + return () => { + setSeeds(''); + setPrivateKey(''); + }; + }, [exportType, exportData]); + return ( @@ -131,13 +159,13 @@ const WalletExportResult: React.FC = ({ exportType, exp - {exportType === 'SEED_PHRASE' && } + {exportType === 'SEED_PHRASE' && } {exportType === 'PRIVATE_KEY' && ( )} - +