Skip to content
This repository has been archived by the owner on May 24, 2022. It is now read-only.

JSON Keyfile backup #101 #260

Merged
merged 34 commits into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f0939bd
WIP: Added JSON Keyfile dropzone logic.
pmespresso Nov 20, 2018
fb43acf
FIle input component
pmespresso Nov 20, 2018
72d8b71
this endpoint
pmespresso Nov 20, 2018
ddee8a8
move data through ui to export account api call
pmespresso Nov 20, 2018
20bb41f
saves backup as json
pmespresso Nov 20, 2018
4db5528
json encoding check, err handling
pmespresso Nov 20, 2018
6f54eea
center stuff
pmespresso Nov 20, 2018
0d19739
error handling
pmespresso Nov 20, 2018
7de6323
lint!
pmespresso Nov 20, 2018
6669bc2
lintgit add .
pmespresso Nov 20, 2018
e6a5b5f
browser filereader
pmespresso Nov 20, 2018
55a404d
Merge branch 'master' into yj-json-keyfile
pmespresso Nov 20, 2018
f526b12
final lint
pmespresso Nov 21, 2018
d372758
Merge branch 'yj-json-keyfile' of https://github.com/paritytech/fethe…
pmespresso Nov 21, 2018
18f66d5
change element pos, typos
pmespresso Nov 21, 2018
7a9a43f
import options screen
pmespresso Nov 21, 2018
3eb3ebf
move backup to separate screen, add 0x prefix
pmespresso Nov 22, 2018
c09d93c
Merge branch 'master' of https://github.com/paritytech/fether into yj…
pmespresso Nov 23, 2018
01d7b6c
accept any file type
pmespresso Nov 23, 2018
ab99135
enter to next step
pmespresso Nov 23, 2018
53d4617
call
pmespresso Nov 23, 2018
204bd0b
Merge branch 'yj-enter-behavior' into yj-json-keyfile
pmespresso Nov 23, 2018
11ed487
prefix cleaning, and dropzone reject callback
pmespresso Nov 24, 2018
a9b7fc2
add sane err msg for when keyfile uploaded is not valid
pmespresso Nov 24, 2018
8aa77c7
account confirmation and unlocking ux
pmespresso Nov 26, 2018
0de909d
goback from password screen
pmespresso Nov 26, 2018
c1e0608
add headers, style fixes, wording
pmespresso Nov 27, 2018
cea6215
Merge branch 'master' of https://github.com/paritytech/fether into yj…
pmespresso Nov 29, 2018
59bcb91
add comment about timeout after backup file
pmespresso Nov 29, 2018
d06861e
error handling
pmespresso Nov 29, 2018
7b6e3eb
center button
pmespresso Dec 6, 2018
9aa209d
revert to working version
pmespresso Dec 7, 2018
697f610
fix backup
pmespresso Dec 12, 2018
49b41cd
Merge branch 'master' into yj-json-keyfile
axelchalon Dec 12, 2018
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
3 changes: 2 additions & 1 deletion packages/fether-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"bignumber.js": "^4.1.0",
"debounce-promise": "^3.1.0",
"debug": "^3.1.0",
"fether-ui": "^0.1.2",
"fether-ui": "^0.1.1",
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
"file-saver": "^2.0.0",
"final-form": "^4.8.3",
"is-electron": "^2.1.0",
"localforage": "^1.7.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,125 @@

import React, { Component } from 'react';
import { AccountCard, Card, Form as FetherForm } from 'fether-ui';

import { inject, observer } from 'mobx-react';

