Passwordify Private Key & Mnemonic Inputs (#925)

* Create reusable password toggle component, convert private key and mnemonic to it.

* Convert keystore modal to togglable password.

* Remove the restore keystore tab code (defunct).

* Replace wallet info with togglable password. Allow for controlled togglable password.

* Convert last password component (generate) and cleanup unneeded files and styles.
This commit is contained in:
William O'Beirne 2018-01-26 17:11:52 -05:00 committed by Daniel Ternyak
parent 77ddf602cc
commit 7fe2888633
19 changed files with 171 additions and 476 deletions

View File

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='20px' height='20px' viewBox='0 0 511.626 511.626' style='enable-background:new 0 0 511.626 511.626;' xml:space='preserve'><path fill='#999' d='M505.918,236.117c-26.651-43.587-62.485-78.609-107.497-105.065c-45.015-26.457-92.549-39.687-142.608-39.687 c-50.059,0-97.595,13.225-142.61,39.687C68.187,157.508,32.355,192.53,5.708,236.117C1.903,242.778,0,249.345,0,255.818 c0,6.473,1.903,13.04,5.708,19.699c26.647,43.589,62.479,78.614,107.495,105.064c45.015,26.46,92.551,39.68,142.61,39.68 c50.06,0,97.594-13.176,142.608-39.536c45.012-26.361,80.852-61.432,107.497-105.208c3.806-6.659,5.708-13.223,5.708-19.699 C511.626,249.345,509.724,242.778,505.918,236.117z M194.568,158.03c17.034-17.034,37.447-25.554,61.242-25.554 c3.805,0,7.043,1.336,9.709,3.999c2.662,2.664,4,5.901,4,9.707c0,3.809-1.338,7.044-3.994,9.704 c-2.662,2.667-5.902,3.999-9.708,3.999c-16.368,0-30.362,5.808-41.971,17.416c-11.613,11.615-17.416,25.603-17.416,41.971 c0,3.811-1.336,7.044-3.999,9.71c-2.667,2.668-5.901,3.999-9.707,3.999c-3.809,0-7.044-1.334-9.71-3.999 c-2.667-2.666-3.999-5.903-3.999-9.71C169.015,195.482,177.535,175.065,194.568,158.03z M379.867,349.04 c-38.164,23.12-79.514,34.687-124.054,34.687c-44.539,0-85.889-11.56-124.051-34.687s-69.901-54.2-95.215-93.222 c28.931-44.921,65.19-78.518,108.777-100.783c-11.61,19.792-17.417,41.207-17.417,64.236c0,35.216,12.517,65.329,37.544,90.362 s55.151,37.544,90.362,37.544c35.214,0,65.329-12.518,90.362-37.544s37.545-55.146,37.545-90.362 c0-23.029-5.808-44.447-17.419-64.236c43.585,22.265,79.846,55.865,108.776,100.783C449.767,294.84,418.031,325.913,379.867,349.04 z'/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='20px' height='20px'
viewBox='0 0 511.626 511.627' style='enable-background:new 0 0 511.626 511.627;'>
<path fill='#999'
d='M361.161,291.652c15.037-21.796,22.56-45.922,22.56-72.375c0-7.422-0.76-15.417-2.286-23.984l-79.938,143.321 C326.235,329.101,346.125,313.438,361.161,291.652z'/>
<path fill='#999'
d='M372.872,94.221c0.191-0.378,0.28-1.235,0.28-2.568c0-3.237-1.522-5.802-4.571-7.715c-0.568-0.38-2.423-1.475-5.568-3.287 c-3.138-1.805-6.14-3.567-8.989-5.282c-2.854-1.713-5.989-3.472-9.422-5.28c-3.426-1.809-6.375-3.284-8.846-4.427 c-2.479-1.141-4.189-1.713-5.141-1.713c-3.426,0-6.092,1.525-7.994,4.569l-15.413,27.696c-17.316-3.234-34.451-4.854-51.391-4.854 c-51.201,0-98.404,12.946-141.613,38.831C70.998,156.08,34.836,191.385,5.711,236.114C1.903,242.019,0,248.586,0,255.819 c0,7.231,1.903,13.801,5.711,19.698c16.748,26.073,36.592,49.396,59.528,69.949c22.936,20.561,48.011,37.018,75.229,49.396 c-8.375,14.273-12.562,22.556-12.562,24.842c0,3.425,1.524,6.088,4.57,7.99c23.219,13.329,35.97,19.985,38.256,19.985 c3.422,0,6.089-1.529,7.992-4.575l13.99-25.406c20.177-35.967,50.248-89.931,90.222-161.878 C322.908,183.871,352.886,130.005,372.872,94.221z M158.456,362.885C108.97,340.616,68.33,304.93,36.547,255.822 c28.931-44.921,65.19-78.518,108.777-100.783c-11.61,19.792-17.417,41.206-17.417,64.237c0,20.365,4.661,39.68,13.99,57.955 c9.327,18.274,22.27,33.4,38.83,45.392L158.456,362.885z M265.525,155.887c-2.662,2.667-5.906,3.999-9.712,3.999 c-16.368,0-30.361,5.808-41.971,17.416c-11.613,11.615-17.416,25.603-17.416,41.971c0,3.811-1.336,7.044-3.999,9.71 c-2.668,2.667-5.902,3.999-9.707,3.999c-3.809,0-7.045-1.334-9.71-3.999c-2.667-2.666-3.999-5.903-3.999-9.71 c0-23.79,8.52-44.206,25.553-61.242c17.034-17.034,37.447-25.553,61.241-25.553c3.806,0,7.043,1.336,9.713,3.999 c2.662,2.664,3.996,5.901,3.996,9.707C269.515,149.992,268.181,153.228,265.525,155.887z'/>
<path fill='#999'
d='M505.916,236.114c-10.853-18.08-24.603-35.594-41.255-52.534c-16.646-16.939-34.022-31.496-52.105-43.68l-17.987,31.977 c31.785,21.888,58.625,49.87,80.51,83.939c-23.024,35.782-51.723,65-86.07,87.648c-34.358,22.661-71.712,35.693-112.065,39.115 l-21.129,37.688c42.257,0,82.18-9.038,119.769-27.121c37.59-18.076,70.668-43.488,99.216-76.225 c13.322-15.421,23.695-29.219,31.121-41.401c3.806-6.476,5.708-13.046,5.708-19.702 C511.626,249.157,509.724,242.59,505.916,236.114z'/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -163,34 +163,6 @@ textarea {
}
}
input[type="text"] + .eye {
cursor: pointer;
&:before {
content: "";
width: 20px;
height: 20px;
margin-left: 6px;
margin-right: 6px;
display: inline-block;
vertical-align: middle;
background: url("../images/icon-eye.svg");
}
}
input[type="password"] + .eye {
cursor: pointer;
&:before {
content: "";
width: 20px;
height: 20px;
margin-left: 6px;
margin-right: 6px;
display: inline-block;
vertical-align: middle;
background: url("../images/icon-eye-closed.svg");
}
}
// collapsable containers
.collapse-container {
h2,
@ -398,4 +370,4 @@ label small {
.ens-response {
color: @gray;
}
}

View File

@ -1,39 +0,0 @@
import React from 'react';
import classnames from 'classnames';
interface Props {
isValid: boolean;
isVisible: boolean;
name: string;
value: string;
placeholder: string;
disabled?: boolean;
handleInput(e: React.FormEvent<HTMLInputElement>): void;
handleToggle(): void;
}
const KeystoreInput: React.SFC<Props> = ({
isValid,
isVisible,
handleInput,
name,
value,
placeholder,
disabled,
handleToggle
}) => (
<div className="input-group">
<input
className={classnames('form-control', isValid ? 'is-valid' : 'is-invalid')}
type={isVisible ? 'text' : 'password'}
name={name}
placeholder={placeholder}
value={value}
onChange={handleInput}
disabled={disabled}
/>
<span onClick={handleToggle} role="button" className="input-group-addon eye" />
</div>
);
export default KeystoreInput;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { generateKeystoreFileInfo, KeystoreFile } from 'utils/keystore';
import Modal from 'components/ui/Modal';
import Input from './Input';
import { TogglablePassword } from 'components';
import translate, { translateRaw } from 'translations';
import { MINIMUM_PASSWORD_LENGTH } from 'config';
import { isValidPrivKey } from 'libs/validators';
@ -16,8 +16,6 @@ interface Props {
interface State {
privateKey: string;
password: string;
isPrivateKeyVisible: boolean;
isPasswordVisible: boolean;
keystoreFile: KeystoreFile | null;
hasError: boolean;
}
@ -25,8 +23,6 @@ interface State {
const initialState: State = {
privateKey: '',
password: '',
isPrivateKeyVisible: false,
isPasswordVisible: false,
keystoreFile: null,
hasError: false
};
@ -52,14 +48,7 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
}
public render() {
const {
privateKey,
password,
isPrivateKeyVisible,
isPasswordVisible,
keystoreFile,
hasError
} = this.state;
const { privateKey, password, keystoreFile, hasError } = this.state;
const isPrivateKeyValid = isValidPrivKey(privateKey);
const isPasswordValid = password.length >= MINIMUM_PASSWORD_LENGTH;
@ -73,27 +62,23 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
<form className="GenKeystore" onSubmit={this.handleSubmit}>
<label className="GenKeystore-field">
<h4 className="GenKeystore-field-label">Private Key</h4>
<Input
isValid={isPrivateKeyValid}
isVisible={isPrivateKeyVisible}
<TogglablePassword
name="privateKey"
value={privateKey}
handleInput={this.handleInput}
disabled={!!privateKey}
onChange={this.handleInput}
placeholder="f1d0e0789c6d40f39..."
handleToggle={this.togglePrivateKey}
disabled={!!this.props.privateKey}
isValid={isPrivateKeyValid}
/>
</label>
<label className="GenKeystore-field">
<h4 className="GenKeystore-field-label">Password</h4>
<Input
isValid={isPasswordValid}
isVisible={isPasswordVisible}
<TogglablePassword
name="password"
value={password}
onChange={this.handleInput}
placeholder={translateRaw('Minimum 9 characters')}
handleInput={this.handleInput}
handleToggle={this.togglePassword}
isValid={isPasswordValid}
/>
</label>
@ -127,18 +112,6 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
);
}
private togglePrivateKey = () => {
this.setState({
isPrivateKeyVisible: !this.state.isPrivateKeyVisible
});
};
private togglePassword = () => {
this.setState({
isPasswordVisible: !this.state.isPasswordVisible
});
};
private handleInput = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget;
let { keystoreFile } = this.state;

View File

@ -0,0 +1,13 @@
@import 'common/sass/variables';
.TogglablePassword {
&-toggle {
cursor: pointer;
color: $gray;
transition: $transition;
&:hover {
color: $gray-darker;
}
}
}

View File

@ -0,0 +1,108 @@
// Either self contained, or controlled component for having a password field
// with a toggle to turn it into a visible text field.
// Pass `isVisible` and `handleToggleVisibility` to control the visibility
// yourself, otherwise all visibiility changes are managed in internal state.
import React from 'react';
import './TogglablePassword.scss';
interface Props {
// Shared props
value: string;
placeholder?: string;
name?: string;
disabled?: boolean;
ariaLabel?: string;
toggleAriaLabel?: string;
isValid?: boolean;
isVisible?: boolean;
// Textarea-only props
isTextareaWhenVisible?: boolean;
rows?: number;
onEnter?(): void;
// Shared callbacks
onChange?(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>): void;
handleToggleVisibility?(): void;
}
interface State {
isVisible: boolean;
}
export default class TogglablePassword extends React.PureComponent<Props, State> {
public state: State = {
isVisible: !!this.props.isVisible
};
public componentWillReceiveProps(nextProps: Props) {
if (this.props.isVisible !== nextProps.isVisible) {
this.setState({ isVisible: !!nextProps.isVisible });
}
}
public render() {
const {
value,
placeholder,
name,
disabled,
ariaLabel,
isTextareaWhenVisible,
isValid,
onChange,
handleToggleVisibility
} = this.props;
const { isVisible } = this.state;
const validClass =
isValid === null || isValid === undefined ? '' : isValid ? 'is-valid' : 'is-invalid';
return (
<div className="TogglablePassword input-group">
{isTextareaWhenVisible && isVisible ? (
<textarea
className={`form-control ${validClass}`}
value={value}
name={name}
disabled={disabled}
onChange={onChange}
onKeyDown={this.handleTextareaKeyDown}
placeholder={placeholder}
rows={this.props.rows || 3}
aria-label={ariaLabel}
/>
) : (
<input
value={value}
name={name}
disabled={disabled}
type={isVisible ? 'text' : 'password'}
className={`form-control ${validClass}`}
placeholder={placeholder}
onChange={onChange}
aria-label={ariaLabel}
/>
)}
<span
onClick={handleToggleVisibility || this.toggleVisibility}
aria-label="show private key"
role="button"
className="TogglablePassword-toggle input-group-addon"
>
<i className={`fa fa-${isVisible ? 'eye-slash' : 'eye'}`} />
</span>
</div>
);
}
private toggleVisibility = () => {
this.setState({ isVisible: !this.state.isVisible });
};
private handleTextareaKeyDown = (ev: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (this.props.onEnter && ev.keyCode === 13) {
ev.preventDefault();
this.props.onEnter();
}
};
}

View File

@ -9,6 +9,7 @@ import { getNetworkConfig } from 'selectors/config';
import { connect } from 'react-redux';
import { DPath } from 'config/dpaths';
import { getPaths, getSingleDPath } from 'utils/network';
import { TogglablePassword } from 'components';
interface Props {
onUnlock(param: any): void;
@ -43,13 +44,14 @@ class MnemonicDecryptClass extends Component<Props & StateProps, State> {
<div>
<div id="selectedTypeKey">
<div className="form-group">
<textarea
id="aria-private-key"
className={`form-control ${isValidMnemonic ? 'is-valid' : 'is-invalid'}`}
<TogglablePassword
value={phrase}
onChange={this.onMnemonicChange}
placeholder={translateRaw('x_Mnemonic')}
rows={4}
placeholder={translateRaw('x_Mnemonic')}
isValid={isValidMnemonic}
isTextareaWhenVisible={true}
onChange={this.onMnemonicChange}
onEnter={isValidMnemonic && this.onDWModalOpen}
/>
</div>
<div className="form-group">

View File

@ -2,6 +2,7 @@ import { isValidEncryptedPrivKey, isValidPrivKey } from 'libs/validators';
import { stripHexPrefix } from 'libs/values';
import React, { Component } from 'react';
import translate, { translateRaw } from 'translations';
import { TogglablePassword } from 'components';
export interface PrivateKeyValue {
key: string;
@ -17,7 +18,7 @@ interface Validated {
}
function validatePkeyAndPass(pkey: string, pass: string): Validated {
const fixedPkey = stripHexPrefix(pkey);
const fixedPkey = stripHexPrefix(pkey).trim();
const validPkey = isValidPrivKey(fixedPkey);
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
const isValidPkey = validPkey || validEncPkey;
@ -53,14 +54,14 @@ export class PrivateKeyDecrypt extends Component<Props> {
return (
<form id="selectedTypeKey" onSubmit={this.unlock}>
<div className="form-group">
<textarea
id="aria-private-key"
className={`form-control ${isValidPkey ? 'is-valid' : 'is-invalid'}`}
<TogglablePassword
value={key}
onChange={this.onPkeyChange}
onKeyDown={this.onKeyDown}
placeholder={translateRaw('x_PrivKey2')}
rows={4}
placeholder={translateRaw('x_PrivKey2')}
isValid={isValidPkey}
isTextareaWhenVisible={true}
onChange={this.onPkeyChange}
onEnter={this.props.onUnlock}
/>
</div>
{isValidPkey &&
@ -84,7 +85,7 @@ export class PrivateKeyDecrypt extends Component<Props> {
);
}
public onPkeyChange = (e: React.FormEvent<HTMLTextAreaElement>) => {
private onPkeyChange = (e: React.FormEvent<HTMLTextAreaElement | HTMLInputElement>) => {
const pkey = e.currentTarget.value;
const pass = this.props.value.password;
const { fixedPkey, valid } = validatePkeyAndPass(pkey, pass);
@ -92,7 +93,7 @@ export class PrivateKeyDecrypt extends Component<Props> {
this.props.onChange({ ...this.props.value, key: fixedPkey, valid });
};
public onPasswordChange = (e: React.FormEvent<HTMLInputElement>) => {
private onPasswordChange = (e: React.FormEvent<HTMLInputElement>) => {
// NOTE: Textareas don't support password type, so we replace the value
// with an equal length number of dots. On change, we replace
const pkey = this.props.value.key;
@ -106,7 +107,7 @@ export class PrivateKeyDecrypt extends Component<Props> {
});
};
public onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
private onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
if (e.keyCode === 13) {
this.unlock(e);
}

View File

@ -16,3 +16,5 @@ export { default as PaperWallet } from './PaperWallet';
export { default as AlphaAgreement } from './AlphaAgreement';
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
export { default as WalletDecrypt } from './WalletDecrypt';
export { default as TogglablePassword } from './TogglablePassword';
export { default as GenerateKeystoreModal } from './GenerateKeystoreModal';

View File

@ -1,9 +1,9 @@
import React, { Component } from 'react';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import { MINIMUM_PASSWORD_LENGTH } from 'config';
import './EnterPassword.scss';
import PasswordInput from './PasswordInput';
import { TogglablePassword } from 'components';
import Template from '../Template';
import './EnterPassword.scss';
interface Props {
continue(pw: string): void;
@ -12,17 +12,15 @@ interface Props {
interface State {
password: string;
isPasswordValid: boolean;
isPasswordVisible: boolean;
}
export default class EnterPassword extends Component<Props, State> {
public state = {
password: '',
isPasswordValid: false,
isPasswordVisible: false
isPasswordValid: false
};
public render() {
const { password, isPasswordValid, isPasswordVisible } = this.state;
const { password, isPasswordValid } = this.state;
return (
<Template>
@ -33,12 +31,13 @@ export default class EnterPassword extends Component<Props, State> {
<label className="EnterPw-password">
<h4 className="EnterPw-password-label">{translate('GEN_Label_1')}</h4>
<PasswordInput
password={password}
onPasswordChange={this.onPasswordChange}
isPasswordVisible={isPasswordVisible}
togglePassword={this.togglePassword}
isPasswordValid={isPasswordValid}
<TogglablePassword
value={password}
placeholder={translateRaw('GEN_Placeholder_1')}
ariaLabel={translateRaw('GEN_Aria_1')}
toggleAriaLabel={translateRaw('GEN_Aria_2')}
isValid={isPasswordValid}
onChange={this.onPasswordChange}
/>
</label>
@ -59,10 +58,6 @@ export default class EnterPassword extends Component<Props, State> {
this.props.continue(this.state.password);
};
private togglePassword = () => {
this.setState({ isPasswordVisible: !this.state.isPasswordVisible });
};
private onPasswordChange = (e: any) => {
const password = e.target.value;
this.setState({

View File

@ -1,46 +0,0 @@
import React, { Component } from 'react';
import { translateRaw } from 'translations';
interface Props {
password: string;
onPasswordChange: any;
togglePassword: any;
isPasswordVisible?: boolean;
isPasswordValid: boolean;
}
export default class PasswordInput extends Component<Props, {}> {
public render() {
const {
password,
isPasswordValid,
isPasswordVisible,
togglePassword,
onPasswordChange
} = this.props;
return (
<div>
<div>
<div className="input-group" style={{ width: '100%' }}>
<input
value={password}
name="password"
className={`form-control ${!isPasswordValid ? 'is-invalid' : ''}`}
type={isPasswordVisible ? 'text' : 'password'}
placeholder={translateRaw('GEN_Placeholder_1')}
aria-label={translateRaw('GEN_Aria_1')}
onChange={onPasswordChange}
/>
<span
onClick={togglePassword}
aria-label={translateRaw('GEN_Aria_2')}
role="button"
className="input-group-addon eye"
/>
</div>
</div>
</div>
);
}
}

View File

@ -1,23 +0,0 @@
@import 'common/sass/variables';
.KeystoreDetails {
&-title {
margin: $space auto $space * 2.5;
}
&-password,
&-key {
max-width: 40rem;
margin: 0 auto $space;
&-label {
margin-bottom: $space;
}
}
&-submit,
&-download {
max-width: 16rem;
margin: 0 auto $space * 3;
}
}

View File

@ -1,177 +0,0 @@
import React, { Component } from 'react';
import Template from './Template';
import KeystoreInput from './KeystoreInput';
import { fromPrivateKey, IFullWallet, fromV3 } from 'ethereumjs-wallet';
import { makeBlob } from 'utils/blob';
import { isValidPrivKey } from 'libs/validators';
import { stripHexPrefix } from 'libs/values';
import translate from 'translations';
import './KeystoreDetails.scss';
import { N_FACTOR } from 'config';
interface State {
secretKey: string;
password: string;
fileName: string;
isPasswordVisible: boolean;
isPrivateKeyVisible: boolean;
wallet: IFullWallet | null | undefined;
}
const initialState: State = {
secretKey: '',
password: '',
isPasswordVisible: false,
isPrivateKeyVisible: false,
fileName: '',
wallet: null
};
const minLength = (min: number) => (value: string) => !!value && value.length >= min;
const minLength9 = minLength(9);
class KeystoreDetails extends Component<{}, State> {
public state = initialState;
public componentWillUnmount() {
this.resetState();
}
public render() {
const {
secretKey,
isPasswordVisible,
isPrivateKeyVisible,
password,
wallet,
fileName
} = this.state;
const privateKey = stripHexPrefix(secretKey);
const privateKeyValid = isValidPrivKey(privateKey);
const content = (
<div className="KeystoreDetails">
<div>
<label className="KeystoreDetails-key">
<h4 className="KeystoreDetails-label">Private Key</h4>
<KeystoreInput
isValid={privateKeyValid}
isVisible={isPrivateKeyVisible}
name="secretKey"
value={secretKey}
handleInput={this.handleInput}
placeholder="Enter your saved private key here"
handleToggle={this.togglePrivateKey}
/>
</label>
</div>
<div>
<label className="KeystoreDetails-password">
<h4 className="KeystoreDetails-label">Password</h4>
<KeystoreInput
isValid={minLength9(password)}
isVisible={isPasswordVisible}
name="password"
value={password}
placeholder="Enter your encryption password here."
handleInput={this.handleInput}
handleToggle={this.togglePassword}
/>
</label>
</div>
{!wallet ? (
<button
onClick={this.handleKeystoreGeneration}
className="KeystoreDetails-submit btn btn-primary btn-block"
disabled={!privateKeyValid || !minLength9(password)}
>
Generate Keystore
</button>
) : this.runtimeKeystoreCheck() ? (
<a
onClick={this.resetState}
href={this.getBlob()}
className="KeystoreDetails-download btn btn-success btn-block"
aria-label="Download Keystore File (UTC / JSON · Recommended · Encrypted)"
aria-describedby={translate('x_KeystoreDesc')}
download={fileName}
>
Download Keystore
</a>
) : (
<p>
Error generating a valid keystore that matches your private key. In order to protect our
users, if our runtime check fails, we prevent you from downloading a potentially
corrupted wallet.
</p>
)}
</div>
);
return (
<div>
<Template title="Regenerate Keystore File" content={content} />
</div>
);
}
private togglePrivateKey = () => {
this.setState({
isPrivateKeyVisible: !this.state.isPrivateKeyVisible
});
};
private togglePassword = () => {
this.setState({
isPasswordVisible: !this.state.isPasswordVisible
});
};
private resetState = () => {
this.setState(initialState);
};
private handleKeystoreGeneration = () => {
const { secretKey } = this.state;
const removeChecksumPkey = stripHexPrefix(secretKey);
const keyBuffer = Buffer.from(removeChecksumPkey, 'hex');
const wallet = fromPrivateKey(keyBuffer);
const fileName = wallet.getV3Filename();
this.setState({
wallet,
fileName
});
};
private handleInput = (e: React.FormEvent<HTMLInputElement>) => {
const name = e.currentTarget.name;
const value = e.currentTarget.value;
if (name === 'secretKey') {
this.setState({
wallet: null
});
}
this.setState({ [name as any]: value });
};
private runtimeKeystoreCheck(): boolean {
const { wallet, password, secretKey } = this.state;
if (wallet) {
const keystore = wallet.toV3(password, { n: N_FACTOR });
const backToWallet = fromV3(keystore, password, true);
if (stripHexPrefix(backToWallet.getPrivateKeyString()) === secretKey) {
return true;
}
}
return false;
}
private getBlob() {
const { wallet, password } = this.state;
if (wallet) {
const keystore = wallet.toV3(password, { n: N_FACTOR });
return makeBlob('text/json;charset=UTF-8', keystore);
}
}
}
export default KeystoreDetails;

View File

@ -1,36 +0,0 @@
import React from 'react';
import classnames from 'classnames';
interface Props {
isValid: boolean;
isVisible: boolean;
name: string;
value: string;
placeholder: string;
handleInput(e: React.FormEvent<HTMLInputElement>): void;
handleToggle(): void;
}
const KeystoreInput: React.SFC<Props> = ({
isValid,
isVisible,
handleInput,
name,
value,
placeholder,
handleToggle
}) => (
<div className="input-group">
<input
className={classnames('form-control', isValid ? 'is-valid' : 'is-invalid')}
type={isVisible ? 'text' : 'password'}
name={name}
placeholder={placeholder}
value={value}
onChange={handleInput}
/>
<span onClick={handleToggle} role="button" className="input-group-addon eye" />
</div>
);
export default KeystoreInput;

View File

@ -1,16 +0,0 @@
import React from 'react';
interface Props {
content: React.ReactElement<any>;
title: string;
}
const RestoreKeystoreTemplate: React.SFC<Props> = ({ title, content }) => (
<div className="Tab-content">
<div className="Tab-content-pane text-center">
<h1>{title}</h1>
{content}
</div>
</div>
);
export default RestoreKeystoreTemplate;

View File

@ -1,11 +0,0 @@
import React from 'react';
import TabSection from 'containers/TabSection';
import KeystoreDetails from './components/KeystoreDetails';
const RestoreKeystore: React.SFC<{}> = () => (
<TabSection>
<KeystoreDetails />
</TabSection>
);
export default RestoreKeystore;

View File

@ -3,7 +3,7 @@ import translate, { translateRaw } from 'translations';
import { IWallet } from 'libs/wallet';
import { print } from 'components/PrintableWallet';
import { Identicon, QRCode } from 'components/ui';
import GenerateKeystoreModal from 'components/GenerateKeystoreModal';
import { GenerateKeystoreModal, TogglablePassword } from 'components';
import './WalletInfo.scss';
interface Props {
@ -55,20 +55,13 @@ export default class WalletInfo extends React.Component<Props, State> {
<div className="row form-group">
<div className="col-xs-12">
<label>{translate('x_PrivKey')}</label>
<div className="input-group">
<input
className="form-control"
disabled={true}
type={isPrivateKeyVisible ? 'text' : 'password'}
value={privateKey}
/>
<span
onClick={this.togglePrivateKey}
aria-label={translateRaw('GEN_Aria_2')}
role="button"
className="input-group-addon eye"
/>
</div>
<TogglablePassword
disabled={true}
value={privateKey}
isVisible={isPrivateKeyVisible}
toggleAriaLabel={translateRaw('GEN_Aria_2')}
handleToggleVisibility={this.togglePrivateKey}
/>
</div>
</div>
)}

View File

@ -5,10 +5,8 @@ import { default as SendTransaction } from './SendTransaction';
import { default as Swap } from './Swap';
import { default as ViewWallet } from './ViewWallet';
import { default as SignAndVerifyMessage } from './SignAndVerifyMessage';
import { default as RestoreKeystore } from './RestoreKeystore';
export default {
RestoreKeystore,
ENS,
GenerateWallet,
Help,