Skip to content

Commit 6f838b9

Browse files
authored
fix: [LW-11855] (#1552)
* fix: lw-11855 * feat(nami): add dedicated password component
1 parent 74fd93b commit 6f838b9

File tree

38 files changed

+467
-447
lines changed

38 files changed

+467
-447
lines changed

apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/WalletAccounts.tsx

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
EnableAccountConfirmWithHW,
1313
EnableAccountConfirmWithHWState,
1414
EnableAccountPasswordPrompt,
15-
useDialogWithData
15+
useDialogWithData,
16+
useSecrets
1617
} from '@lace/core';
1718
import { useWalletStore } from '@src/stores';
1819
import { useWalletManager } from '@hooks';
@@ -58,6 +59,7 @@ export const WalletAccounts = ({ isPopup, onBack }: { isPopup: boolean; onBack:
5859
account: activeAccount
5960
}
6061
} = cardanoWallet;
62+
const { clearSecrets, password } = useSecrets();
6163

6264
const editAccountDrawer = useDialogWithData<ProfileDropdown.AccountData | undefined>();
6365
const disableAccountConfirmation = useDialogWithData<ProfileDropdown.AccountData | undefined>();
@@ -117,9 +119,10 @@ export const WalletAccounts = ({ isPopup, onBack }: { isPopup: boolean; onBack:
117119
accountIndex
118120
});
119121
const accountName = accountsData.find((acc) => acc.accountNumber === accountIndex)?.label;
122+
clearSecrets();
120123
closeDropdownAndShowAccountActivated(accountName);
121124
},
122-
[wallet.walletId, activateWallet, accountsData, closeDropdownAndShowAccountActivated, analytics]
125+
[wallet.walletId, activateWallet, accountsData, closeDropdownAndShowAccountActivated, analytics, clearSecrets]
123126
);
124127

