Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: password-protect account private key reveal #14390

Merged
merged 6 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/brave_wallet/browser/brave_wallet_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ constexpr char kRampBuyUrl[] =
constexpr char kRampID[] = "8yxja8782as5essk2myz3bmh4az6gpq4nte9n2gf";

constexpr webui::LocalizedString kLocalizedStrings[] = {
{"braveWalletEnterYourPassword", IDS_BRAVE_WALLET_ENTER_YOUR_PASSWORD},
{"braveWallet", IDS_BRAVE_WALLET},
{"braveWalletDefiCategory", IDS_BRAVE_WALLET_DEFI_CATEGORY},
{"braveWalletNftCategory", IDS_BRAVE_WALLET_NFT_CATEGORY},
Expand Down
12 changes: 11 additions & 1 deletion components/brave_wallet_ui/common/async/__mocks__/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export class MockedWalletApiProxy {
})
}

keyringService = {
validatePassword: async () => ({ result: true })
}

ethTxManagerProxy = {
getGasEstimation1559: async () => {
return {
Expand All @@ -93,6 +97,12 @@ export class MockedWalletApiProxy {
}
}

export default function getAPIProxy (): Partial<WalletApiProxy> {
export function getAPIProxy (): Partial<WalletApiProxy> {
return new MockedWalletApiProxy() as unknown as Partial<WalletApiProxy> & MockedWalletApiProxy
}

export function getMockedAPIProxy (): WalletApiProxy {
return new MockedWalletApiProxy() as unknown as WalletApiProxy
}

export default getAPIProxy
4 changes: 3 additions & 1 deletion components/brave_wallet_ui/common/async/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import WalletApiProxy from '../wallet_api_proxy'
import getWalletPanelApiProxy from '../../panel/wallet_panel_api_proxy'
import getWalletPageApiProxy from '../../page/wallet_page_api_proxy'

export default function getAPIProxy (): WalletApiProxy {
export function getAPIProxy (): WalletApiProxy {
return window.location.hostname === 'wallet-panel.top-chrome'
? getWalletPanelApiProxy() : getWalletPageApiProxy()
}

export default getAPIProxy
10 changes: 10 additions & 0 deletions components/brave_wallet_ui/common/context/api-proxy.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.

import * as React from 'react'

import WalletApiProxy from '../wallet_api_proxy'

export const ApiProxyContext = React.createContext<WalletApiProxy | undefined>(undefined)
16 changes: 16 additions & 0 deletions components/brave_wallet_ui/common/hooks/use-api-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.

import * as React from 'react'

import { ApiProxyContext } from '../context/api-proxy.context'

export const useApiProxy = () => {
const context = React.useContext(ApiProxyContext)
if (context === undefined) {
throw new Error('useApiProxy must be used within a ApiProxyContext.Provider')
}
return context
}
4 changes: 3 additions & 1 deletion components/brave_wallet_ui/common/wallet_api_proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Store } from './async/types'
import { getBraveKeyring } from './api/hardware_keyrings'
import { BraveWallet } from '../constants/types'

export default class WalletApiProxy {
export class WalletApiProxy {
walletHandler = new BraveWallet.WalletHandlerRemote()
jsonRpcService = new BraveWallet.JsonRpcServiceRemote()
swapService = new BraveWallet.SwapServiceRemote()
Expand Down Expand Up @@ -109,3 +109,5 @@ export default class WalletApiProxy {
this.braveWalletService.addObserver(braveWalletServiceObserverReceiver.$.bindNewPipeAndPassRemote())
}
}

export default WalletApiProxy
2 changes: 1 addition & 1 deletion components/brave_wallet_ui/components/desktop/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import WalletMorePopup from './wallet-more-popup'
import WalletBanner from './wallet-banner'
import PopupModal from './popup-modals'
import { AddAccountModal } from './popup-modals/add-account-modal'
import AccountSettingsModal from './popup-modals/account-settings-modal'
import { AccountSettingsModal } from './popup-modals/account-settings-modal/account-settings-modal'
import EditVisibleAssetsModal from './popup-modals/edit-visible-assets-modal'
import AssetWatchlistItem from './asset-watchlist-item'
import SelectNetworkDropdown from './select-network-dropdown'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.

import styled from 'styled-components'

import ClipboardIcon from '../../../../assets/svg-icons/clipboard-icon.svg'
import { WalletButton } from '../../../shared/style'

Expand Down Expand Up @@ -48,6 +54,13 @@ export const Input = styled.input`
margin: 0;
}
`
export const InputLabelText = styled.label`
display: block;
margin-bottom: 8px;
color: ${(p) => p.theme.color.text03};
text-align: left;
width: 100%;
`

export const QRCodeWrapper = styled.img`
display: flex;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.

import * as React from 'react'
import * as qr from 'qr-image'

// utils
import { reduceAddress } from '../../../../utils/reduce-address'
import { getLocale, getLocaleWithTag } from '../../../../../common/locale'

// constants
import { FILECOIN_FORMAT_DESCRIPTION_URL } from '../../../../common/constants/urls'

// types
import {
AccountSettingsNavTypes,
BraveWallet,
WalletAccountType,
UpdateAccountNamePayloadType,
TopTabNavObjectType
} from '../../../../constants/types'
import {
PopupModal,
TopTabNav
} from '../..'

// options
import {
AccountSettingsNavOptions,
HardwareAccountSettingsNavOptions
} from '../../../../options/account-settings-nav-options'
import { FILECOIN_FORMAT_DESCRIPTION_URL } from '../../../../common/constants/urls'
import { reduceAddress } from '../../../../utils/reduce-address'

// components
import { NavButton } from '../../../extension'
import { CopyTooltip } from '../../../shared/copy-tooltip/copy-tooltip'
import { getLocale, getLocaleWithTag } from '../../../../../common/locale'
import TopTabNav from '../../top-tab-nav/index'
import PopupModal from '../index'
import PasswordInput from '../../../shared/password-input/index'

// Styled Components
// hooks
import { useApiProxy } from '../../../../common/hooks/use-api-proxy'

// style
import {
Input,
StyledWrapper,
Expand All @@ -34,10 +51,11 @@ import {
WarningWrapper,
PrivateKeyBubble,
ButtonWrapper,
ErrorText
} from './style'
ErrorText,
InputLabelText
} from './account-settings-modal.style'

export interface Props {
interface Props {
onClose: () => void
onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean }
onChangeTab: (id: AccountSettingsNavTypes) => void
Expand All @@ -52,28 +70,32 @@ export interface Props {
account: WalletAccountType
}

const AddAccountModal = (props: Props) => {
const {
title,
account,
tab,
hideNav,
privateKey,
onClose,
onToggleNav,
onUpdateAccountName,
onChangeTab,
onRemoveAccount,
onViewPrivateKey,
onDoneViewingPrivateKey
} = props
export const AccountSettingsModal = ({
title,
account,
tab,
hideNav,
privateKey,
onClose,
onToggleNav,
onUpdateAccountName,
onChangeTab,
onRemoveAccount,
onViewPrivateKey,
onDoneViewingPrivateKey
}: Props) => {
// state
const [accountName, setAccountName] = React.useState<string>(account.name)
const [showPrivateKey, setShowPrivateKey] = React.useState<boolean>(false)
const [updateError, setUpdateError] = React.useState<boolean>(false)
const [password, setPassword] = React.useState<string>('')
const [isCorrectPassword, setIsCorrectPassword] = React.useState<boolean>(true)
const [qrCode, setQRCode] = React.useState<string>('')

// custom hooks
const { keyringService } = useApiProxy()

// methods
const handleAccountNameChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
setAccountName(event.target.value)
setUpdateError(false)
Expand All @@ -99,11 +121,8 @@ const AddAccountModal = (props: Props) => {
})
}

React.useEffect(() => {
generateQRData()
})

const onSelectTab = (id: AccountSettingsNavTypes) => {
setShowPrivateKey(false)
onChangeTab(id)
}

Expand All @@ -113,7 +132,25 @@ const AddAccountModal = (props: Props) => {
onClose()
}

const onShowPrivateKey = () => {
const onShowPrivateKey = async () => {
kdenhartog marked this conversation as resolved.
Show resolved Hide resolved
if (!password) { // require password to view key
return
}

// entered password must be correct
const {
result: isPasswordValid
} = await keyringService.validatePassword(password)

if (!isPasswordValid) {
setIsCorrectPassword(isPasswordValid) // set or clear error
return // need valid password to continue
}

// clear entered password & error
setPassword('')
setIsCorrectPassword(true)

if (onViewPrivateKey) {
const isDefault = account?.accountType === 'Primary'
onViewPrivateKey(account?.address ?? '', isDefault, account?.coin)
Expand All @@ -138,16 +175,35 @@ const AddAccountModal = (props: Props) => {
}
}

const onPasswordChange = (value: string): void => {
setIsCorrectPassword(true) // clear error
setPassword(value)
}

const handlePasswordKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
onShowPrivateKey()
}
}

// memos / computed
const tabList = React.useMemo((): TopTabNavObjectType[] => {
return account.accountType === 'Trezor' ||
account.accountType === 'Ledger'
? HardwareAccountSettingsNavOptions()
: AccountSettingsNavOptions()
}, [account])

const filPrivateKeyFormatDescriptionTextParts =
getLocaleWithTag('braveWalletFilExportPrivateKeyFormatDescription')
const filPrivateKeyFormatDescriptionTextParts = getLocaleWithTag(
'braveWalletFilExportPrivateKeyFormatDescription'
)

// effects
React.useEffect(() => {
generateQRData()
})

// render
return (
<PopupModal title={title} onClose={onClickClose}>
{!hideNav &&
Expand Down Expand Up @@ -195,27 +251,46 @@ const AddAccountModal = (props: Props) => {
<WarningWrapper>
<WarningText>{getLocale('braveWalletAccountSettingsDisclaimer')}</WarningText>
</WarningWrapper>
{showPrivateKey && account.coin === BraveWallet.CoinType.FIL &&
<WarningWrapper>
<WarningText>
{filPrivateKeyFormatDescriptionTextParts.beforeTag}
<a target='_blank' href={FILECOIN_FORMAT_DESCRIPTION_URL}>
{filPrivateKeyFormatDescriptionTextParts.duringTag}
</a>
{filPrivateKeyFormatDescriptionTextParts.afterTag}
</WarningText>
</WarningWrapper>
}
{showPrivateKey &&
<CopyTooltip text={privateKey}>
<PrivateKeyBubble>{privateKey}</PrivateKeyBubble>
</CopyTooltip>
{showPrivateKey
? <>
{account.coin === BraveWallet.CoinType.FIL &&
<WarningWrapper>
<WarningText>
{filPrivateKeyFormatDescriptionTextParts.beforeTag}
<a target='_blank' href={FILECOIN_FORMAT_DESCRIPTION_URL} rel='noopener noreferrer'>
{filPrivateKeyFormatDescriptionTextParts.duringTag}
</a>
{filPrivateKeyFormatDescriptionTextParts.afterTag}
</WarningText>
</WarningWrapper>
}
<CopyTooltip text={privateKey}>
<PrivateKeyBubble>{privateKey}</PrivateKeyBubble>
</CopyTooltip>
</>
: <>
<InputLabelText>{getLocale('braveWalletEnterYourPassword')}</InputLabelText>
<PasswordInput
placeholder={getLocale('braveWalletCreatePasswordInput')}
onChange={onPasswordChange}
hasError={!!password && !isCorrectPassword}
error={getLocale('braveWalletLockScreenError')}
autoFocus={false}
value={password}
onKeyDown={handlePasswordKeyDown}
/>
</>
}
<ButtonWrapper>
<NavButton
onSubmit={!showPrivateKey ? onShowPrivateKey : onHidePrivateKey}
text={!showPrivateKey ? getLocale('braveWalletAccountSettingsShowKey') : getLocale('braveWalletAccountSettingsHideKey')}
buttonType='primary'
disabled={
showPrivateKey
? false
: password ? !isCorrectPassword : true
}
/>
</ButtonWrapper>
</PrivateKeyWrapper>
Expand All @@ -224,5 +299,3 @@ const AddAccountModal = (props: Props) => {
</PopupModal>
)
}

export default AddAccountModal
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
import {
WarningText,
WarningWrapper
} from '../account-settings-modal/style'
} from '../account-settings-modal/account-settings-modal.style'

interface Params {
accountTypeName: string
Expand Down
Loading