From a7f0e6571828dba329fae705ec8be7654f050385 Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:08:07 +0000 Subject: [PATCH 01/16] fix: lw-11855 --- .../src/hooks/useWalletManager.ts | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/apps/browser-extension-wallet/src/hooks/useWalletManager.ts b/apps/browser-extension-wallet/src/hooks/useWalletManager.ts index 4b5609f03..96d10dbd0 100644 --- a/apps/browser-extension-wallet/src/hooks/useWalletManager.ts +++ b/apps/browser-extension-wallet/src/hooks/useWalletManager.ts @@ -568,44 +568,45 @@ export const useWalletManager = (): UseWalletManager => { [createWallet] ); - const createWalletFromMnemonic = useCallback( - async ({ name, chainId = getCurrentChainId(), mnemonic }: CreateWalletParams): Promise => { - const accountIndex = 0; - const passphrase = Buffer.from(password.value, 'utf8'); - const lockValue = HexBlob.fromBytes(await Wallet.KeyManagement.emip3encrypt(LOCK_VALUE, passphrase)); - const keyAgent = await Wallet.KeyManagement.InMemoryKeyAgent.fromBip39MnemonicWords( - { - chainId, - getPassphrase: async () => passphrase, - mnemonicWords: mnemonic, - accountIndex, - purpose: KeyManagement.KeyPurpose.STANDARD - }, - { - bip32Ed25519: Wallet.bip32Ed25519, - logger - } - ); - const metadata = { - name, - lastActiveAccountIndex: accountIndex, - lockValue - }; - const encryptedSecrets = { - keyMaterial: await encryptMnemonic(mnemonic, passphrase), - rootPrivateKeyBytes: HexBlob.fromBytes( - Buffer.from( - (keyAgent.serializableData as Wallet.KeyManagement.SerializableInMemoryKeyAgentData) - .encryptedRootPrivateKeyBytes - ) + const createWalletFromMnemonic = async ({ + name, + chainId = getCurrentChainId(), + mnemonic + }: CreateWalletParams): Promise => { + const accountIndex = 0; + const passphrase = Buffer.from(password.value, 'utf8'); + const lockValue = HexBlob.fromBytes(await Wallet.KeyManagement.emip3encrypt(LOCK_VALUE, passphrase)); + const keyAgent = await Wallet.KeyManagement.InMemoryKeyAgent.fromBip39MnemonicWords( + { + chainId, + getPassphrase: async () => passphrase, + mnemonicWords: mnemonic, + accountIndex, + purpose: KeyManagement.KeyPurpose.STANDARD + }, + { + bip32Ed25519: Wallet.bip32Ed25519, + logger + } + ); + const metadata = { + name, + lastActiveAccountIndex: accountIndex, + lockValue + }; + const encryptedSecrets = { + keyMaterial: await encryptMnemonic(mnemonic, passphrase), + rootPrivateKeyBytes: HexBlob.fromBytes( + Buffer.from( + (keyAgent.serializableData as Wallet.KeyManagement.SerializableInMemoryKeyAgentData) + .encryptedRootPrivateKeyBytes ) - }; - const extendedAccountPublicKey = keyAgent.extendedAccountPublicKey; + ) + }; + const extendedAccountPublicKey = keyAgent.extendedAccountPublicKey; - return createWallet({ name, passphrase, metadata, encryptedSecrets, extendedAccountPublicKey }); - }, - [createWallet, getCurrentChainId, password?.value] - ); + return createWallet({ name, passphrase, metadata, encryptedSecrets, extendedAccountPublicKey }); + }; const activateWallet = useCallback( async (props: ActivateWalletProps): Promise => { From ff12a81c51ad409510e1e8802f2f6c37239e0ce7 Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:24:00 +0000 Subject: [PATCH 02/16] fixup! fix: lw-11855 --- .../src/features/dapp/components/SignData.tsx | 14 ++++++++------ .../dapp/components/SignTransaction.tsx | 14 ++++++++------ .../collateral/CreateCollateral.tsx | 19 ++++++++++++++----- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/browser-extension-wallet/src/features/dapp/components/SignData.tsx b/apps/browser-extension-wallet/src/features/dapp/components/SignData.tsx index c363bd4f6..547d43470 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/SignData.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/SignData.tsx @@ -3,7 +3,7 @@ import { Spin } from 'antd'; import { Wallet } from '@lace/cardano'; import { useTranslation } from 'react-i18next'; import { Button } from '@lace/common'; -import { OnPasswordChange, Password } from '@lace/core'; +import { Password, useSecrets } from '@lace/core'; import { useRedirection } from '@hooks'; import { dAppRoutePaths } from '@routes'; import { Layout } from './Layout'; @@ -20,15 +20,17 @@ export const SignData = (): React.ReactElement => { const redirectToSignFailure = useRedirection(dAppRoutePaths.dappDataSignFailure); const redirectToSignSuccess = useRedirection(dAppRoutePaths.dappDataSignSuccess); const [isLoading, setIsLoading] = useState(false); - const [password, setPassword] = useState(); const [validPassword, setValidPassword] = useState(); + const { password, setPassword, clearSecrets } = useSecrets(); const onConfirm = useCallback(async () => { setIsLoading(true); + const passphrase = Buffer.from(password.value, 'utf8'); try { - const passphrase = Buffer.from(password, 'utf8'); await request.sign(passphrase, { willRetryOnFailure: true }); setValidPassword(true); + clearSecrets(); + passphrase.fill(0); redirectToSignSuccess(); } catch (error) { if (error instanceof Wallet.KeyManagement.errors.AuthenticationError) { @@ -37,12 +39,12 @@ export const SignData = (): React.ReactElement => { redirectToSignFailure(); } } finally { + passphrase.fill(0); + clearSecrets(); setIsLoading(false); } }, [password, redirectToSignFailure, redirectToSignSuccess, request]); - const handleChange: OnPasswordChange = (target) => setPassword(target.value); - const confirmIsDisabled = useMemo(() => { if (request.walletType !== WalletType.InMemory) return false; return !password; @@ -68,7 +70,7 @@ export const SignData = (): React.ReactElement => { {t('browserView.transaction.send.enterWalletPasswordToConfirmTransaction')} { const redirectToSignFailure = useRedirection(dAppRoutePaths.dappTxSignFailure); const redirectToSignSuccess = useRedirection(dAppRoutePaths.dappTxSignSuccess); const [isLoading, setIsLoading] = useState(false); - const [password, setPassword] = useState(); + const { password, setPassword, clearSecrets } = useSecrets(); const [validPassword, setValidPassword] = useState(); const analytics = useAnalyticsContext(); @@ -35,10 +35,12 @@ export const SignTransaction = (): React.ReactElement => { [TX_CREATION_TYPE_KEY]: TxCreationType.External }); + const passphrase = Buffer.from(password.value, 'utf8'); try { - const passphrase = Buffer.from(password, 'utf8'); await request.sign(passphrase, { willRetryOnFailure: true }); setValidPassword(true); + clearSecrets(); + passphrase.fill(0); redirectToSignSuccess(); } catch (error) { if (error instanceof Wallet.KeyManagement.errors.AuthenticationError) { @@ -47,12 +49,12 @@ export const SignTransaction = (): React.ReactElement => { redirectToSignFailure(); } } finally { + clearSecrets(); + passphrase.fill(0); setIsLoading(false); } }, [password, analytics, redirectToSignFailure, redirectToSignSuccess, request]); - const handleChange: OnPasswordChange = (target) => setPassword(target.value); - const confirmIsDisabled = useMemo(() => { if (request.walletType !== WalletType.InMemory) return false; return !password; @@ -85,7 +87,7 @@ export const SignTransaction = (): React.ReactElement => { {t('browserView.transaction.send.enterWalletPasswordToConfirmTransaction')} { setIsPasswordValid(true); - setPassword(target.value); + setPassword(target); }; const { priceResult } = useFetchCoinPrice(); const { fiatCurrency } = useCurrencyStore(); @@ -76,12 +84,13 @@ export const CreateCollateral = ({ }; try { - await withSignTxConfirmation(submitTx, password); + await withSignTxConfirmation(submitTx, password.value); } catch (error) { if (error instanceof Wallet.KeyManagement.errors.AuthenticationError) { - setPassword(''); setIsPasswordValid(false); } + } finally { + clearSecrets(); } setIsSubmitting(false); }, [collateralTx, collateralInfo.amount, inMemoryWallet, password, confirm]); From a4532962549a63b5aadbb163dc7e90360098be89 Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:36:15 +0000 Subject: [PATCH 03/16] fixup! fix: lw-11855 --- .../MigrationContainer/MigrationContainer.tsx | 13 ++++++++----- .../components/UnlockWalletContainer.tsx | 10 ++++++---- .../components/Collateral/CollateralDrawer.tsx | 10 +++++----- .../Collateral/send/CollateralStepSend.tsx | 4 ++-- .../components/PaperWallet/PassphraseStage.tsx | 4 ++-- .../WalletSetup/WalletSetupPasswordStep.tsx | 14 +++++++------- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/apps/browser-extension-wallet/src/components/MigrationContainer/MigrationContainer.tsx b/apps/browser-extension-wallet/src/components/MigrationContainer/MigrationContainer.tsx index 42e6a996f..605c845a8 100644 --- a/apps/browser-extension-wallet/src/components/MigrationContainer/MigrationContainer.tsx +++ b/apps/browser-extension-wallet/src/components/MigrationContainer/MigrationContainer.tsx @@ -10,7 +10,7 @@ import { Lock } from '@src/views/browser-view/components/Lock'; import { MainLoader } from '@components/MainLoader'; import { FailedMigration } from './FailedMigration'; import { MigrationInProgress } from './MigrationInProgress'; -import { OnPasswordChange } from '@lace/core'; +import { OnPasswordChange, useSecrets } from '@lace/core'; export interface MigrationContainerProps { children: React.ReactNode; @@ -33,12 +33,12 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp const [renderState, setRenderState] = useState(INITIAL_RENDER_STATE); const [isVerifyingPassword, setIsVerifyingPassword] = useState(false); - const [password, setPassword] = useState(); + const { password, setPassword, clearSecrets } = useSecrets(); const [isValidPassword, setIsValidPassword] = useState(true); const migrate = useCallback(async () => { setRenderState(INITIAL_RENDER_STATE); - if (appMode === APP_MODE_POPUP) await applyMigrations(migrationState, password); + if (appMode === APP_MODE_POPUP) await applyMigrations(migrationState, password.value); }, [migrationState, password, appMode]); const handlePasswordChange = useCallback( @@ -46,7 +46,7 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp if (!isValidPassword) { setIsValidPassword(true); } - setPassword(target.value); + setPassword(target); }, [isValidPassword] ); @@ -66,8 +66,11 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp await unlockWallet(); setIsValidPassword(true); await migrate(); + clearSecrets(); } catch { setIsValidPassword(false); + } finally { + clearSecrets(); } setIsVerifyingPassword(false); }, [unlockWallet, migrate]); @@ -143,7 +146,7 @@ export const MigrationContainer = ({ children, appMode }: MigrationContainerProp isLoading={isVerifyingPassword} onUnlock={onUnlock} passwordInput={{ handleChange: handlePasswordChange, invalidPass: !isValidPassword }} - unlockButtonDisabled={password === ''} + unlockButtonDisabled={!password.value} // TODO: show forgot password here too. Use same logic as in ResetDataError on click showForgotPassword={false} /> diff --git a/apps/browser-extension-wallet/src/features/unlock-wallet/components/UnlockWalletContainer.tsx b/apps/browser-extension-wallet/src/features/unlock-wallet/components/UnlockWalletContainer.tsx index 90704c955..c12fe7a26 100644 --- a/apps/browser-extension-wallet/src/features/unlock-wallet/components/UnlockWalletContainer.tsx +++ b/apps/browser-extension-wallet/src/features/unlock-wallet/components/UnlockWalletContainer.tsx @@ -5,7 +5,7 @@ import { useWalletStore } from '@src/stores'; import { useBackgroundServiceAPIContext } from '@providers/BackgroundServiceAPI'; import { saveValueInLocalStorage } from '@src/utils/local-storage'; import { useKeyboardShortcut } from '@lace/common'; -import { OnPasswordChange } from '@lace/core'; +import { OnPasswordChange, useSecrets } from '@lace/core'; import { BrowserViewSections } from '@lib/scripts/types'; import { useAnalyticsContext } from '@providers'; import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker'; @@ -22,7 +22,7 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine const backgroundService = useBackgroundServiceAPIContext(); const [isVerifyingPassword, setIsVerifyingPassword] = useState(false); - const [password, setPassword] = useState(''); + const { password, setPassword, clearSecrets } = useSecrets(); const [isValidPassword, setIsValidPassword] = useState(true); const handlePasswordChange = useCallback( @@ -30,7 +30,7 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine if (!isValidPassword) { setIsValidPassword(true); } - setPassword(target.value); + setPassword(target); }, [isValidPassword] ); @@ -52,6 +52,8 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine } } catch { setIsValidPassword(false); + } finally { + clearSecrets(); } setIsVerifyingPassword(false); }; @@ -73,7 +75,7 @@ export const UnlockWalletContainer = ({ validateMnemonic }: UnlockWalletContaine isLoading={isVerifyingPassword} onUnlock={onUnlock} passwordInput={{ handleChange: handlePasswordChange, invalidPass: !isValidPassword }} - unlockButtonDisabled={password === ''} + unlockButtonDisabled={!password.value} onForgotPasswordClick={onForgotPasswordClick} /> ); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/Collateral/CollateralDrawer.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/Collateral/CollateralDrawer.tsx index acedef16f..440fd62d6 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/Collateral/CollateralDrawer.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/Collateral/CollateralDrawer.tsx @@ -15,6 +15,7 @@ import { TransactionFail } from '@src/views/browser-view/features/send-transacti import { useBuiltTxState } from '@src/views/browser-view/features/send-transaction'; import { FooterHW } from './hardware-wallet/FooterHW'; import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker'; +import { useSecrets } from '@lace/core'; interface CollateralDrawerProps { visible: boolean; @@ -39,8 +40,7 @@ export const CollateralDrawer = ({ walletUI: { appMode } } = useWalletStore(); const popupView = appMode === APP_MODE_POPUP; - const [password, setPassword] = useState(); - const clearPassword = () => setPassword(''); + const { password, setPassword, clearSecrets } = useSecrets(); const [isPasswordValid, setIsPasswordValid] = useState(true); const isWalletSyncingForTheFirstTime = useSyncingTheFirstTime(); const { initializeCollateralTx, submitCollateralTx, isInitializing, isSubmitting, hasEnoughAda, txFee } = @@ -122,7 +122,7 @@ export const CollateralDrawer = ({ @@ -131,11 +131,11 @@ export const CollateralDrawer = ({ void; + setPassword: OnPasswordChange; isInMemory: boolean; isPasswordValid: boolean; setIsPasswordValid: (isPasswordValid: boolean) => void; @@ -36,7 +36,7 @@ export const CollateralStepSend = ({ const { t } = useTranslation(); const handlePasswordChange: OnPasswordChange = (target) => { setIsPasswordValid(true); - return setPassword(target.value); + return setPassword(target); }; const { priceResult } = useFetchCoinPrice(); const { fiatCurrency } = useCurrencyStore(); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/PaperWallet/PassphraseStage.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/PaperWallet/PassphraseStage.tsx index bade3a900..0a0cf9112 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/PaperWallet/PassphraseStage.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/settings/components/PaperWallet/PassphraseStage.tsx @@ -4,13 +4,13 @@ import { Banner } from '@lace/common'; import { i18n } from '@lace/translation'; import { Flex } from '@input-output-hk/lace-ui-toolkit'; import styles from '../SettingsLayout.module.scss'; -import { Password, PasswordObj } from '@lace/core'; +import { Password, OnPasswordChange } from '@lace/core'; export const PassphraseStage = ({ setPassword, isPasswordValid }: { - setPassword: (pw: Partial) => void; + setPassword: OnPasswordChange; isPasswordValid: boolean; }): JSX.Element => ( diff --git a/packages/core/src/ui/components/WalletSetup/WalletSetupPasswordStep.tsx b/packages/core/src/ui/components/WalletSetup/WalletSetupPasswordStep.tsx index dc25dc2d8..999ea579b 100644 --- a/packages/core/src/ui/components/WalletSetup/WalletSetupPasswordStep.tsx +++ b/packages/core/src/ui/components/WalletSetup/WalletSetupPasswordStep.tsx @@ -5,6 +5,7 @@ import { complexityLevels, PasswordVerification, PasswordVerificationProps } fro import { OnPasswordChange, Password } from '@ui/components/Password'; import { TranslationsFor } from '@ui/utils/types'; import { passwordComplexity } from '@src/ui/utils/password-complexity'; +import { useSecrets } from '@src/ui/hooks'; type BarStates = PasswordVerificationProps['complexityBarList']; @@ -47,11 +48,10 @@ export const WalletSetupPasswordStep = ({ translations, getFeedbackTranslations }: WalletSetupPasswordStepProps): React.ReactElement => { - const [password, setPassword] = useState(''); - const [passwordConfirmation, setPasswordConfirmation] = useState(''); + const { password, setPassword, setPasswordConfirmation, passwordConfirmation } = useSecrets(); const [passHasBeenValidated, setPassHasBeenValidated] = useState(false); - const { feedbackKeys, score } = useMemo(() => passwordComplexity(password), [password]); + const { feedbackKeys, score } = useMemo(() => passwordComplexity(password.value), [password.value]); const feedbackTranslation = useMemo( () => getFeedbackTranslations(feedbackKeys), [feedbackKeys, getFeedbackTranslations] @@ -63,14 +63,14 @@ export const WalletSetupPasswordStep = ({ passHasBeenValidated && !passwordConfirmationErrorMessage && score >= minimumPassLevelRequired && - password.length > 0 + password.value.length > 0 ); const complexityBarList: BarStates = useMemo(() => getComplexityBarStateList(score), [score]); const handlePasswordConfirmationChange: OnPasswordChange = (target) => { setPassHasBeenValidated(true); - setPasswordConfirmation(target.value); + setPasswordConfirmation(target); }; return ( @@ -79,7 +79,7 @@ export const WalletSetupPasswordStep = ({ description={translations.description} onBack={onBack} onNext={() => { - onNext({ password }); + onNext({ password: password.value }); }} isNextEnabled={isNextEnabled} currentTimelineStep={WalletTimelineSteps.WALLET_SETUP} @@ -88,7 +88,7 @@ export const WalletSetupPasswordStep = ({ setPassword(e.value)} + onChange={setPassword} level={score} feedbacks={feedbackTranslation} complexityBarList={complexityBarList} From 1ad4cd22737feea7960f9a37829088c986a5864f Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:47:33 +0000 Subject: [PATCH 04/16] fixup! fix: lw-11855 --- .../src/lib/wallet-api-ui.ts | 2 +- .../EnterPassword/EnterPassword.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/browser-extension-wallet/src/lib/wallet-api-ui.ts b/apps/browser-extension-wallet/src/lib/wallet-api-ui.ts index 229b7a0cf..d88dd9452 100644 --- a/apps/browser-extension-wallet/src/lib/wallet-api-ui.ts +++ b/apps/browser-extension-wallet/src/lib/wallet-api-ui.ts @@ -71,7 +71,7 @@ export const withSignTxConfirmation = async (action: () => Promise, passwo throw new TypeError('Invalid state: expected password for in-memory wallet'); } const passphrase = Buffer.from(password, 'utf8'); - await req.sign(passphrase); + await req.sign(passphrase).finally(() => passphrase.fill(0)); } else { await req.sign(); } diff --git a/packages/core/src/shared-wallets/add-shared-wallet/generate-key-flow/EnterPassword/EnterPassword.tsx b/packages/core/src/shared-wallets/add-shared-wallet/generate-key-flow/EnterPassword/EnterPassword.tsx index 4f4f3cb47..1514a71b0 100644 --- a/packages/core/src/shared-wallets/add-shared-wallet/generate-key-flow/EnterPassword/EnterPassword.tsx +++ b/packages/core/src/shared-wallets/add-shared-wallet/generate-key-flow/EnterPassword/EnterPassword.tsx @@ -2,11 +2,12 @@ import { HardwareWalletComponent as ColdWalletIcon, Flex, WalletComponent as HotWalletIcon, - PasswordBox, Text, + UncontrolledPasswordBox, } from '@input-output-hk/lace-ui-toolkit'; +import { useSecrets } from '@src/ui/hooks'; import cn from 'classnames'; -import React, { ReactElement, VFC, useState } from 'react'; +import React, { ReactElement, VFC } from 'react'; import { useTranslation } from 'react-i18next'; import { SharedWalletLayout } from '../../SharedWalletLayout'; import { keyGenerationTimelineSteps } from '../timelineSteps'; @@ -39,12 +40,12 @@ export const EnterPassword: VFC = ({ walletName, }) => { const { t } = useTranslation(); - const [password, setPassword] = useState(''); + const { password, setPassword, clearSecrets } = useSecrets(); const icon = mapOfWalletTypeIconProperties[kind]; const next = () => { - onGenerateKeys(password); - setPassword(''); + onGenerateKeys(password.value); + clearSecrets(); }; let passwordErrorMessage; @@ -88,11 +89,10 @@ export const EnterPassword: VFC = ({ {walletName} - setPassword(event.target.value)} + onChange={setPassword} onSubmit={(event) => { event.preventDefault(); next(); From 590f5837393ed35f514b89dbfb7e14a79d4d33fb Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:32:47 +0000 Subject: [PATCH 05/16] fixup! fix: lw-11855 --- .../components/WalletAccounts.tsx | 52 ++++++++++--------- .../EnableAccountPasswordPrompt.tsx | 37 +++++++------ 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/WalletAccounts.tsx b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/WalletAccounts.tsx index 4d1d5b06a..6a0be466d 100644 --- a/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/WalletAccounts.tsx +++ b/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/WalletAccounts.tsx @@ -12,7 +12,8 @@ import { EnableAccountConfirmWithHW, EnableAccountConfirmWithHWState, EnableAccountPasswordPrompt, - useDialogWithData + useDialogWithData, + useSecrets } from '@lace/core'; import { useWalletStore } from '@src/stores'; import { useWalletManager } from '@hooks'; @@ -58,6 +59,7 @@ export const WalletAccounts = ({ isPopup, onBack }: { isPopup: boolean; onBack: account: activeAccount } } = cardanoWallet; + const { clearSecrets, password } = useSecrets(); const editAccountDrawer = useDialogWithData(); const disableAccountConfirmation = useDialogWithData(); @@ -117,6 +119,7 @@ export const WalletAccounts = ({ isPopup, onBack }: { isPopup: boolean; onBack: accountIndex }); const accountName = accountsData.find((acc) => acc.accountNumber === accountIndex)?.label; + clearSecrets(); closeDropdownAndShowAccountActivated(accountName); }, [wallet.walletId, activateWallet, accountsData, closeDropdownAndShowAccountActivated, analytics] @@ -198,29 +201,30 @@ export const WalletAccounts = ({ isPopup, onBack }: { isPopup: boolean; onBack: [wallet.type, enableAccountPasswordDialog, enableAccountHWSigningDialog, unlockHWAccount] ); - const unlockInMemoryWalletAccountWithPassword = useCallback( - async (passphrase: Uint8Array) => { - const { accountIndex } = enableAccountPasswordDialog.data; - const name = defaultAccountName(accountIndex); - try { - await addAccount({ - wallet, - accountIndex, - passphrase, - metadata: { name: defaultAccountName(accountIndex) } - }); - analytics.sendEventToPostHog(PostHogAction.MultiWalletEnableAccount, { - // eslint-disable-next-line camelcase - $set: { wallet_accounts_quantity: await getWalletAccountsQtyString(walletRepository) } - }); - enableAccountPasswordDialog.hide(); - closeDropdownAndShowAccountActivated(name); - } catch { - enableAccountPasswordDialog.setData({ ...enableAccountPasswordDialog.data, wasPasswordIncorrect: true }); - } - }, - [wallet, addAccount, enableAccountPasswordDialog, closeDropdownAndShowAccountActivated, analytics, walletRepository] - ); + const unlockInMemoryWalletAccountWithPassword = async () => { + const { accountIndex } = enableAccountPasswordDialog.data; + const name = defaultAccountName(accountIndex); + const passphrase = Buffer.from(password?.value); + try { + await addAccount({ + wallet, + accountIndex, + passphrase, + metadata: { name: defaultAccountName(accountIndex) } + }); + analytics.sendEventToPostHog(PostHogAction.MultiWalletEnableAccount, { + // eslint-disable-next-line camelcase + $set: { wallet_accounts_quantity: await getWalletAccountsQtyString(walletRepository) } + }); + enableAccountPasswordDialog.hide(); + closeDropdownAndShowAccountActivated(name); + } catch { + enableAccountPasswordDialog.setData({ ...enableAccountPasswordDialog.data, wasPasswordIncorrect: true }); + } finally { + passphrase.fill(0); + clearSecrets(); + } + }; const lockAccount = useCallback(async () => { await walletRepository.removeAccount({ diff --git a/packages/core/src/ui/components/Account/EnableAccountPasswordPrompt/EnableAccountPasswordPrompt.tsx b/packages/core/src/ui/components/Account/EnableAccountPasswordPrompt/EnableAccountPasswordPrompt.tsx index 6c2fb944a..86107d8ab 100644 --- a/packages/core/src/ui/components/Account/EnableAccountPasswordPrompt/EnableAccountPasswordPrompt.tsx +++ b/packages/core/src/ui/components/Account/EnableAccountPasswordPrompt/EnableAccountPasswordPrompt.tsx @@ -1,7 +1,8 @@ -import React, { useState } from 'react'; -import { Box, Button, Flex, PasswordBox, Text } from '@input-output-hk/lace-ui-toolkit'; +import React from 'react'; +import { Box, Button, Flex, Text, UncontrolledPasswordBox } from '@input-output-hk/lace-ui-toolkit'; import { Drawer, DrawerNavigation } from '@lace/common'; import styles from './EnableAccountPasswordPrompt.module.scss'; +import { useSecrets } from '@src/ui/hooks'; interface Props { open: boolean; @@ -16,7 +17,7 @@ interface Props { confirm: string; }; onCancel: () => void; - onConfirm: (passphrase: Uint8Array) => void; + onConfirm: () => void; isPopup: boolean; } @@ -28,32 +29,37 @@ export const EnableAccountPasswordPrompt = ({ onCancel, translations }: Props): JSX.Element => { - const [currentPassword, setCurrentPassword] = useState(''); + const { password, setPassword, clearSecrets } = useSecrets(); + + const handleClose = () => { + clearSecrets(); + // wait for propogation before executing onCancel + setTimeout(onCancel, 0); + }; return ( } - onClose={() => { - onCancel(); - setCurrentPassword(''); - }} + navigation={} + onClose={handleClose} popupView={isPopup} footer={ onConfirm(Buffer.from(currentPassword))} + disabled={!password?.value} + onClick={() => { + onConfirm(); + }} data-testid="enable-account-password-prompt-confirm-btn" label={translations.confirm} /> @@ -75,14 +81,13 @@ export const EnableAccountPasswordPrompt = ({ justifyContent={isPopup ? undefined : 'center'} className={isPopup ? styles.passwordPopUpLayout : styles.passwordExtendedLayout} > - setCurrentPassword(e.target.value)} + onChange={setPassword} onSubmit={(event): void => { event.preventDefault(); - onConfirm(Buffer.from(currentPassword)); + onConfirm(); }} errorMessage={wasPasswordIncorrect ? translations.wrongPassword : undefined} rootStyle={{ width: '100%' }} From e74d9e7f65bc55a177e8d132bc43b68f98563ad1 Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:24:24 +0000 Subject: [PATCH 06/16] feat(nami): add dedicated password component --- packages/nami/package.json | 2 +- packages/nami/rollup.config.js | 12 ++++++++++-- packages/nami/src/typings/css.modules.d.ts | 7 +++++++ .../ui/app/components/namiPassword/index.ts | 1 + .../namiPassword/namiPassword.module.scss | 12 ++++++++++++ .../components/namiPassword/namiPassword.tsx | 18 ++++++++++++++++++ yarn.lock | 15 ++------------- 7 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 packages/nami/src/typings/css.modules.d.ts create mode 100644 packages/nami/src/ui/app/components/namiPassword/index.ts create mode 100644 packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss create mode 100644 packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx diff --git a/packages/nami/package.json b/packages/nami/package.json index 30f5415d9..7035ab46f 100644 --- a/packages/nami/package.json +++ b/packages/nami/package.json @@ -125,7 +125,7 @@ "prettier": "3.2.5", "react": "17.0.2", "react-dom": "17.0.2", - "rollup-plugin-import-css": "^3.5.0", + "rollup-plugin-postcss": "4.0.1", "rollup-plugin-svg": "^2.0.0", "sass": "^1.68.0", "storybook": "^8.1.6", diff --git a/packages/nami/rollup.config.js b/packages/nami/rollup.config.js index 03afcac0a..ae8711448 100644 --- a/packages/nami/rollup.config.js +++ b/packages/nami/rollup.config.js @@ -1,12 +1,12 @@ import commonjs from '@rollup/plugin-commonjs'; import typescript from '@rollup/plugin-typescript'; -import css from 'rollup-plugin-import-css'; import image from '@rollup/plugin-image'; import json from '@rollup/plugin-json'; import packageJson from './package.json'; import svgr from '@svgr/rollup'; import copy from 'rollup-plugin-copy'; import url from '@rollup/plugin-url'; +import postcss from 'rollup-plugin-postcss'; const common = { plugins: [ @@ -18,7 +18,15 @@ const common = { }), json(), commonjs(), - css(), + postcss({ + // postcss plugin includes always path to the es version of the style-inject + // no matter what build type it is. It makes cjs build requiring esm version + // https://github.com/egoist/rollup-plugin-postcss/issues/381 + // https://github.com/egoist/rollup-plugin-postcss/issues/367 + inject: cssVariableName => ` +import styleInject from 'style-inject'; +styleInject(${cssVariableName});`, + }), image(), svgr(), copy({ diff --git a/packages/nami/src/typings/css.modules.d.ts b/packages/nami/src/typings/css.modules.d.ts new file mode 100644 index 000000000..14d4148ff --- /dev/null +++ b/packages/nami/src/typings/css.modules.d.ts @@ -0,0 +1,7 @@ +declare module '*.scss' { + interface IClassNames { + [className: string]: string; + } + const classNames: IClassNames; + export = classNames; +} diff --git a/packages/nami/src/ui/app/components/namiPassword/index.ts b/packages/nami/src/ui/app/components/namiPassword/index.ts new file mode 100644 index 000000000..874dca6cf --- /dev/null +++ b/packages/nami/src/ui/app/components/namiPassword/index.ts @@ -0,0 +1 @@ +export * from './namiPassword'; diff --git a/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss b/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss new file mode 100644 index 000000000..53edad5f3 --- /dev/null +++ b/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss @@ -0,0 +1,12 @@ +.passwordContainer { + background: transparent; + border: 3px solid #19a9a2; + padding: 0; + div > button { + transform: scale(0.65); + background-color: #38414d; + svg * { + stroke: #fff; + } + } +} diff --git a/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx b/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx new file mode 100644 index 000000000..7f72c274f --- /dev/null +++ b/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { UncontrolledPasswordBox } from '@input-output-hk/lace-ui-toolkit'; +import type { UncontrolledPasswordBoxProps } from '@input-output-hk/lace-ui-toolkit'; +import styles from './namiPassword.module.scss'; + +type NamiPasswordProps = Omit< + UncontrolledPasswordBoxProps, + 'containerClassName' +>; + +export const NamiPassword = (props: NamiPasswordProps) => { + return ( + + ); +}; diff --git a/yarn.lock b/yarn.lock index 9864c8f2f..389c7f1ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13814,7 +13814,7 @@ __metadata: react-router-dom: 5.2.0 react-time-ago: ^7.3.3 react-window: ^1.8.10 - rollup-plugin-import-css: ^3.5.0 + rollup-plugin-postcss: 4.0.1 rollup-plugin-svg: ^2.0.0 rxjs: 7.4.0 sass: ^1.68.0 @@ -16698,7 +16698,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.0.4, @rollup/pluginutils@npm:^5.0.5": +"@rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.0.5": version: 5.1.0 resolution: "@rollup/pluginutils@npm:5.1.0" dependencies: @@ -53515,17 +53515,6 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-import-css@npm:^3.5.0": - version: 3.5.0 - resolution: "rollup-plugin-import-css@npm:3.5.0" - dependencies: - "@rollup/pluginutils": ^5.0.4 - peerDependencies: - rollup: ^2.x.x || ^3.x.x || ^4.x.x - checksum: fda5120f52f29ea2721439ccdba693b0aa1912680c142a491b2cc54b2fb3e01f10234a2153397cd25d5202cfa6a8afd8b80601930c25f8b527a16bf90af9ae35 - languageName: node - linkType: hard - "rollup-plugin-inject@npm:^3.0.0": version: 3.0.2 resolution: "rollup-plugin-inject@npm:3.0.2" From 563362c4a206bcbd8e07a31ed36a293dae7e97cd Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:50:27 +0000 Subject: [PATCH 07/16] fixup! fix: lw-11855 --- .../features/send-transaction/store/store.ts | 6 - .../views/nami-mode/NamiDappConnectorView.tsx | 9 +- packages/core/src/ui/hooks/useSecrets.ts | 16 +- packages/nami/src/api/extension/wallet.ts | 5 +- .../dapp-outside-handles-provider/types.ts | 6 + .../outside-handles-provider/types.ts | 4 + .../ui/app/components/changePasswordModal.tsx | 210 +++++------------- .../src/ui/app/components/confirmModal.tsx | 115 ++++------ .../ui/app/components/transactionBuilder.tsx | 19 +- .../ui/app/pages/dapp-connector/signData.tsx | 11 +- .../ui/app/pages/dapp-connector/signTx.tsx | 14 +- packages/nami/src/ui/app/pages/send.tsx | 18 +- packages/nami/src/ui/app/pages/wallet.tsx | 11 +- 13 files changed, 189 insertions(+), 255 deletions(-) diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/send-transaction/store/store.ts b/apps/browser-extension-wallet/src/views/browser-view/features/send-transaction/store/store.ts index 60bada9b4..fa9be53f0 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/send-transaction/store/store.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/send-transaction/store/store.ts @@ -15,7 +15,6 @@ import { calculateSpentBalance, getOutputValues } from '../helpers'; import { useCallback, useMemo } from 'react'; import { IAssetInfo } from '@src/features/send/types'; import { v4 as uuid } from 'uuid'; -import omit from 'lodash/omit'; import { useWalletStore } from '@src/stores'; import { isValidAddress, isValidAddressPerNetwork } from '@src/utils/validators'; import { compactNumberWithUnit, formatNumberForDisplay } from '@src/utils/format-number'; @@ -86,9 +85,6 @@ export interface Store { resetStates: () => void; setOutputDefaultStateOnFormSwitch: (form: FormOptions) => void; - password?: string; - setPassword: (pass: string) => void; - removePassword: () => void; isSubmitingTx?: boolean; isPasswordValid?: boolean; setSubmitingTxState: (args: { isSubmitingTx?: boolean; isPasswordValid?: boolean }) => void; @@ -338,8 +334,6 @@ const useStore = create((set, get) => ({ isPasswordValid: params?.isPasswordValid, isSubmitingTx: params?.isSubmitingTx }), - setPassword: (pass) => set({ password: pass }), - removePassword: () => set((state) => omit(state, ['password']), true), setIsMultipleSelectionAvailable: (param) => set( param === false diff --git a/apps/browser-extension-wallet/src/views/nami-mode/NamiDappConnectorView.tsx b/apps/browser-extension-wallet/src/views/nami-mode/NamiDappConnectorView.tsx index fec64be27..ea5560871 100644 --- a/apps/browser-extension-wallet/src/views/nami-mode/NamiDappConnectorView.tsx +++ b/apps/browser-extension-wallet/src/views/nami-mode/NamiDappConnectorView.tsx @@ -21,6 +21,7 @@ import { Wallet } from '@lace/cardano'; import { createWalletAssetProvider } from '@cardano-sdk/wallet'; import { tryGetAssetInfos } from './utils'; import { useNetworkError } from '@hooks/useNetworkError'; +import { useSecrets } from '@lace/core'; const DAPP_TOAST_DURATION = 100; const dappConnector: Omit = { @@ -88,7 +89,7 @@ const dappConnector: Omit = { }, sign: async (password: string) => { const passphrase = Buffer.from(password, 'utf8'); - await r.sign(passphrase, { willRetryOnFailure: true }); + await r.sign(passphrase, { willRetryOnFailure: true }).finally(() => passphrase.fill(0)); } } })), @@ -124,7 +125,7 @@ const dappConnector: Omit = { }, sign: async (password: string) => { const passphrase = Buffer.from(password, 'utf8'); - return r.sign(passphrase, { willRetryOnFailure: true }); + return r.sign(passphrase, { willRetryOnFailure: true }).finally(() => passphrase.fill(0)); } } })), @@ -146,6 +147,7 @@ export const NamiDappConnectorView = withDappContext((): React.ReactElement => { environmentName, blockchainProvider: { assetProvider } } = useWalletStore(); + const passwordUtil = useSecrets(); const { theme } = useTheme(); const cardanoCoin = useMemo( @@ -203,7 +205,8 @@ export const NamiDappConnectorView = withDappContext((): React.ReactElement => { walletManager, walletRepository, environmentName, - dappConnector: { ...dappConnector, getAssetInfos } + dappConnector: { ...dappConnector, getAssetInfos }, + passwordUtil }} > = {}; const globalPassword$ = new BehaviorSubject>(NO_PASSWORD); const globalPasswordConfirmation$ = new BehaviorSubject>(NO_PASSWORD); +const globalRepeatedPassword$ = new BehaviorSubject>(NO_PASSWORD); // used for nami password reset const deletePasswordValues = (password$: BehaviorSubject>) => { if (password$.value.input) { @@ -22,23 +23,36 @@ const setPasswordConfirmation = (pw: Partial) => { globalPasswordConfirmation$.next(pw); }; +// used for nami password reset +const setRepeatedConfirmation = (pw: Partial) => { + globalRepeatedPassword$.next(pw); +}; + const clearSecrets = () => { deletePasswordValues(globalPassword$); setPassword(NO_PASSWORD); deletePasswordValues(globalPasswordConfirmation$); setPasswordConfirmation(NO_PASSWORD); + deletePasswordValues(globalRepeatedPassword$); + // used for nami password reset + setRepeatedConfirmation(NO_PASSWORD); }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useSecrets = () => { const password = useObservable(globalPassword$, NO_PASSWORD); const passwordConfirmation = useObservable(globalPasswordConfirmation$, NO_PASSWORD); + // used for nami password reset + const repeatedPassword = useObservable(globalRepeatedPassword$, NO_PASSWORD); return { clearSecrets, password, setPassword, passwordConfirmation, - setPasswordConfirmation + setPasswordConfirmation, + // used for nami password reset + repeatedPassword, + setRepeatedConfirmation }; }; diff --git a/packages/nami/src/api/extension/wallet.ts b/packages/nami/src/api/extension/wallet.ts index cfdd57e85..4f5ea966c 100644 --- a/packages/nami/src/api/extension/wallet.ts +++ b/packages/nami/src/api/extension/wallet.ts @@ -9,6 +9,7 @@ import type { CommonOutsideHandlesContextValue } from '../../features/common-out import type { Serialization } from '@cardano-sdk/core'; import type { UnwitnessedTx } from '@cardano-sdk/tx-construction'; import type { Wallet } from '@lace/cardano'; +import type { PasswordObj } from '@lace/core'; export const buildTx = async ({ output, @@ -42,7 +43,7 @@ export const signAndSubmit = async ({ inMemoryWallet, }: Readonly<{ tx: UnwitnessedTx; - password: string; + password: Partial; withSignTxConfirmation: CommonOutsideHandlesContextValue['withSignTxConfirmation']; inMemoryWallet: Wallet.ObservableWallet; }>) => @@ -50,4 +51,4 @@ export const signAndSubmit = async ({ const { cbor: signedTx } = await tx.sign(); return await submitTx(signedTx, inMemoryWallet); - }, password); + }, password.value); diff --git a/packages/nami/src/features/dapp-outside-handles-provider/types.ts b/packages/nami/src/features/dapp-outside-handles-provider/types.ts index a97cb90be..f4314891d 100644 --- a/packages/nami/src/features/dapp-outside-handles-provider/types.ts +++ b/packages/nami/src/features/dapp-outside-handles-provider/types.ts @@ -5,6 +5,7 @@ import type { WalletManagerApi, WalletRepositoryApi, } from '@cardano-sdk/web-extension'; +import type { Password } from '@input-output-hk/lace-ui-toolkit'; import type { Wallet } from '@lace/cardano'; export interface DappConnector { @@ -57,4 +58,9 @@ export interface DappOutsideHandlesContextValue { >; environmentName: Wallet.ChainName; dappConnector: DappConnector; + passwordUtil: { + clearSecrets: () => void; + password: Partial; + setPassword: (pw: Readonly>) => void; + }; } diff --git a/packages/nami/src/features/outside-handles-provider/types.ts b/packages/nami/src/features/outside-handles-provider/types.ts index e8c4b9b97..62703752f 100644 --- a/packages/nami/src/features/outside-handles-provider/types.ts +++ b/packages/nami/src/features/outside-handles-provider/types.ts @@ -73,6 +73,10 @@ export interface OutsideHandlesContextValue { clearSecrets: () => void; password: Partial; setPassword: (pw: Readonly>) => void; + passwordConfirmation: Partial; + setPasswordConfirmation: (pw: Readonly>) => void; + repeatedPassword: Partial; + setRepeatedConfirmation: (pw: Readonly>) => void; }; delegationTxFee: string; delegationStoreDelegationTxBuilder?: TxBuilder; diff --git a/packages/nami/src/ui/app/components/changePasswordModal.tsx b/packages/nami/src/ui/app/components/changePasswordModal.tsx index 793306d6c..78122a67e 100644 --- a/packages/nami/src/ui/app/components/changePasswordModal.tsx +++ b/packages/nami/src/ui/app/components/changePasswordModal.tsx @@ -1,6 +1,6 @@ /* eslint-disable unicorn/no-null */ /* eslint-disable react/no-unescaped-entities */ -import React from 'react'; +import React, { useState } from 'react'; import { Button, @@ -21,6 +21,9 @@ import { import { Events } from '../../../features/analytics/events'; import { useCaptureEvent } from '../../../features/analytics/hooks'; +import { NamiPassword } from './namiPassword'; +import { useOutsideHandles } from 'features/outside-handles-provider'; +import { noop } from 'lodash'; interface Props { changePassword: ( @@ -33,42 +36,14 @@ export interface ChangePasswordModalComponentRef { openModal: () => void; } -interface State { - currentPassword: string; - newPassword: string; - repeatPassword: string; - matchingPassword: boolean; - passwordLen: boolean | null; - show: boolean; -} - const ChangePasswordModalComponent = ({ changePassword }: Props, ref) => { const capture = useCaptureEvent(); const cancelRef = React.useRef(null); - const inputRef = React.useRef(null); const toast = useToast(); + const { passwordUtil } = useOutsideHandles(); const { isOpen, onOpen, onClose } = useDisclosure(); const [isLoading, setIsLoading] = React.useState(false); - const [state, setState] = React.useState({ - currentPassword: '', - newPassword: '', - repeatPassword: '', - matchingPassword: false, - passwordLen: null, - show: false, - }); - - React.useEffect(() => { - setState({ - currentPassword: '', - newPassword: '', - repeatPassword: '', - matchingPassword: false, - passwordLen: null, - show: false, - }); - }, [isOpen]); React.useImperativeHandle(ref, () => ({ openModal: () => { @@ -76,19 +51,33 @@ const ChangePasswordModalComponent = ({ changePassword }: Props, ref) => { }, })); + const destroySecrets = () => { + passwordUtil.clearSecrets(); + }; + + const handleClose = () => { + destroySecrets(); + setTimeout(onClose, 0); + }; + const confirmHandler = async () => { if ( - !state.currentPassword || - !state.newPassword || - !state.repeatPassword || - state.newPassword !== state.repeatPassword + !passwordUtil.password?.value || + !passwordUtil.passwordConfirmation?.value || + !passwordUtil.repeatedPassword?.value || + passwordUtil.passwordConfirmation.value !== + passwordUtil.repeatedPassword.value ) return; setIsLoading(true); try { - await changePassword(state.currentPassword, state.newPassword); + await changePassword( + passwordUtil.password.value, + passwordUtil.passwordConfirmation.value, + ); + toast({ title: 'Password updated', status: 'success', @@ -96,9 +85,9 @@ const ChangePasswordModalComponent = ({ changePassword }: Props, ref) => { }); capture(Events.SettingsChangePasswordConfirm); - - onClose(); + handleClose(); } catch (error) { + destroySecrets(); toast({ title: error instanceof Error ? error.message : 'Password update failed!', @@ -111,13 +100,7 @@ const ChangePasswordModalComponent = ({ changePassword }: Props, ref) => { }; return ( - + Change Password @@ -126,127 +109,56 @@ const ChangePasswordModalComponent = ({ changePassword }: Props, ref) => { Type your current password and new password below, if you want to continue. - - - - { - setState(s => ({ ...s, currentPassword: e.target.value })); - }} - placeholder="Enter current password" - /> - - - - - - + - - - { - setState(s => ({ ...s, newPassword: e.target.value })); - }} - onBlur={e => { - setState(s => ({ - ...s, - passwordLen: e.target.value - ? e.target.value.length >= 8 - : false, - })); - }} - placeholder="Enter new password" - /> - - - - + + {!!passwordUtil.password?.value && + passwordUtil.password.value.length < 8 && ( + + Password must be at least 8 characters long + + )} - - {state.passwordLen === false && ( - - Password must be at least 8 characters long - - )} - - - - { - setState(s => ({ ...s, repeatPassword: e.target.value })); - }} - placeholder="Repeat new password" - /> - - - - - - {state.repeatPassword && - state.repeatPassword !== state.newPassword && ( + + + {!!passwordUtil.passwordConfirmation?.value && + !!passwordUtil.repeatedPassword?.value && + passwordUtil.passwordConfirmation.value !== + passwordUtil.repeatedPassword?.value && ( Password doesn't match )} - - - - {state.wrongPassword && ( - Password is wrong - )} + await confirmHandler()} + /> @@ -236,16 +210,17 @@ const ConfirmModalNormal = ({ mr={3} variant="ghost" onClick={() => { - if (props.onCloseBtn) { - props.onCloseBtn(); - } - onClose(); + handleClose(() => props.onCloseBtn?.()); }} > Close - {state.wrongPassword && ( Password is wrong From 34b189a59a3f6004a187bdbae6e1c117aa68b328 Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:52:12 +0000 Subject: [PATCH 15/16] fixup! fix: lw-11855 --- .../namiPassword/namiPassword.module.scss | 40 ++++++++++++++++--- .../components/namiPassword/namiPassword.tsx | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss b/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss index 53edad5f3..400953ec7 100644 --- a/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss +++ b/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss @@ -1,12 +1,42 @@ -.passwordContainer { +.namiPasswordContainer { background: transparent; - border: 3px solid #19a9a2; + border: 1px solid rgba(255, 255, 255, 0.16); + outline-offset: 1px; + &:focus-within { + border: 2px solid #19a9a2; + outline-offset: 0px; + } + border-radius: 0.5rem; padding: 0; div > button { transform: scale(0.65); - background-color: #38414d; - svg * { - stroke: #fff; + } +} + +html[data-theme='dark'] { + .namiPasswordContainer { + div > button { + background-color: #38414d; + &:hover { + background-color: rgba(255, 255, 255, 0.16); + } + svg * { + stroke: #fff; + } + } + } +} + +html[data-theme='light'] { + .namiPasswordContainer { + div > button { + background-color: #edf2f7; + &:hover { + background-color: #e2e8f0; + } + svg * { + stroke: currentcolor; + } } } } diff --git a/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx b/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx index 7f72c274f..779e934c8 100644 --- a/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx +++ b/packages/nami/src/ui/app/components/namiPassword/namiPassword.tsx @@ -11,7 +11,7 @@ type NamiPasswordProps = Omit< export const NamiPassword = (props: NamiPasswordProps) => { return ( ); From 74c74916f592012be19dc4790231b993f7101604 Mon Sep 17 00:00:00 2001 From: Michael Chappell <7581002+mchappell@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:18:42 +0000 Subject: [PATCH 16/16] fixup! fix: lw-11855 --- .../ui/app/components/namiPassword/namiPassword.module.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss b/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss index 400953ec7..711fbaec4 100644 --- a/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss +++ b/packages/nami/src/ui/app/components/namiPassword/namiPassword.module.scss @@ -1,6 +1,9 @@ .namiPasswordContainer { background: transparent; - border: 1px solid rgba(255, 255, 255, 0.16); + border: 1px solid #edf2f7; + &:hover { + border: 1px solid #e2e8f0; + } outline-offset: 1px; &:focus-within { border: 2px solid #19a9a2;