125128
const editAccount = useCallback(
@@ -198,29 +201,30 @@ export const WalletAccounts = ({ isPopup, onBack }: { isPopup: boolean; onBack:
198201
[wallet.type, enableAccountPasswordDialog, enableAccountHWSigningDialog, unlockHWAccount]
199202
);
200203

201-
const unlockInMemoryWalletAccountWithPassword = useCallback(
202-
async (passphrase: Uint8Array) => {
203-
const { accountIndex } = enableAccountPasswordDialog.data;
204-
const name = defaultAccountName(accountIndex);
205-
try {
206-
await addAccount({
207-
wallet,
208-
accountIndex,
209-
passphrase,
210-
metadata: { name: defaultAccountName(accountIndex) }
211-
});
212-
analytics.sendEventToPostHog(PostHogAction.MultiWalletEnableAccount, {
213-
// eslint-disable-next-line camelcase
214-
$set: { wallet_accounts_quantity: await getWalletAccountsQtyString(walletRepository) }
215-
});
216-
enableAccountPasswordDialog.hide();
217-
closeDropdownAndShowAccountActivated(name);
218-
} catch {
219-
enableAccountPasswordDialog.setData({ ...enableAccountPasswordDialog.data, wasPasswordIncorrect: true });
220-
}
221-
},
222-
[wallet, addAccount, enableAccountPasswordDialog, closeDropdownAndShowAccountActivated, analytics, walletRepository]
223-
);
204+
const unlockInMemoryWalletAccountWithPassword = async () => {
205+
const { accountIndex } = enableAccountPasswordDialog.data;
206+
const name = defaultAccountName(accountIndex);
207+
const passphrase = Buffer.from(password?.value);
208+
try {
209+
await addAccount({
210+
wallet,
211+
accountIndex,
212+
passphrase,
213+
metadata: { name: defaultAccountName(accountIndex) }
214+
});
215+
analytics.sendEventToPostHog(PostHogAction.MultiWalletEnableAccount, {
216+
// eslint-disable-next-line camelcase
217+
$set: { wallet_accounts_quantity: await getWalletAccountsQtyString(walletRepository) }
218+
});
219+
enableAccountPasswordDialog.hide();
220+
closeDropdownAndShowAccountActivated(name);
221+
} catch {
222+
enableAccountPasswordDialog.setData({ ...enableAccountPasswordDialog.data, wasPasswordIncorrect: true });
223+
} finally {
224+
passphrase.fill(0);
225+
clearSecrets();
226+
}
227+
};
224228

225229
const lockAccount = useCallback(async () => {
226230
await walletRepository.removeAccount({

apps/browser-extension-wallet/src/components/MigrationContainer/MigrationContainer.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Lock } from '@src/views/browser-view/components/Lock';
1010
import { MainLoader } from '@components/MainLoader';
1111
import { FailedMigration } from './FailedMigration';
1212
import { MigrationInProgress } from './MigrationInProgress';
13-
import { OnPasswordChange } from '@lace/core';
13+
import { OnPasswordChange, useSecrets } from '@lace/core';
1414

1515
export interface MigrationContainerProps {
1616
children: React.ReactNode;
@@ -33,22 +33,22 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp
3333
const [renderState, setRenderState] = useState<RenderState>(INITIAL_RENDER_STATE);
3434

3535
const [isVerifyingPassword, setIsVerifyingPassword] = useState(false);
36-
const [password, setPassword] = useState<string>();
36+
const { password, setPassword, clearSecrets } = useSecrets();
3737
const [isValidPassword, setIsValidPassword] = useState(true);
3838

3939
const migrate = useCallback(async () => {
4040
setRenderState(INITIAL_RENDER_STATE);
41-
if (appMode === APP_MODE_POPUP) await applyMigrations(migrationState, password);
41+
if (appMode === APP_MODE_POPUP) await applyMigrations(migrationState, password.value);
4242
}, [migrationState, password, appMode]);
4343

4444
const handlePasswordChange = useCallback<OnPasswordChange>(
4545
(target) => {
4646
if (!isValidPassword) {
4747
setIsValidPassword(true);
4848
}
49-
setPassword(target.value);
49+
setPassword(target);
5050
},
51-
[isValidPassword]
51+
[isValidPassword, setPassword]
5252
);
5353

5454
const lockAndMigrate = useCallback(async () => {
@@ -66,11 +66,14 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp
6666
await unlockWallet();
6767
setIsValidPassword(true);
6868
await migrate();
69+
clearSecrets();
6970
} catch {
7071
setIsValidPassword(false);
72+
} finally {
73+
clearSecrets();
7174
}
7275
setIsVerifyingPassword(false);
73-
}, [unlockWallet, migrate]);
76+
}, [unlockWallet, migrate, clearSecrets]);
7477

7578
useEffect(() => {
7679
// Load initial migrationState value
@@ -143,7 +146,7 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp
143146
isLoading={isVerifyingPassword}
144147
onUnlock={onUnlock}
145148
passwordInput={{ handleChange: handlePasswordChange, invalidPass: !isValidPassword }}
146-
unlockButtonDisabled={password === ''}
149+
unlockButtonDisabled={!password.value}
147150
// TODO: show forgot password here too. Use same logic as in ResetDataError on click
148151
showForgotPassword={false}
149152
/>

apps/browser-extension-wallet/src/features/dapp/components/SignData.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Spin } from 'antd';
33
import { Wallet } from '@lace/cardano';
44
import { useTranslation } from 'react-i18next';
55
import { Button } from '@lace/common';
6-
import { OnPasswordChange, Password } from '@lace/core';
6+
import { Password, useSecrets } from '@lace/core';
77
import { useRedirection } from '@hooks';
88
import { dAppRoutePaths } from '@routes';
99
import { Layout } from './Layout';
@@ -20,15 +20,17 @@ export const SignData = (): React.ReactElement => {
2020
const redirectToSignFailure = useRedirection(dAppRoutePaths.dappDataSignFailure);
2121
const redirectToSignSuccess = useRedirection(dAppRoutePaths.dappDataSignSuccess);
2222
const [isLoading, setIsLoading] = useState(false);
23-
const [password, setPassword] = useState<string>();
2423
const [validPassword, setValidPassword] = useState<boolean>();
24+
const { password, setPassword, clearSecrets } = useSecrets();
2525

2626
const onConfirm = useCallback(async () => {
2727
setIsLoading(true);
28+
const passphrase = Buffer.from(password.value, 'utf8');
2829
try {
29-
const passphrase = Buffer.from(password, 'utf8');
3030
await request.sign(passphrase, { willRetryOnFailure: true });
3131
setValidPassword(true);
32+
clearSecrets();
33+
passphrase.fill(0);
3234
redirectToSignSuccess();
3335
} catch (error) {
3436
if (error instanceof Wallet.KeyManagement.errors.AuthenticationError) {
@@ -37,11 +39,11 @@ export const SignData = (): React.ReactElement => {
3739
redirectToSignFailure();
3840
}
3941
} finally {
42+
passphrase.fill(0);
43+
clearSecrets();
4044
setIsLoading(false);
4145
}
42-
}, [password, redirectToSignFailure, redirectToSignSuccess, request]);
43-
44-
const handleChange: OnPasswordChange = (target) => setPassword(target.value);
46+
}, [password, redirectToSignFailure, redirectToSignSuccess, request, clearSecrets]);
4547

4648
const confirmIsDisabled = useMemo(() => {
4749
if (request.walletType !== WalletType.InMemory) return false;
@@ -68,7 +70,7 @@ export const SignData = (): React.ReactElement => {
6870
{t('browserView.transaction.send.enterWalletPasswordToConfirmTransaction')}
6971
</h5>
7072
<Password
71-
onChange={handleChange}
73+
onChange={setPassword}
7274
onSubmit={handleSubmit}
7375
error={validPassword === false}
7476
errorMessage={t('browserView.transaction.send.error.invalidPassword')}

apps/browser-extension-wallet/src/features/dapp/components/SignTransaction.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Spin } from 'antd';
33
import { Wallet } from '@lace/cardano';
44
import { useTranslation } from 'react-i18next';
55
import { Button, PostHogAction } from '@lace/common';
6-
import { OnPasswordChange, Password } from '@lace/core';
6+
import { Password, useSecrets } from '@lace/core';
77
import { useRedirection } from '@hooks';
88
import { dAppRoutePaths } from '@routes';
99
import { Layout } from './Layout';
@@ -21,7 +21,7 @@ export const SignTransaction = (): React.ReactElement => {
2121
const redirectToSignFailure = useRedirection(dAppRoutePaths.dappTxSignFailure);
2222
const redirectToSignSuccess = useRedirection(dAppRoutePaths.dappTxSignSuccess);
2323
const [isLoading, setIsLoading] = useState(false);
24-
const [password, setPassword] = useState<string>();
24+
const { password, setPassword, clearSecrets } = useSecrets();
2525
const [validPassword, setValidPassword] = useState<boolean>();
2626
const analytics = useAnalyticsContext();
2727

@@ -35,10 +35,12 @@ export const SignTransaction = (): React.ReactElement => {
3535
[TX_CREATION_TYPE_KEY]: TxCreationType.External
3636
});
3737

38+
const passphrase = Buffer.from(password.value, 'utf8');
3839
try {
39-
const passphrase = Buffer.from(password, 'utf8');
4040
await request.sign(passphrase, { willRetryOnFailure: true });
4141
setValidPassword(true);
42+
clearSecrets();
43+
passphrase.fill(0);
4244
redirectToSignSuccess();
4345
} catch (error) {
4446
if (error instanceof Wallet.KeyManagement.errors.AuthenticationError) {
@@ -47,11 +49,11 @@ export const SignTransaction = (): React.ReactElement => {
4749
redirectToSignFailure();
4850
}
4951
} finally {
52+
clearSecrets();
53+
passphrase.fill(0);
5054
setIsLoading(false);
5155
}
52-
}, [password, analytics, redirectToSignFailure, redirectToSignSuccess, request]);
53-
54-
const handleChange: OnPasswordChange = (target) => setPassword(target.value);
56+
}, [password, analytics, redirectToSignFailure, redirectToSignSuccess, request, clearSecrets]);
5557

5658
const confirmIsDisabled = useMemo(() => {
5759
if (request.walletType !== WalletType.InMemory) return false;
@@ -85,7 +87,7 @@ export const SignTransaction = (): React.ReactElement => {
8587
{t('browserView.transaction.send.enterWalletPasswordToConfirmTransaction')}
8688
</h5>
8789
<Password
88-
onChange={handleChange}
90+
onChange={setPassword}
8991
onSubmit={handleSubmit}
9092
error={validPassword === false}
9193
errorMessage={t('browserView.transaction.send.error.invalidPassword')}

apps/browser-extension-wallet/src/features/dapp/components/collateral/CreateCollateral.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
/* eslint-disable react/no-multi-comp */
22
import React, { useCallback, useEffect, useMemo, useState } from 'react';
33
import { DappCreateCollateralProps } from './types';
4-
import { OnPasswordChange, Password, DappInfo, RowContainer, renderAmountInfo, renderLabel } from '@lace/core';
4+
import {
5+
OnPasswordChange,
6+
Password,
7+
DappInfo,
8+
RowContainer,
9+
renderAmountInfo,
10+
renderLabel,
11+
useSecrets
12+
} from '@lace/core';
513
import { APIErrorCode, ApiError } from '@cardano-sdk/dapp-connector';
614
import { Wallet } from '@lace/cardano';
715
import { useTranslation } from 'react-i18next';
@@ -31,12 +39,12 @@ export const CreateCollateral = ({
3139
const { inMemoryWallet, walletType, isInMemoryWallet } = useWalletStore();
3240
const addresses = useObservable(inMemoryWallet.addresses$);
3341
const [isSubmitting, setIsSubmitting] = useState(false);
34-
const [password, setPassword] = useState('');
42+
const { password, setPassword, clearSecrets } = useSecrets();
3543
const [isPasswordValid, setIsPasswordValid] = useState(true);
3644

3745
const handleChange: OnPasswordChange = (target) => {
3846
setIsPasswordValid(true);
39-
setPassword(target.value);
47+
setPassword(target);
4048
};
4149
const { priceResult } = useFetchCoinPrice();
4250
const { fiatCurrency } = useCurrencyStore();
@@ -76,15 +84,16 @@ export const CreateCollateral = ({
7684
};
7785

7886
try {
79-
await withSignTxConfirmation(submitTx, password);
87+
await withSignTxConfirmation(submitTx, password.value);
8088
} catch (error) {
8189
if (error instanceof Wallet.KeyManagement.errors.AuthenticationError) {
82-
setPassword('');
8390
setIsPasswordValid(false);
8491
}
92+
} finally {
93+
clearSecrets();
8594
}
8695
setIsSubmitting(false);
87-
}, [collateralTx, collateralInfo.amount, inMemoryWallet, password, confirm]);
96+
}, [collateralTx, collateralInfo.amount, inMemoryWallet, password, confirm, clearSecrets]);
8897

8998
const confirmButtonLabel = useMemo(() => {
9099
if (isInMemoryWallet) {

apps/browser-extension-wallet/src/features/unlock-wallet/components/UnlockWalletContainer.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useWalletStore } from '@src/stores';
55
import { useBackgroundServiceAPIContext } from '@providers/BackgroundServiceAPI';
66
import { saveValueInLocalStorage } from '@src/utils/local-storage';
77
import { useKeyboardShortcut } from '@lace/common';
8-
import { OnPasswordChange } from '@lace/core';
8+
import { OnPasswordChange, useSecrets } from '@lace/core';
99
import { BrowserViewSections } from '@lib/scripts/types';
1010
import { useAnalyticsContext } from '@providers';
1111
import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker';
@@ -22,17 +22,17 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine
2222
const backgroundService = useBackgroundServiceAPIContext();
2323

2424
const [isVerifyingPassword, setIsVerifyingPassword] = useState(false);
25-
const [password, setPassword] = useState('');
25+
const { password, setPassword, clearSecrets } = useSecrets();
2626
const [isValidPassword, setIsValidPassword] = useState(true);
2727

2828
const handlePasswordChange = useCallback<OnPasswordChange>(
2929
(target) => {
3030
if (!isValidPassword) {
3131
setIsValidPassword(true);
3232
}
33-
setPassword(target.value);
33+
setPassword(target);
3434
},
35-
[isValidPassword]
35+
[isValidPassword, setPassword]
3636
);
3737

3838
useEffect(() => {
@@ -52,6 +52,8 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine
5252
}
5353
} catch {
5454
setIsValidPassword(false);
55+
} finally {
56+
clearSecrets();
5557
}
5658
setIsVerifyingPassword(false);
5759
};
@@ -73,7 +75,7 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine
7375
isLoading={isVerifyingPassword}
7476
onUnlock={onUnlock}
7577
passwordInput={{ handleChange: handlePasswordChange, invalidPass: !isValidPassword }}
76-
unlockButtonDisabled={password === ''}
78+
unlockButtonDisabled={!password.value}
7779
onForgotPasswordClick={onForgotPasswordClick}
7880
/>
7981
);

0 commit comments

Comments
 (0)