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 1f1aa86d..241ddff9 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 0f93fb24..7e316681 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 8b72c633..f716bd79 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 5ba298e4..d2ccbabb 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 a4289fe8..84f19520 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 eb1dca1d..9e752503 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 cea08ba2..ef44dfb9 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 5ec20f5a..d5c5a9f7 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 00000000..9a8fb103 --- /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 00000000..2df9ed1e --- /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 00000000..d64c6e98 --- /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 0978b4a3..896b3714 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 d8c54b5d..91961cdb 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 00000000..be18801b --- /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 ade36157..c7a48be4 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 8135cd16..788ecbb3 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 ffeaca0e..2dfb128d 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;