From c43caef9cf199c52784abb4e7d33983e41d7c91d Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Tue, 29 Oct 2024 17:47:58 +0200 Subject: [PATCH] Add PasswordProtect for SeedPhrase and PrivateKey pages Fixed Header and ChangePasscode styles --- .../MainLayout/Header/Header.scss | 1 + .../page-layouts/MainLayout/Header/Header.tsx | 6 +- .../pages/ChangePasscode/ChangePasscode.scss | 14 +++- .../pages/ChangePasscode/ChangePasscode.tsx | 4 +- .../popupX/pages/PrivateKey/PrivateKey.tsx | 9 ++- .../popup/popupX/pages/PrivateKey/i18n/en.ts | 2 + .../popupX/pages/SeedPhrase/SeedPhrase.tsx | 41 ++++++++-- .../popup/popupX/pages/SeedPhrase/i18n/en.ts | 2 + .../src/popup/popupX/shared/Form/Form.tsx | 2 +- .../PasswordProtect/PasswordProtect.scss | 5 ++ .../PasswordProtect/PasswordProtect.tsx | 79 +++++++++++++++++++ .../popupX/shared/PasswordProtect/index.ts | 2 + .../src/popup/popupX/shared/i18n/en.ts | 3 + .../src/popup/popupX/shared/utils/hoc.tsx | 16 +++- .../popupX/shared/utils/typescriptHelpers.ts | 4 + .../src/popup/popupX/styles/_elements.scss | 1 + .../browser-wallet/src/popup/shell/Root.tsx | 4 +- .../src/popup/shell/i18n/i18n.ts | 2 + 18 files changed, 174 insertions(+), 23 deletions(-) create mode 100644 packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss create mode 100644 packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss index 1f1aa86de..241ddff9d 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss @@ -138,6 +138,7 @@ margin: rem(1px); width: rem(106px); height: rem(106px); + background-color: $color-main-bg; &.wide { width: rem(160px); diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.tsx index 0f93fb249..7e3166812 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.tsx @@ -25,10 +25,10 @@ export default function Header({ accountOpen, setAccountOpen, }: HeaderProps) { - if (menuOpen) { - setAccountOpen(false); - } useEffect(() => { + if (menuOpen) { + setAccountOpen(false); + } if (menuOpen || accountOpen) { background?.classList.add('fade-bg'); } else { diff --git a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss index 8b72c633e..f716bd791 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss +++ b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.scss @@ -1,7 +1,13 @@ .change-passcode-x { - .divider { - display: block; - margin: rem(16px) 0 rem(24px) 0; - border-bottom: 1px solid rgba($color-white, 0.1); + .change-passcode-page__form { + display: flex; + flex-direction: column; + gap: rem(8px); + + .divider { + display: block; + margin: rem(8px) 0 rem(16px) 0; + border-bottom: 1px solid rgba($color-white, 0.1); + } } } diff --git a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx index 5ba298e4d..d2ccbabb3 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/ChangePasscode/ChangePasscode.tsx @@ -54,7 +54,7 @@ export default function ChangePasscode() {
; } + +export default withPasswordProtected(Loader, { + headingKey: 'privateKey.accountPrivateKey', + pageInfoKey: 'privateKey.passwordDescription', + submitKey: 'privateKey.showPrivateKey', +}); diff --git a/packages/browser-wallet/src/popup/popupX/pages/PrivateKey/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/PrivateKey/i18n/en.ts index a4289fe82..84f195206 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/PrivateKey/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/PrivateKey/i18n/en.ts @@ -4,6 +4,8 @@ const t = { 'Your account private key is the access key to all the funds in your account. Copy it and keep it safe. To avoid mistakes, do not write it down manually.', export: 'Export', copyKey: 'Copy account private key', + passwordDescription: 'Please enter your passcode to show the private key', + showPrivateKey: 'Show private key', }; export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/SeedPhrase.tsx b/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/SeedPhrase.tsx index eb1dca1d5..9e7525030 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/SeedPhrase.tsx +++ b/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/SeedPhrase.tsx @@ -5,14 +5,33 @@ import Page from '@popup/popupX/shared/Page'; import Text from '@popup/popupX/shared/Text'; import Card from '@popup/popupX/shared/Card'; import Copy from '@assets/svgX/copy.svg'; +import { useAsyncMemo } from 'wallet-common-helpers'; +import { decrypt } from '@shared/utils/crypto'; +import { useAtomValue } from 'jotai'; +import { encryptedSeedPhraseAtom, sessionPasscodeAtom } from '@popup/store/settings'; +import { copyToClipboard } from '@popup/popupX/shared/utils/helpers'; +import { withPasswordProtected } from '@popup/popupX/shared/utils/hoc'; -const RECOVERY_PHRASE = - 'meadow salad weather rural next promote fence mass leopard mail regret mushroom love coral viable layer lumber soft setup radar oppose miracle rural agree'.split( - ' ' +function SeedPhrase() { + const { t } = useTranslation('x', { keyPrefix: 'seedPhrase' }); + const passcode = useAtomValue(sessionPasscodeAtom); + const encryptedSeed = useAtomValue(encryptedSeedPhraseAtom); + + const seedPhrase = useAsyncMemo( + async () => { + if (encryptedSeed.loading || passcode.loading) { + return undefined; + } + if (encryptedSeed.value && passcode.value) { + return decrypt(encryptedSeed.value, passcode.value); + } + throw new Error('SeedPhrase should not be retrieved without unlocking the wallet.'); + }, + undefined, + [encryptedSeed.loading, passcode.loading] ); -export default function SeedPhrase() { - const { t } = useTranslation('x', { keyPrefix: 'seedPhrase' }); + if (!seedPhrase) return null; return ( @@ -20,12 +39,18 @@ export default function SeedPhrase() { {t('seedPhraseDescription')} - {RECOVERY_PHRASE.map((word) => ( - {word} + {seedPhrase.split(' ').map((word) => ( + {word} ))} - } label={t('copy')} /> + } label={t('copy')} onClick={() => copyToClipboard(seedPhrase)} /> ); } + +export default withPasswordProtected(SeedPhrase, { + headingKey: 'seedPhrase.seedPhrase', + pageInfoKey: 'seedPhrase.passwordDescription', + submitKey: 'seedPhrase.showSeedPhrase', +}); diff --git a/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/i18n/en.ts index cea08ba2b..ef44dfb92 100644 --- a/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/pages/SeedPhrase/i18n/en.ts @@ -3,6 +3,8 @@ const t = { seedPhraseDescription: 'Your seed phrase is the access key to all the funds in your wallet. If you forget it you will lose access to your wallet(s). Keep it somewhere safe.', copy: 'Copy seed phrase', + passwordDescription: 'Please enter your passcode to show the seed phrase.', + showSeedPhrase: 'Show secret recovery phrase', }; export default t; diff --git a/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx b/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx index 5ec20f5ad..d5c5a9f79 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx +++ b/packages/browser-wallet/src/popup/popupX/shared/Form/Form.tsx @@ -75,7 +75,7 @@ const Form = forwardRef( const internal = useForm({ defaultValues }); const methods = external ?? internal; - const submit = () => (onSubmit === undefined ? noOp : methods.handleSubmit(onSubmit)); + const submit = onSubmit === undefined ? () => noOp : methods.handleSubmit(onSubmit); return ( diff --git a/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss new file mode 100644 index 000000000..9a8fb1035 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss @@ -0,0 +1,5 @@ +.confirm-password-x { + #confirm-password-form { + margin-top: rem(30px); + } +} diff --git a/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx new file mode 100644 index 000000000..2df9ed1ed --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Validate } from 'react-hook-form'; +import { useAtomValue } from 'jotai'; +import { useTranslation } from 'react-i18next'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import Button from '@popup/popupX/shared/Button'; +import FormPassword from '@popup/popupX/shared/Form/Password'; +import Form from '@popup/popupX/shared/Form/Form'; +import { sessionPasscodeAtom } from '@popup/store/settings'; +import { useForm } from '@popup/shared/Form'; +import { TranslationKeyX } from '@popup/shell/i18n/i18n'; + +type FormValues = { + currentPasscode: string; +}; + +export type PasswordProtectConfigType = { + headingKey: TranslationKeyX; + pageInfoKey: TranslationKeyX; + submitKey: TranslationKeyX; +}; + +type PasswordProtectProps = { + setPasswordConfirmed: (passwordConfirmed: boolean) => void; + config: PasswordProtectConfigType; +}; + +export default function PasswordProtect({ + setPasswordConfirmed, + config: { headingKey, pageInfoKey, submitKey }, +}: PasswordProtectProps) { + const { t: tUse } = useTranslation('x'); + const t = (key: TranslationKeyX) => tUse(key) as unknown as string; + const { t: tPasscode } = useTranslation('x', { keyPrefix: 'sharedX.form.password' }); + const passcode = useAtomValue(sessionPasscodeAtom); + const form = useForm(); + + const handleSubmit = () => { + setPasswordConfirmed(true); + }; + + function validateCurrentPasscode(): Validate { + return (currentPasscode) => (currentPasscode !== passcode.value ? tPasscode('incorrectPasscode') : undefined); + } + + return ( + + + + {t(pageInfoKey)} + + {(f) => { + return ( + + ); + }} + + + + + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts new file mode 100644 index 000000000..d64c6e982 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts @@ -0,0 +1,2 @@ +export { default } from './PasswordProtect'; +export type { PasswordProtectConfigType } from './PasswordProtect'; diff --git a/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts index 0978b4a34..896b3714f 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts +++ b/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts @@ -5,6 +5,9 @@ const t = { weak: 'Weak', medium: 'Medium', strong: 'Strong', + incorrectPasscode: 'Incorrect passcode', + currentPasscode: 'Enter current passcode', + passcodeRequired: 'A passcode must be entered', }, tokenAmount: { token: { diff --git a/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx b/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx index d8c54b5db..91961cdb4 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx +++ b/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useSelectedCredential } from '@popup/shared/utils/account-helpers'; import Loader from '@popup/popupX/shared/Loader'; +import PasswordProtect, { PasswordProtectConfigType } from '@popup/popupX/shared/PasswordProtect'; export function withSelectedCredential

( Component: React.ComponentType

@@ -16,3 +17,16 @@ export function withSelectedCredential

( } return NewComponent; } + +export function withPasswordProtected(Component: React.ComponentType, config: PasswordProtectConfigType) { + function NewComponent() { + const [passwordConfirmed, setPasswordConfirmed] = useState(false); + + if (!passwordConfirmed) { + return ; + } + + return ; + } + return NewComponent; +} diff --git a/packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts b/packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts new file mode 100644 index 000000000..be18801b5 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts @@ -0,0 +1,4 @@ +// Generic type for iterating through nested object keys +export type ObjectPath = { + [K in keyof T]: `${D}${Exclude}${'' | (T[K] extends object ? ObjectPath : '')}`; +}[keyof T]; diff --git a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss index ade361575..c7a48be41 100644 --- a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss +++ b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss @@ -37,6 +37,7 @@ @import '../shared/Text/Text'; @import '../shared/Loader/Loader'; @import '../shared/IdCard/IdCard'; +@import '../shared/PasswordProtect/PasswordProtect'; @import '../shared/Web3IdCard/Web3IdCard'; @import '../shared/Button/Button'; @import '../shared/ExternalLink/ExternalLink'; diff --git a/packages/browser-wallet/src/popup/shell/Root.tsx b/packages/browser-wallet/src/popup/shell/Root.tsx index 8135cd16a..788ecbb3a 100644 --- a/packages/browser-wallet/src/popup/shell/Root.tsx +++ b/packages/browser-wallet/src/popup/shell/Root.tsx @@ -136,9 +136,7 @@ export default function Root() { return ( - + diff --git a/packages/browser-wallet/src/popup/shell/i18n/i18n.ts b/packages/browser-wallet/src/popup/shell/i18n/i18n.ts index ffeaca0ea..2dfb128d2 100644 --- a/packages/browser-wallet/src/popup/shell/i18n/i18n.ts +++ b/packages/browser-wallet/src/popup/shell/i18n/i18n.ts @@ -3,6 +3,7 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import countries from 'i18n-iso-countries'; +import { ObjectPath } from '@popup/popupX/shared/utils/typescriptHelpers'; import en from './locales/en'; import da from './locales/da'; @@ -10,6 +11,7 @@ import da from './locales/da'; countries.registerLocale(require('i18n-iso-countries/langs/en.json')); countries.registerLocale(require('i18n-iso-countries/langs/da.json')); +export type TranslationKeyX = ObjectPath; export const defaultNS: keyof typeof en = 'shared'; const ns: Array = Object.keys(en) as Array;