Unlock for 15m toggle (#3749)
This commit is contained in:
parent
49c9b0e4cc
commit
f2dbedd58a
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 });
|
||||
});
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ export interface AddressFlags {
|
||||
|
||||
export interface AddressProxy {
|
||||
isMultiCall: boolean;
|
||||
isUnlockCached: boolean;
|
||||
multiRoot: string | null;
|
||||
proxyRoot: string | null;
|
||||
signAddress: string | null;
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user