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:
parent
77ddf602cc
commit
7fe2888633
|
@ -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 |
|
@ -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 |
|
@ -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
|
// collapsable containers
|
||||||
.collapse-container {
|
.collapse-container {
|
||||||
h2,
|
h2,
|
||||||
|
@ -398,4 +370,4 @@ label small {
|
||||||
|
|
||||||
.ens-response {
|
.ens-response {
|
||||||
color: @gray;
|
color: @gray;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { generateKeystoreFileInfo, KeystoreFile } from 'utils/keystore';
|
import { generateKeystoreFileInfo, KeystoreFile } from 'utils/keystore';
|
||||||
import Modal from 'components/ui/Modal';
|
import Modal from 'components/ui/Modal';
|
||||||
import Input from './Input';
|
import { TogglablePassword } from 'components';
|
||||||
import translate, { translateRaw } from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
import { MINIMUM_PASSWORD_LENGTH } from 'config';
|
import { MINIMUM_PASSWORD_LENGTH } from 'config';
|
||||||
import { isValidPrivKey } from 'libs/validators';
|
import { isValidPrivKey } from 'libs/validators';
|
||||||
|
@ -16,8 +16,6 @@ interface Props {
|
||||||
interface State {
|
interface State {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
password: string;
|
password: string;
|
||||||
isPrivateKeyVisible: boolean;
|
|
||||||
isPasswordVisible: boolean;
|
|
||||||
keystoreFile: KeystoreFile | null;
|
keystoreFile: KeystoreFile | null;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
}
|
}
|
||||||
|
@ -25,8 +23,6 @@ interface State {
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
privateKey: '',
|
privateKey: '',
|
||||||
password: '',
|
password: '',
|
||||||
isPrivateKeyVisible: false,
|
|
||||||
isPasswordVisible: false,
|
|
||||||
keystoreFile: null,
|
keystoreFile: null,
|
||||||
hasError: false
|
hasError: false
|
||||||
};
|
};
|
||||||
|
@ -52,14 +48,7 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const { privateKey, password, keystoreFile, hasError } = this.state;
|
||||||
privateKey,
|
|
||||||
password,
|
|
||||||
isPrivateKeyVisible,
|
|
||||||
isPasswordVisible,
|
|
||||||
keystoreFile,
|
|
||||||
hasError
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const isPrivateKeyValid = isValidPrivKey(privateKey);
|
const isPrivateKeyValid = isValidPrivKey(privateKey);
|
||||||
const isPasswordValid = password.length >= MINIMUM_PASSWORD_LENGTH;
|
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}>
|
<form className="GenKeystore" onSubmit={this.handleSubmit}>
|
||||||
<label className="GenKeystore-field">
|
<label className="GenKeystore-field">
|
||||||
<h4 className="GenKeystore-field-label">Private Key</h4>
|
<h4 className="GenKeystore-field-label">Private Key</h4>
|
||||||
<Input
|
<TogglablePassword
|
||||||
isValid={isPrivateKeyValid}
|
|
||||||
isVisible={isPrivateKeyVisible}
|
|
||||||
name="privateKey"
|
name="privateKey"
|
||||||
value={privateKey}
|
value={privateKey}
|
||||||
handleInput={this.handleInput}
|
disabled={!!privateKey}
|
||||||
|
onChange={this.handleInput}
|
||||||
placeholder="f1d0e0789c6d40f39..."
|
placeholder="f1d0e0789c6d40f39..."
|
||||||
handleToggle={this.togglePrivateKey}
|
isValid={isPrivateKeyValid}
|
||||||
disabled={!!this.props.privateKey}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="GenKeystore-field">
|
<label className="GenKeystore-field">
|
||||||
<h4 className="GenKeystore-field-label">Password</h4>
|
<h4 className="GenKeystore-field-label">Password</h4>
|
||||||
<Input
|
<TogglablePassword
|
||||||
isValid={isPasswordValid}
|
|
||||||
isVisible={isPasswordVisible}
|
|
||||||
name="password"
|
name="password"
|
||||||
value={password}
|
value={password}
|
||||||
|
onChange={this.handleInput}
|
||||||
placeholder={translateRaw('Minimum 9 characters')}
|
placeholder={translateRaw('Minimum 9 characters')}
|
||||||
handleInput={this.handleInput}
|
isValid={isPasswordValid}
|
||||||
handleToggle={this.togglePassword}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</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>) => {
|
private handleInput = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = e.currentTarget;
|
const { name, value } = e.currentTarget;
|
||||||
let { keystoreFile } = this.state;
|
let { keystoreFile } = this.state;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
@import 'common/sass/variables';
|
||||||
|
|
||||||
|
.TogglablePassword {
|
||||||
|
&-toggle {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $gray;
|
||||||
|
transition: $transition;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $gray-darker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import { getNetworkConfig } from 'selectors/config';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { DPath } from 'config/dpaths';
|
import { DPath } from 'config/dpaths';
|
||||||
import { getPaths, getSingleDPath } from 'utils/network';
|
import { getPaths, getSingleDPath } from 'utils/network';
|
||||||
|
import { TogglablePassword } from 'components';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onUnlock(param: any): void;
|
onUnlock(param: any): void;
|
||||||
|
@ -43,13 +44,14 @@ class MnemonicDecryptClass extends Component<Props & StateProps, State> {
|
||||||
<div>
|
<div>
|
||||||
<div id="selectedTypeKey">
|
<div id="selectedTypeKey">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<textarea
|
<TogglablePassword
|
||||||
id="aria-private-key"
|
|
||||||
className={`form-control ${isValidMnemonic ? 'is-valid' : 'is-invalid'}`}
|
|
||||||
value={phrase}
|
value={phrase}
|
||||||
onChange={this.onMnemonicChange}
|
|
||||||
placeholder={translateRaw('x_Mnemonic')}
|
|
||||||
rows={4}
|
rows={4}
|
||||||
|
placeholder={translateRaw('x_Mnemonic')}
|
||||||
|
isValid={isValidMnemonic}
|
||||||
|
isTextareaWhenVisible={true}
|
||||||
|
onChange={this.onMnemonicChange}
|
||||||
|
onEnter={isValidMnemonic && this.onDWModalOpen}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { isValidEncryptedPrivKey, isValidPrivKey } from 'libs/validators';
|
||||||
import { stripHexPrefix } from 'libs/values';
|
import { stripHexPrefix } from 'libs/values';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate, { translateRaw } from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
|
import { TogglablePassword } from 'components';
|
||||||
|
|
||||||
export interface PrivateKeyValue {
|
export interface PrivateKeyValue {
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -17,7 +18,7 @@ interface Validated {
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePkeyAndPass(pkey: string, pass: string): Validated {
|
function validatePkeyAndPass(pkey: string, pass: string): Validated {
|
||||||
const fixedPkey = stripHexPrefix(pkey);
|
const fixedPkey = stripHexPrefix(pkey).trim();
|
||||||
const validPkey = isValidPrivKey(fixedPkey);
|
const validPkey = isValidPrivKey(fixedPkey);
|
||||||
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
|
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
|
||||||
const isValidPkey = validPkey || validEncPkey;
|
const isValidPkey = validPkey || validEncPkey;
|
||||||
|
@ -53,14 +54,14 @@ export class PrivateKeyDecrypt extends Component<Props> {
|
||||||
return (
|
return (
|
||||||
<form id="selectedTypeKey" onSubmit={this.unlock}>
|
<form id="selectedTypeKey" onSubmit={this.unlock}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<textarea
|
<TogglablePassword
|
||||||
id="aria-private-key"
|
|
||||||
className={`form-control ${isValidPkey ? 'is-valid' : 'is-invalid'}`}
|
|
||||||
value={key}
|
value={key}
|
||||||
onChange={this.onPkeyChange}
|
|
||||||
onKeyDown={this.onKeyDown}
|
|
||||||
placeholder={translateRaw('x_PrivKey2')}
|
|
||||||
rows={4}
|
rows={4}
|
||||||
|
placeholder={translateRaw('x_PrivKey2')}
|
||||||
|
isValid={isValidPkey}
|
||||||
|
isTextareaWhenVisible={true}
|
||||||
|
onChange={this.onPkeyChange}
|
||||||
|
onEnter={this.props.onUnlock}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isValidPkey &&
|
{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 pkey = e.currentTarget.value;
|
||||||
const pass = this.props.value.password;
|
const pass = this.props.value.password;
|
||||||
const { fixedPkey, valid } = validatePkeyAndPass(pkey, pass);
|
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 });
|
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
|
// NOTE: Textareas don't support password type, so we replace the value
|
||||||
// with an equal length number of dots. On change, we replace
|
// with an equal length number of dots. On change, we replace
|
||||||
const pkey = this.props.value.key;
|
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) {
|
if (e.keyCode === 13) {
|
||||||
this.unlock(e);
|
this.unlock(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,5 @@ export { default as PaperWallet } from './PaperWallet';
|
||||||
export { default as AlphaAgreement } from './AlphaAgreement';
|
export { default as AlphaAgreement } from './AlphaAgreement';
|
||||||
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
|
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
|
||||||
export { default as WalletDecrypt } from './WalletDecrypt';
|
export { default as WalletDecrypt } from './WalletDecrypt';
|
||||||
|
export { default as TogglablePassword } from './TogglablePassword';
|
||||||
|
export { default as GenerateKeystoreModal } from './GenerateKeystoreModal';
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
import { MINIMUM_PASSWORD_LENGTH } from 'config';
|
import { MINIMUM_PASSWORD_LENGTH } from 'config';
|
||||||
import './EnterPassword.scss';
|
import { TogglablePassword } from 'components';
|
||||||
import PasswordInput from './PasswordInput';
|
|
||||||
import Template from '../Template';
|
import Template from '../Template';
|
||||||
|
import './EnterPassword.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
continue(pw: string): void;
|
continue(pw: string): void;
|
||||||
|
@ -12,17 +12,15 @@ interface Props {
|
||||||
interface State {
|
interface State {
|
||||||
password: string;
|
password: string;
|
||||||
isPasswordValid: boolean;
|
isPasswordValid: boolean;
|
||||||
isPasswordVisible: boolean;
|
|
||||||
}
|
}
|
||||||
export default class EnterPassword extends Component<Props, State> {
|
export default class EnterPassword extends Component<Props, State> {
|
||||||
public state = {
|
public state = {
|
||||||
password: '',
|
password: '',
|
||||||
isPasswordValid: false,
|
isPasswordValid: false
|
||||||
isPasswordVisible: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { password, isPasswordValid, isPasswordVisible } = this.state;
|
const { password, isPasswordValid } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template>
|
<Template>
|
||||||
|
@ -33,12 +31,13 @@ export default class EnterPassword extends Component<Props, State> {
|
||||||
|
|
||||||
<label className="EnterPw-password">
|
<label className="EnterPw-password">
|
||||||
<h4 className="EnterPw-password-label">{translate('GEN_Label_1')}</h4>
|
<h4 className="EnterPw-password-label">{translate('GEN_Label_1')}</h4>
|
||||||
<PasswordInput
|
<TogglablePassword
|
||||||
password={password}
|
value={password}
|
||||||
onPasswordChange={this.onPasswordChange}
|
placeholder={translateRaw('GEN_Placeholder_1')}
|
||||||
isPasswordVisible={isPasswordVisible}
|
ariaLabel={translateRaw('GEN_Aria_1')}
|
||||||
togglePassword={this.togglePassword}
|
toggleAriaLabel={translateRaw('GEN_Aria_2')}
|
||||||
isPasswordValid={isPasswordValid}
|
isValid={isPasswordValid}
|
||||||
|
onChange={this.onPasswordChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -59,10 +58,6 @@ export default class EnterPassword extends Component<Props, State> {
|
||||||
this.props.continue(this.state.password);
|
this.props.continue(this.state.password);
|
||||||
};
|
};
|
||||||
|
|
||||||
private togglePassword = () => {
|
|
||||||
this.setState({ isPasswordVisible: !this.state.isPasswordVisible });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onPasswordChange = (e: any) => {
|
private onPasswordChange = (e: any) => {
|
||||||
const password = e.target.value;
|
const password = e.target.value;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -3,7 +3,7 @@ import translate, { translateRaw } from 'translations';
|
||||||
import { IWallet } from 'libs/wallet';
|
import { IWallet } from 'libs/wallet';
|
||||||
import { print } from 'components/PrintableWallet';
|
import { print } from 'components/PrintableWallet';
|
||||||
import { Identicon, QRCode } from 'components/ui';
|
import { Identicon, QRCode } from 'components/ui';
|
||||||
import GenerateKeystoreModal from 'components/GenerateKeystoreModal';
|
import { GenerateKeystoreModal, TogglablePassword } from 'components';
|
||||||
import './WalletInfo.scss';
|
import './WalletInfo.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -55,20 +55,13 @@ export default class WalletInfo extends React.Component<Props, State> {
|
||||||
<div className="row form-group">
|
<div className="row form-group">
|
||||||
<div className="col-xs-12">
|
<div className="col-xs-12">
|
||||||
<label>{translate('x_PrivKey')}</label>
|
<label>{translate('x_PrivKey')}</label>
|
||||||
<div className="input-group">
|
<TogglablePassword
|
||||||
<input
|
disabled={true}
|
||||||
className="form-control"
|
value={privateKey}
|
||||||
disabled={true}
|
isVisible={isPrivateKeyVisible}
|
||||||
type={isPrivateKeyVisible ? 'text' : 'password'}
|
toggleAriaLabel={translateRaw('GEN_Aria_2')}
|
||||||
value={privateKey}
|
handleToggleVisibility={this.togglePrivateKey}
|
||||||
/>
|
/>
|
||||||
<span
|
|
||||||
onClick={this.togglePrivateKey}
|
|
||||||
aria-label={translateRaw('GEN_Aria_2')}
|
|
||||||
role="button"
|
|
||||||
className="input-group-addon eye"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,10 +5,8 @@ import { default as SendTransaction } from './SendTransaction';
|
||||||
import { default as Swap } from './Swap';
|
import { default as Swap } from './Swap';
|
||||||
import { default as ViewWallet } from './ViewWallet';
|
import { default as ViewWallet } from './ViewWallet';
|
||||||
import { default as SignAndVerifyMessage } from './SignAndVerifyMessage';
|
import { default as SignAndVerifyMessage } from './SignAndVerifyMessage';
|
||||||
import { default as RestoreKeystore } from './RestoreKeystore';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
RestoreKeystore,
|
|
||||||
ENS,
|
ENS,
|
||||||
GenerateWallet,
|
GenerateWallet,
|
||||||
Help,
|
Help,
|
||||||
|
|
Loading…
Reference in New Issue