@inject('createAccountStore')
@observer
class AccountRewritePhrase extends Component {
state = {
isLoading: false,
isFileValid: false,
json: null,
value: ''
};

handleChange = ({ target: { value } }) => {
this.setState({ value });
};

handleChangeFile = ({ target: { result } }) => {
try {
const json = JSON.parse(result);

const isFileValid =
json.address.length === 32 &&
typeof json.meta === 'object' &&
json.crpyto &&
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
json.crypto.cipher === 'aes-128-ctr';

this.setState({
isFileValid,
json
});
} catch (error) {
this.setState({
isFileValid: false,
json: null
});
console.error(error);
}
};

handleNextStep = async () => {
const {
history,
location: { pathname },
createAccountStore: { isImport, setPhrase }
createAccountStore: { isImport, isJSON, setJSON, setPhrase }
} = this.props;
const currentStep = pathname.slice(-1);
const { value } = this.state;
const { json, value } = this.state;

if (isJSON) {
this.setState({ isLoading: true });
await setJSON(json);
}

// If we're importing, derive address from recovery phrase when we submit
if (isImport) {
if (isImport && !isJSON) {
this.setState({ isLoading: true });
await setPhrase(value);
}

history.push(`/accounts/new/${+currentStep + 1}`);
};

toggleImportMethod = () => {
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
const { createAccountStore } = this.props;
createAccountStore.setIsJSON(!createAccountStore.isJSON);
};

render () {
const {
createAccountStore: { address, isImport, name },
createAccountStore: { address, isImport, isJSON, name },
history,
location: { pathname }
} = this.props;
const { value } = this.state;
const currentStep = pathname.slice(-1);
const body = [
<div key='createAccount'>
<div className='text'>
<div className='text -centered'>
{isImport ? (
<p>Type your Recovery phrase</p>
isJSON ? (
<div>
<p> Drop your JSON keyfile below </p>
<button onClick={this.toggleImportMethod} className='button'>
Use Seed Phrase
</button>
</div>
) : (
<div>
<p> Type your Recovery phrase </p>
<button onClick={this.toggleImportMethod} className='button'>
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
Use JSON Keyfile
</button>
</div>
)
) : (
<p>
Type your secret phrase to confirm that you wrote it down
correctly:
</p>
)}
</div>
<FetherForm.Field
as='textarea'
label='Recovery phrase'
onChange={this.handleChange}
required
value={value}
/>

{isJSON ? (
<FetherForm.InputFile
label='JSON Backup Keyfile'
onChangeFile={this.handleChangeFile}
required
value={value}
/>
) : (
<FetherForm.Field
as='textarea'
label='Recovery phrase'
onChange={this.handleChange}
required
value={value}
/>
)}

<nav className='form-nav -space-around'>
{currentStep > 1 && (
Expand Down Expand Up @@ -91,7 +151,7 @@ class AccountRewritePhrase extends Component {
const {
createAccountStore: { isImport, phrase }
} = this.props;
const { isLoading, value } = this.state;
const { isLoading, json, value } = this.state;

// If we are creating a new account, the button just checks the phrase has
// been correctly written by the user.
Expand All @@ -111,7 +171,7 @@ class AccountRewritePhrase extends Component {
return (
<button
className='button'
disabled={!value.length || isLoading}
disabled={(!value.length && !json) || isLoading}
onClick={this.handleNextStep}
>
Next
Expand Down
94 changes: 91 additions & 3 deletions packages/fether-react/src/Tokens/Tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import React, { PureComponent } from 'react';
import { AccountHeader } from 'fether-ui';
import React, { Component } from 'react';
import { AccountHeader, Form as FetherForm } from 'fether-ui';
import { accountsInfo$ } from '@parity/light.js';
import light from '@parity/light.js-react';
import { Link, Redirect, withRouter } from 'react-router-dom';
Expand All @@ -13,17 +13,95 @@ import Health from '../Health';
import TokensList from './TokensList';
import withAccount from '../utils/withAccount';

import { inject, observer } from 'mobx-react';

@withRouter
@withAccount
@light({
accountsInfo: accountsInfo$
})
class Tokens extends PureComponent {
@inject('createAccountStore')
@observer
class Tokens extends Component {
state = {
password: '',
toggleBackupScreen: false,
error: ''
};

handleGoToWhitelist = () => {
this.props.history.push(`/whitelist/${this.props.accountAddress}`);
};

handlePasswordChange = ({ target: { value } }) => {
this.setState({ password: value });
};

handleSubmit = event => {
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
const { accountAddress, createAccountStore, history } = this.props;
const { password } = this.state;

event.preventDefault();

const _this = this;
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
// api.parity.exportAccount
createAccountStore.backupAccount(accountAddress, password).then(res => {
if (res && res.type === 'ACCOUNT_ERROR') {
_this.toggleError(
res.text + ' Please check your password and try again.'
);
} else {
createAccountStore.clear();
history.push(`/accounts`);
}
});
};

toggleBackupScreen = () => {
const { toggleBackupScreen } = this.state;
this.setState({ toggleBackupScreen: !toggleBackupScreen });
};

toggleError = err => {
this.setState({
error: err
});
};

renderPasswordFormField = password => {
const { error } = this.state;

return (
<div>
<div className='text -centered'>
<button className='button' onClick={this.toggleBackupScreen}>
close
</button>
<p>Unlock your account:</p>
</div>
<fieldset className='form_fields -centered'>
<form key='createAccount' onSubmit={this.handleSubmit}>
<FetherForm.Field
label='Password'
onChange={this.handlePasswordChange}
required
type='password'
value={password}
/>

<p className='error'> {error} </p>

<button className='button -right' disabled={!password}>
Confirm Backup
</button>
</form>
</fieldset>
</div>
);
};

render () {
const { password, toggleBackupScreen } = this.state;
const { accountsInfo, accountAddress } = this.props;

// If the accountsInfo object is empty (i.e. no accounts), then we redirect
Expand All @@ -49,6 +127,16 @@ class Tokens extends PureComponent {
}
/>

<div className='center-md'>
{toggleBackupScreen ? (
this.renderPasswordFormField(password)
) : (
<button className='button' onClick={this.toggleBackupScreen}>
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
backup account
</button>
)}
</div>

<TokensList />

<nav className='footer-nav'>
Expand Down
18 changes: 18 additions & 0 deletions packages/fether-react/src/assets/sass/shared/_form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@
word-wrap: break-word;
}

.dropzone {
&.-sm {
height: 3.5rem;
}

&.-md {
height: 4.75rem;
}

&.-lg {
height: 8rem;
}

&.-xlg {
height: 12rem;
}
}

input[type='text'],
input[type='number'],
input[type='tel'],
Expand Down
37 changes: 37 additions & 0 deletions packages/fether-react/src/stores/createAccountStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import { action, observable } from 'mobx';

import Debug from '../utils/debug';
import parityStore from './parityStore';
import FileSaver from 'file-saver';

const debug = Debug('createAccountStore');

export class CreateAccountStore {
@observable
address = null;
@observable
json = {};
@observable
isImport = false; // Are we creating a new account, or importing via phrase?
@observable
isJSON = false; // Are we recovering an account from a JSON backup file/
@observable
name = ''; // Account name
@observable
phrase = null; // The 12-word seed phrase
Expand All @@ -28,6 +33,27 @@ export class CreateAccountStore {
this.setName('');
}

backupAccount = (address, password) => {
debug('Generating Backup JSON');

return parityStore.api.parity
.exportAccount(address, password)
.then(res => {
console.log('good, ', res);
pmespresso marked this conversation as resolved.
Show resolved Hide resolved

const blob = new window.Blob([JSON.stringify(res)], {
type: 'application/json; charset=utf-8'
});

console.log(blob);
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
FileSaver.saveAs(blob, `${res.address}.json`);
})
.catch(err => {
console.error('bad, ', err);
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
return err;
});
};

generateNewAccount = () => {
debug('Generating new account.');
return this.setPhrase(null)
Expand All @@ -37,6 +63,7 @@ export class CreateAccountStore {

saveAccountToParity = password => {
debug('Saving account to Parity.');
console.log(this.phrase);
pmespresso marked this conversation as resolved.
Show resolved Hide resolved
return parityStore.api.parity
.newAccountFromPhrase(this.phrase, password)
.then(() =>
Expand All @@ -54,11 +81,21 @@ export class CreateAccountStore {
this.address = address;
};

@action
setJSON = json => {
this.json = json;
};

@action
setIsImport = isImport => {
this.isImport = isImport;
};

@action
setIsJSON = isJSON => {
this.isJSON = isJSON;
};

@action
setName = name => {
this.name = name;
Expand Down
Loading