Unlock for 15m toggle (#3749)

This commit is contained in:
Jaco Greeff 2020-09-23 14:44:50 +02:00 committed by GitHub
parent 49c9b0e4cc
commit f2dbedd58a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 63 additions and 24 deletions

View File

@ -7,7 +7,7 @@ import { AccountId, BalanceOf, Call, Multisig, ProxyDefinition, ProxyType } from
import { ITuple } from '@polkadot/types/types';
import { AddressFlags, AddressProxy } from './types';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ApiPromise } from '@polkadot/api';
import { InputAddress, Modal, Toggle } from '@polkadot/react-components';
import { useAccounts, useApi, useIsMountedRef } from '@polkadot/react-hooks';
@ -34,6 +34,11 @@ interface MultiState {
whoFilter: string[];
}
interface PasswordState {
isUnlockCached: boolean;
signPassword: string;
}
interface ProxyState {
address: string;
isProxied: boolean;
@ -146,7 +151,7 @@ function Address ({ currentItem, onChange, onEnter, passwordError, requestAddres
const [isProxyActive, setIsProxyActive] = useState(true);
const [multiInfo, setMultInfo] = useState<MultiState | null>(null);
const [proxyInfo, setProxyInfo] = useState<ProxyState | null>(null);
const [signPassword, setSignPassword] = useState('');
const [{ isUnlockCached, signPassword }, setSignPassword] = useState<PasswordState>({ isUnlockCached: false, signPassword: '' });
const [signAddress, flags] = useMemo(
(): [string, AddressFlags] => {
@ -159,6 +164,11 @@ function Address ({ currentItem, onChange, onEnter, passwordError, requestAddres
[multiAddress, proxyAddress, isProxyActive, multiInfo, proxyInfo, requestAddress]
);
const _updatePassword = useCallback(
(signPassword: string, isUnlockCached: boolean) => setSignPassword({ isUnlockCached, signPassword }),
[]
);
useEffect((): void => {
!proxyInfo && setProxyAddress(null);
}, [proxyInfo]);
@ -190,13 +200,14 @@ function Address ({ currentItem, onChange, onEnter, passwordError, requestAddres
useEffect((): void => {
onChange({
isMultiCall: isMultiCall,
isMultiCall,
isUnlockCached,
multiRoot: multiInfo ? multiInfo.address : null,
proxyRoot: (proxyInfo && isProxyActive) ? proxyInfo.address : null,
signAddress,
signPassword
});
}, [isProxyActive, isMultiCall, multiAddress, multiInfo, onChange, proxyAddress, proxyInfo, signAddress, signPassword]);
}, [isProxyActive, isMultiCall, isUnlockCached, multiAddress, multiInfo, onChange, proxyAddress, proxyInfo, signAddress, signPassword]);
return (
<>
@ -251,7 +262,7 @@ function Address ({ currentItem, onChange, onEnter, passwordError, requestAddres
<Password
address={signAddress}
error={passwordError}
onChange={setSignPassword}
onChange={_updatePassword}
onEnter={onEnter}
/>
)}

View File

@ -5,16 +5,17 @@ import { KeyringPair } from '@polkadot/keyring/types';
import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { Modal, Password } from '@polkadot/react-components';
import { Modal, Password, Toggle } from '@polkadot/react-components';
import keyring from '@polkadot/ui-keyring';
import { useTranslation } from './translate';
import { UNLOCK_MINS } from './util';
interface Props {
address: string;
className?: string;
error?: string;
onChange: (password: string) => void;
onChange: (password: string, isUnlockCached: boolean) => void;
onEnter?: () => void;
password: string;
tabIndex?: number;
@ -31,6 +32,7 @@ function getPair (address: string): KeyringPair | null {
function Unlock ({ address, className, error, onChange, onEnter, tabIndex }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const [password, setPassword] = useState('');
const [isUnlockCached, setIsUnlockCached] = useState(false);
const pair = useMemo(
() => getPair(address),
@ -38,8 +40,8 @@ function Unlock ({ address, className, error, onChange, onEnter, tabIndex }: Pro
);
useEffect((): void => {
onChange(password);
}, [onChange, password]);
onChange(password, isUnlockCached);
}, [onChange, isUnlockCached, password]);
if (!pair || !pair.isLocked || pair.meta.isInjected) {
return null;
@ -51,13 +53,19 @@ function Unlock ({ address, className, error, onChange, onEnter, tabIndex }: Pro
<Password
autoFocus
isError={!!error}
label={t('unlock account with password')}
labelExtra={error && <div className='errorLabel'>{t('wrong password supplied')}</div>}
label={t<string>('unlock account with password')}
onChange={setPassword}
onEnter={onEnter}
tabIndex={tabIndex}
value={password}
/>
>
<Toggle
isOverlay
label={t<string>('unlock for {{expiry}} min', { replace: { expiry: UNLOCK_MINS } })}
onChange={setIsUnlockCached}
value={isUnlockCached}
/>
</Password>
</Modal.Column>
<Modal.Column>
<p>{t('Unlock the sending account to allow signing of this transaction.')}</p>
@ -68,7 +76,11 @@ function Unlock ({ address, className, error, onChange, onEnter, tabIndex }: Pro
export default React.memo(styled(Unlock)`
.errorLabel {
margin-right: 2rem;
margin-right: 1rem;
color: #9f3a38 !important;
}
.ui--Toggle {
bottom: 1.1rem;
}
`);

View File

@ -19,14 +19,14 @@ import { Option } from '@polkadot/types';
import keyring from '@polkadot/ui-keyring';
import { BN_ZERO, assert } from '@polkadot/util';
import { LedgerSigner, QrSigner } from './signers';
import { AccountSigner, LedgerSigner, QrSigner } from './signers';
import { useTranslation } from './translate';
import Address from './Address';
import Qr from './Qr';
import SignFields from './SignFields';
import Tip from './Tip';
import Transaction from './Transaction';
import { extractExternal, handleTxResults } from './util';
import { cacheUnlock, extractExternal, handleTxResults } from './util';
interface Props {
className?: string;
@ -38,7 +38,7 @@ const NOOP = () => undefined;
let qrId = 0;
function unlockAccount ({ signAddress, signPassword }: AddressProxy): string | null {
function unlockAccount ({ isUnlockCached, signAddress, signPassword }: AddressProxy): string | null {
let publicKey;
try {
@ -53,6 +53,7 @@ function unlockAccount ({ signAddress, signPassword }: AddressProxy): string | n
try {
pair.decodePkcs8(signPassword);
isUnlockCached && cacheUnlock(pair);
} catch (error) {
console.error(error);
@ -133,7 +134,7 @@ async function wrapTx (api: ApiPromise, currentItem: QueueTx, { isMultiCall, mul
return tx;
}
async function extractParams (address: string, options: Partial<SignerOptions>, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', KeyringPair | string, Partial<SignerOptions>]> {
async function extractParams (address: string, options: Partial<SignerOptions>, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>]> {
const pair = keyring.getPair(address);
const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, source } } = pair;
@ -149,7 +150,7 @@ async function extractParams (address: string, options: Partial<SignerOptions>,
return ['signing', address, { ...options, signer: injected.signer }];
}
return ['signing', pair, options];
return ['signing', pair.address, { ...options, signer: new AccountSigner(pair) }];
}
function TxSigned ({ className, currentItem, requestAddress }: Props): React.ReactElement<Props> | null {
@ -162,7 +163,7 @@ function TxSigned ({ className, currentItem, requestAddress }: Props): React.Rea
const [isRenderError, toggleRenderError] = useToggle();
const [isSubmit, setIsSubmit] = useState(true);
const [passwordError, setPasswordError] = useState<string | null>(null);
const [senderInfo, setSenderInfo] = useState<AddressProxy>({ isMultiCall: false, multiRoot: null, proxyRoot: null, signAddress: requestAddress, signPassword: '' });
const [senderInfo, setSenderInfo] = useState<AddressProxy>({ isMultiCall: false, isUnlockCached: false, multiRoot: null, proxyRoot: null, signAddress: requestAddress, signPassword: '' });
const [signedOptions, setSignedOptions] = useState<Partial<SignerOptions>>({});
const [signedTx, setSignedTx] = useState<string | null>(null);
const [multiCall, setMultiCall] = useState<string | null>(null);

View File

@ -7,6 +7,8 @@ import { SignerPayloadJSON } from '@polkadot/types/types';
import { registry } from '@polkadot/react-api';
import { lockAccount } from '../util';
let id = 0;
export default class AccountSigner implements Signer {
@ -20,6 +22,7 @@ export default class AccountSigner implements Signer {
return new Promise((resolve): void => {
const signed = registry.createType('ExtrinsicPayload', payload, { version: payload.version }).sign(this.#keyringPair);
lockAccount(this.#keyringPair);
resolve({ id: ++id, ...signed });
});
}

View File

@ -16,6 +16,7 @@ export interface AddressFlags {
export interface AddressProxy {
isMultiCall: boolean;
isUnlockCached: boolean;
multiRoot: string | null;
proxyRoot: string | null;
signAddress: string | null;

View File

@ -1,6 +1,7 @@
// Copyright 2017-2020 @polkadot/react-signer authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { KeyringPair } from '@polkadot/keyring/types';
import { QueueTx, QueueTxMessageSetStatus, QueueTxStatus } from '@polkadot/react-components/Status/types';
import { AddressFlags } from './types';
@ -9,10 +10,22 @@ import keyring from '@polkadot/ui-keyring';
const NOOP = () => undefined;
const LOCK_DELAY = 15 * 60 * 1000;
export const UNLOCK_MINS = 15;
const LOCK_DELAY = UNLOCK_MINS * 60 * 1000;
const lockCountdown: Record<string, number> = {};
export function cacheUnlock (pair: KeyringPair): void {
lockCountdown[pair.address] = Date.now() + LOCK_DELAY;
}
export function lockAccount (pair: KeyringPair): void {
if ((Date.now() > (lockCountdown[pair.address] || 0)) && !pair.isLocked) {
pair.lock();
}
}
export function extractExternal (accountId: string | null): AddressFlags {
if (!accountId) {
return { isHardware: false, isMultisig: false, isProxied: false, isQr: false, isUnlockable: false, threshold: 0, who: [] };
@ -33,13 +46,11 @@ export function extractExternal (accountId: string | null): AddressFlags {
if (isUnlockable) {
const entry = lockCountdown[pair.address];
const now = Date.now();
if (entry && (now > entry) && !pair.isLocked) {
if (entry && (Date.now() > entry) && !pair.isLocked) {
pair.lock();
lockCountdown[pair.address] = 0;
}
lockCountdown[pair.address] = now + LOCK_DELAY;
}
return {