Merge branch 'master' into check-updates

This commit is contained in:
Jaco Greeff 2016-12-14 14:37:38 +01:00
commit 2588aea6b2
38 changed files with 506 additions and 154 deletions

2
Cargo.lock generated
View File

@ -1316,7 +1316,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#a8f4db707c521f71f093b62e3173b98144569c4e" source = "git+https://github.com/ethcore/js-precompiled.git#175003ae159b126302fd1a90dd875dc86d7adba0"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -10,14 +10,15 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"homesteadTransition": "0x118c30", "homesteadTransition": 1150000,
"eip150Transition": "0x2625a0", "eip150Transition": 2500000,
"eip155Transition": "0x7fffffffffffffff", "eip155Transition": 3000000,
"eip160Transition": "0x7fffffffffffffff", "eip160Transition": 3000000,
"ecip1010PauseTransition": 3000000,
"ecip1010ContinueTransition": 5000000,
"eip161abcTransition": "0x7fffffffffffffff", "eip161abcTransition": "0x7fffffffffffffff",
"eip161dTransition": "0x7fffffffffffffff", "eip161dTransition": "0x7fffffffffffffff"
"ecip1010PauseTransition": "0x2dc6c0",
"ecip1010ContinueTransition": "0x4c4b40"
} }
} }
}, },

View File

@ -9,12 +9,15 @@
"durationLimit": "0x0d", "durationLimit": "0x0d",
"blockReward": "0x4563918244F40000", "blockReward": "0x4563918244F40000",
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
"homesteadTransition": "0x789b0", "homesteadTransition": 494000,
"eip150Transition": "0x1b34d8", "eip150Transition": 1783000,
"eip155Transition": 1885000, "eip155Transition": 1915000,
"eip160Transition": 1885000, "eip160Transition": 1915000,
"eip161abcTransition": 1885000, "ecip1010PauseTransition": 1915000,
"eip161dTransition": 1885000 "ecip1010ContinueTransition": 3415000,
"eip161abcTransition": "0x7fffffffffffffff",
"eip161dTransition": "0x7fffffffffffffff"
} }
} }
}, },

@ -1 +1 @@
Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.121", "version": "0.2.122",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",

View File

@ -0,0 +1,33 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring';
export const postToServer = (query, isTestnet = false) => {
const port = isTestnet ? 28443 : 18443;
query = stringify(query);
return fetch(`https://email-verification.parity.io:${port}/?` + query, {
method: 'POST', mode: 'cors', cache: 'no-store'
})
.then((res) => {
return res.json().then((data) => {
if (res.ok) {
return data.message;
}
throw new Error(data.message || 'unknown error');
});
});
};

View File

@ -0,0 +1,23 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
export default (
<ul>
<li>todo</li>
</ul>
);

View File

@ -15,17 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import React from 'react';
export const termsOfService = (
<ul>
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilios privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
</ul>
);
export const postToServer = (query, isTestnet = false) => { export const postToServer = (query, isTestnet = false) => {
const port = isTestnet ? 8443 : 443; const port = isTestnet ? 8443 : 443;

View File

@ -0,0 +1,27 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
export default (
<ul>
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilios privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://ethcore.io/legal.html' }>https://ethcore.io/legal.html</a>.</li>
</ul>
);

View File

@ -0,0 +1 @@
[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]

View File

@ -19,6 +19,7 @@ import basiccoin from './basiccoin.json';
import basiccoinmanager from './basiccoinmanager.json'; import basiccoinmanager from './basiccoinmanager.json';
import dappreg from './dappreg.json'; import dappreg from './dappreg.json';
import eip20 from './eip20.json'; import eip20 from './eip20.json';
import emailverification from './email-verification.json';
import gavcoin from './gavcoin.json'; import gavcoin from './gavcoin.json';
import githubhint from './githubhint.json'; import githubhint from './githubhint.json';
import owned from './owned.json'; import owned from './owned.json';
@ -34,6 +35,7 @@ export {
basiccoinmanager, basiccoinmanager,
dappreg, dappreg,
eip20, eip20,
emailverification,
gavcoin, gavcoin,
githubhint, githubhint,
owned, owned,

View File

@ -19,7 +19,7 @@ import Registry from './registry';
import SignatureReg from './signaturereg'; import SignatureReg from './signaturereg';
import TokenReg from './tokenreg'; import TokenReg from './tokenreg';
import GithubHint from './githubhint'; import GithubHint from './githubhint';
import * as smsVerification from './sms-verification'; import * as verification from './verification';
import BadgeReg from './badgereg'; import BadgeReg from './badgereg';
let instance = null; let instance = null;
@ -58,7 +58,11 @@ export default class Contracts {
} }
get smsVerification () { get smsVerification () {
return smsVerification; return verification;
}
get emailVerification () {
return verification;
} }
static create (api) { static create (api) {

View File

@ -15,10 +15,6 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.list li {
padding: .1em 0;
}
.spacing { .spacing {
margin-top: 1.5em; margin-top: 1.5em;
} }

View File

@ -25,41 +25,33 @@ import { fromWei } from '~/api/util/wei';
import { Form, Input } from '~/ui'; import { Form, Input } from '~/ui';
import { nullableProptype } from '~/util/proptypes'; import { nullableProptype } from '~/util/proptypes';
import { termsOfService } from '../../../3rdparty/sms-verification'; import smsTermsOfService from '~/3rdparty/sms-verification/terms-of-service';
import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service';
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
import styles from './gatherData.css'; import styles from './gatherData.css';
export default class GatherData extends Component { export default class GatherData extends Component {
static propTypes = { static propTypes = {
fee: React.PropTypes.instanceOf(BigNumber), fee: React.PropTypes.instanceOf(BigNumber),
isNumberValid: PropTypes.bool.isRequired, method: PropTypes.string.isRequired,
fields: PropTypes.array.isRequired,
isVerified: nullableProptype(PropTypes.bool.isRequired), isVerified: nullableProptype(PropTypes.bool.isRequired),
hasRequested: nullableProptype(PropTypes.bool.isRequired), hasRequested: nullableProptype(PropTypes.bool.isRequired),
setNumber: PropTypes.func.isRequired,
setConsentGiven: PropTypes.func.isRequired setConsentGiven: PropTypes.func.isRequired
} }
render () { render () {
const { isNumberValid, isVerified } = this.props; const { method, isVerified } = this.props;
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
return ( return (
<Form> <Form>
<p>The following steps will let you prove that you control both an account and a phone number.</p> { howItWorks }
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via SMS is the solution to this puzzle.</li>
</ol>
{ this.renderFee() } { this.renderFee() }
{ this.renderCertified() } { this.renderCertified() }
{ this.renderRequested() } { this.renderRequested() }
<Input { this.renderFields() }
label={ 'phone number in international format' }
hint={ 'the SMS will be sent to this number' }
error={ isNumberValid ? null : 'invalid number' }
disabled={ isVerified }
onChange={ this.numberOnChange }
onSubmit={ this.numberOnSubmit }
/>
<Checkbox <Checkbox
className={ styles.spacing } className={ styles.spacing }
label={ 'I agree to the terms and conditions below.' } label={ 'I agree to the terms and conditions below.' }
@ -136,12 +128,28 @@ export default class GatherData extends Component {
); );
} }
numberOnSubmit = (value) => { renderFields () {
this.props.setNumber(value); const { isVerified, fields } = this.props;
}
numberOnChange = (_, value) => { const rendered = fields.map((field) => {
this.props.setNumber(value); const onChange = (_, v) => {
field.onChange(v);
};
const onSubmit = field.onChange;
return (
<Input
key={ field.key }
label={ field.label }
hint={ field.hint }
error={ field.error }
disabled={ isVerified }
onChange={ onChange }
onSubmit={ onSubmit }
/>
);
});
return (<div>{rendered}</div>);
} }
consentOnChange = (_, consentGiven) => { consentOnChange = (_, consentGiven) => {

View File

@ -20,20 +20,25 @@ import { Form, Input } from '~/ui';
export default class QueryCode extends Component { export default class QueryCode extends Component {
static propTypes = { static propTypes = {
number: PropTypes.string.isRequired, receiver: PropTypes.string.isRequired,
hint: PropTypes.string,
isCodeValid: PropTypes.bool.isRequired, isCodeValid: PropTypes.bool.isRequired,
setCode: PropTypes.func.isRequired setCode: PropTypes.func.isRequired
} }
static defaultProps = {
hint: 'Enter the code you received.'
}
render () { render () {
const { number, isCodeValid } = this.props; const { receiver, hint, isCodeValid } = this.props;
return ( return (
<Form> <Form>
<p>The verification code has been sent to { number }.</p> <p>The verification code has been sent to { receiver }.</p>
<Input <Input
label={ 'verification code' } label={ 'verification code' }
hint={ 'Enter the code you received via SMS.' } hint={ hint }
error={ isCodeValid ? null : 'invalid code' } error={ isCodeValid ? null : 'invalid code' }
onChange={ this.onChange } onChange={ this.onChange }
onSubmit={ this.onSubmit } onSubmit={ this.onSubmit }

View File

@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { nullableProptype } from '~/util/proptypes'; import { nullableProptype } from '~/util/proptypes';
import TxHash from '~/ui/TxHash'; import TxHash from '~/ui/TxHash';
import { import {
POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS POSTING_REQUEST, POSTED_REQUEST, REQUESTING_CODE
} from '../store'; } from '../store';
import styles from './sendRequest.css'; import styles from './sendRequest.css';
@ -45,9 +45,9 @@ export default class SendRequest extends Component {
</div> </div>
); );
case REQUESTING_SMS: case REQUESTING_CODE:
return ( return (
<p>Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.</p> <p>Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.</p>
); );
default: default:

View File

@ -0,0 +1,70 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observable, computed, action } from 'mobx';
import { sha3 } from '~/api/util/sha3';
import EmailVerificationABI from '~/contracts/abi/email-verification.json';
import VerificationStore, {
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
} from './store';
import { postToServer } from '../../3rdparty/email-verification';
export default class EmailVerificationStore extends VerificationStore {
@observable email = '';
@computed get isEmailValid () {
// See https://davidcel.is/posts/stop-validating-email-addresses-with-regex/
return this.email && this.email.indexOf('@') >= 0;
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isEmailValid && this.consentGiven;
case QUERY_CODE:
return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
return !!this.confirmationTx;
default:
return false;
}
}
constructor (api, account, isTestnet) {
super(api, EmailVerificationABI, 'emailverification3', account, isTestnet);
}
requestValues = () => [ sha3(this.email) ]
@action setEmail = (email) => {
this.email = email;
}
requestCode = () => {
const { email, account, isTestnet } = this;
return postToServer({ email, address: account }, isTestnet);
}
}

View File

@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import styles from './verification.css';
export const howSMSVerificationWorks = (
<div>
<p>The following steps will let you prove that you control both an account and a phone number.</p>
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via SMS is the solution to this puzzle.</li>
</ol>
</div>
);
export const howEmailVerificationWorks = (
<div>
<p>The following steps will let you prove that you control both an account and an e-mail address.</p>
<ol className={ styles.list }>
<li>You send a verification request to a specific contract.</li>
<li>Our server puts a puzzle into this contract.</li>
<li>The code you receive via e-mail is the solution to this puzzle.</li>
</ol>
</div>
);

View File

@ -1,4 +1,4 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity. // This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify // Parity is free software: you can redistribute it and/or modify
@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './SMSVerification'; export default from './verification';

View File

@ -0,0 +1,67 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observable, computed, action } from 'mobx';
import phone from 'phoneformat.js';
import SMSVerificationABI from '~/contracts/abi/sms-verification.json';
import VerificationStore, {
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
} from './store';
import { postToServer } from '../../3rdparty/sms-verification';
export default class SMSVerificationStore extends VerificationStore {
@observable number = '';
@computed get isNumberValid () {
return phone.isValidNumber(this.number);
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isNumberValid && this.consentGiven;
case QUERY_CODE:
return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
return !!this.confirmationTx;
default:
return false;
}
}
constructor (api, account, isTestnet) {
super(api, SMSVerificationABI, 'smsverification', account, isTestnet);
}
@action setNumber = (number) => {
this.number = number;
}
requestCode = () => {
const { number, account, isTestnet } = this;
return postToServer({ number, address: account }, isTestnet);
}
}

View File

@ -14,21 +14,19 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observable, computed, autorun, action } from 'mobx'; import { observable, autorun, action } from 'mobx';
import phone from 'phoneformat.js';
import { sha3 } from '~/api/util/sha3'; import { sha3 } from '~/api/util/sha3';
import Contract from '~/api/contract';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification';
import { postToServer } from '~/3rdparty/sms-verification';
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
export const LOADING = 'fetching-contract'; export const LOADING = 'fetching-contract';
export const QUERY_DATA = 'query-data'; export const QUERY_DATA = 'query-data';
export const POSTING_REQUEST = 'posting-request'; export const POSTING_REQUEST = 'posting-request';
export const POSTED_REQUEST = 'posted-request'; export const POSTED_REQUEST = 'posted-request';
export const REQUESTING_SMS = 'requesting-sms'; export const REQUESTING_CODE = 'requesting-code';
export const QUERY_CODE = 'query-code'; export const QUERY_CODE = 'query-code';
export const POSTING_CONFIRMATION = 'posting-confirmation'; export const POSTING_CONFIRMATION = 'posting-confirmation';
export const POSTED_CONFIRMATION = 'posted-confirmation'; export const POSTED_CONFIRMATION = 'posted-confirmation';
@ -43,56 +41,30 @@ export default class VerificationStore {
@observable isVerified = null; @observable isVerified = null;
@observable hasRequested = null; @observable hasRequested = null;
@observable consentGiven = false; @observable consentGiven = false;
@observable number = '';
@observable requestTx = null; @observable requestTx = null;
@observable code = ''; @observable code = '';
@observable isCodeValid = null; @observable isCodeValid = null;
@observable confirmationTx = null; @observable confirmationTx = null;
@computed get isNumberValid () { constructor (api, abi, name, account, isTestnet) {
return phone.isValidNumber(this.number);
}
@computed get isStepValid () {
if (this.step === DONE) {
return true;
}
if (this.error) {
return false;
}
switch (this.step) {
case LOADING:
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isNumberValid && this.consentGiven;
case QUERY_CODE:
return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
return !!this.confirmationTx;
default:
return false;
}
}
constructor (api, account, isTestnet) {
this.api = api; this.api = api;
this.account = account; this.account = account;
this.isTestnet = isTestnet; this.isTestnet = isTestnet;
this.step = LOADING; this.step = LOADING;
Contracts.create(api).registry.getContract('smsverification') Contracts.get().badgeReg.fetchCertifier(name)
.then((contract) => { .then(({ address }) => {
this.contract = contract; this.contract = new Contract(api, abi).at(address);
this.load(); this.load();
}) })
.catch((err) => { .catch((err) => {
console.error('error', err);
this.error = 'Failed to fetch the contract: ' + err.message; this.error = 'Failed to fetch the contract: ' + err.message;
}); });
autorun(() => { autorun(() => {
if (this.error) { if (this.error) {
console.error('sms verification: ' + this.error); console.error('verification: ' + this.error);
} }
}); });
} }
@ -135,10 +107,6 @@ export default class VerificationStore {
}); });
} }
@action setNumber = (number) => {
this.number = number;
}
@action setConsentGiven = (consentGiven) => { @action setConsentGiven = (consentGiven) => {
this.consentGiven = consentGiven; this.consentGiven = consentGiven;
} }
@ -166,19 +134,22 @@ export default class VerificationStore {
}); });
} }
requestValues = () => []
@action sendRequest = () => { @action sendRequest = () => {
const { api, account, contract, fee, number, hasRequested } = this; const { api, account, contract, fee, hasRequested } = this;
const request = contract.functions.find((fn) => fn.name === 'request'); const request = contract.functions.find((fn) => fn.name === 'request');
const options = { from: account, value: fee.toString() }; const options = { from: account, value: fee.toString() };
const values = this.requestValues();
let chain = Promise.resolve(); let chain = Promise.resolve();
if (!hasRequested) { if (!hasRequested) {
this.step = POSTING_REQUEST; this.step = POSTING_REQUEST;
chain = request.estimateGas(options, []) chain = request.estimateGas(options, values)
.then((gas) => { .then((gas) => {
options.gas = gas.mul(1.2).toFixed(0); options.gas = gas.mul(1.2).toFixed(0);
return request.postTransaction(options, []); return request.postTransaction(options, values);
}) })
.then((handle) => { .then((handle) => {
// TODO: The "request rejected" error doesn't have any property to // TODO: The "request rejected" error doesn't have any property to
@ -200,18 +171,15 @@ export default class VerificationStore {
chain chain
.then(() => { .then(() => {
return api.parity.netChain(); this.step = REQUESTING_CODE;
}) return this.requestCode();
.then((chain) => {
this.step = REQUESTING_SMS;
return postToServer({ number, address: account }, this.isTestnet);
}) })
.then(() => awaitPuzzle(api, contract, account)) .then(() => awaitPuzzle(api, contract, account))
.then(() => { .then(() => {
this.step = QUERY_CODE; this.step = QUERY_CODE;
}) })
.catch((err) => { .catch((err) => {
this.error = 'Failed to request a confirmation SMS: ' + err.message; this.error = 'Failed to request a confirmation code: ' + err.message;
}); });
} }

View File

@ -0,0 +1,25 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.noSpacing {
margin-top: 0;
margin-bottom: 0;
}
.list li {
padding: .1em 0;
}

View File

@ -20,12 +20,27 @@ import DoneIcon from 'material-ui/svg-icons/action/done-all';
import CancelIcon from 'material-ui/svg-icons/content/clear'; import CancelIcon from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '~/ui'; import { Button, IdentityIcon, Modal } from '~/ui';
import RadioButtons from '~/ui/Form/RadioButtons';
import { nullableProptype } from '~/util/proptypes';
import styles from './verification.css';
const methods = {
sms: {
label: 'SMS Verification', key: 0, value: 'sms',
description: (<p className={ styles.noSpacing }>It will be stored on the blockchain that you control a phone number (not <em>which</em>).</p>)
},
email: {
label: 'E-mail Verification', key: 1, value: 'email',
description: (<p className={ styles.noSpacing }>The hash of the e-mail address you prove control over will be stored on the blockchain.</p>)
}
};
import { import {
LOADING, LOADING,
QUERY_DATA, QUERY_DATA,
POSTING_REQUEST, POSTED_REQUEST, POSTING_REQUEST, POSTED_REQUEST,
REQUESTING_SMS, QUERY_CODE, REQUESTING_CODE, QUERY_CODE,
POSTING_CONFIRMATION, POSTED_CONFIRMATION, POSTING_CONFIRMATION, POSTED_CONFIRMATION,
DONE DONE
} from './store'; } from './store';
@ -37,34 +52,44 @@ import SendConfirmation from './SendConfirmation';
import Done from './Done'; import Done from './Done';
@observer @observer
export default class SMSVerification extends Component { export default class Verification extends Component {
static propTypes = { static propTypes = {
store: PropTypes.any.isRequired, store: nullableProptype(PropTypes.object.isRequired),
account: PropTypes.string.isRequired, account: PropTypes.string.isRequired,
onSelectMethod: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired onClose: PropTypes.func.isRequired
} }
static phases = { // mapping (store steps -> steps) static phases = { // mapping (store steps -> steps)
[LOADING]: 0, [LOADING]: 1, [QUERY_DATA]: 1,
[QUERY_DATA]: 1, [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2,
[POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2,
[QUERY_CODE]: 3, [QUERY_CODE]: 3,
[POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4,
[DONE]: 5 [DONE]: 5
} }
state = {
method: 'sms'
};
render () { render () {
const phase = SMSVerification.phases[this.props.store.step]; const { store } = this.props;
const { error, isStepValid } = this.props.store; let phase = 0; let error = false; let isStepValid = true;
if (store) {
phase = Verification.phases[store.step];
error = store.error;
isStepValid = store.isStepValid;
}
return ( return (
<Modal <Modal
actions={ this.renderDialogActions(phase, error, isStepValid) } actions={ this.renderDialogActions(phase, error, isStepValid) }
title='verify your account via SMS' title='verify your account'
visible visible
current={ phase } current={ phase }
steps={ ['Prepare', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] } steps={ ['Method', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
waiting={ error ? [] : [ 0, 2, 4 ] } waiting={ error ? [] : [ 2, 4 ] }
> >
{ this.renderStep(phase, error) } { this.renderStep(phase, error) }
</Modal> </Modal>
@ -101,6 +126,13 @@ export default class SMSVerification extends Component {
let action = () => {}; let action = () => {};
switch (phase) { switch (phase) {
case 0:
action = () => {
const { onSelectMethod } = this.props;
const { method } = this.state;
onSelectMethod(method);
};
break;
case 1: case 1:
action = store.sendRequest; action = store.sendRequest;
break; break;
@ -133,26 +165,58 @@ export default class SMSVerification extends Component {
return (<p>{ error }</p>); return (<p>{ error }</p>);
} }
const { method } = this.state;
if (phase === 0) {
const values = Object.values(methods);
const value = values.findIndex((v) => v.value === method);
return (
<RadioButtons
value={ value < 0 ? 0 : value }
values={ values }
onChange={ this.selectMethod }
/>
);
}
const { const {
step, step,
fee, number, isNumberValid, isVerified, hasRequested, fee, isVerified, hasRequested,
requestTx, isCodeValid, confirmationTx, requestTx, isCodeValid, confirmationTx,
setCode setCode
} = this.props.store; } = this.props.store;
switch (phase) { switch (phase) {
case 0:
return (
<p>Loading SMS Verification.</p>
);
case 1: case 1:
const { setNumber, setConsentGiven } = this.props.store; if (step === LOADING) {
return (<p>Loading verification data.</p>);
}
const { setConsentGiven } = this.props.store;
const fields = [];
if (method === 'sms') {
fields.push({
key: 'number',
label: 'phone number in international format',
hint: 'the SMS will be sent to this number',
error: this.props.store.isNumberValid ? null : 'invalid number',
onChange: this.props.store.setNumber
});
} else if (method === 'email') {
fields.push({
key: 'email',
label: 'email address',
hint: 'the code will be sent to this address',
error: this.props.store.isEmailValid ? null : 'invalid email',
onChange: this.props.store.setEmail
});
}
return ( return (
<GatherData <GatherData
fee={ fee } isNumberValid={ isNumberValid } method={ method } fields={ fields }
isVerified={ isVerified } hasRequested={ hasRequested } fee={ fee } isVerified={ isVerified } hasRequested={ hasRequested }
setNumber={ setNumber } setConsentGiven={ setConsentGiven } setConsentGiven={ setConsentGiven }
/> />
); );
@ -162,9 +226,19 @@ export default class SMSVerification extends Component {
); );
case 3: case 3:
let receiver, hint;
if (method === 'sms') {
receiver = this.props.store.number;
hint = 'Enter the code you received via SMS.';
} else if (method === 'email') {
receiver = this.props.store.email;
hint = 'Enter the code you received via e-mail.';
}
return ( return (
<QueryCode <QueryCode
number={ number } fee={ fee } isCodeValid={ isCodeValid } receiver={ receiver }
hint={ hint }
isCodeValid={ isCodeValid }
setCode={ setCode } setCode={ setCode }
/> />
); );
@ -183,4 +257,8 @@ export default class SMSVerification extends Component {
return null; return null;
} }
} }
selectMethod = (choice, i) => {
this.setState({ method: choice.value });
}
} }

View File

@ -26,7 +26,7 @@ import FirstRun from './FirstRun';
import LoadContract from './LoadContract'; import LoadContract from './LoadContract';
import SaveContract from './SaveContract'; import SaveContract from './SaveContract';
import Shapeshift from './Shapeshift'; import Shapeshift from './Shapeshift';
import SMSVerification from './SMSVerification'; import Verification from './Verification';
import Transfer from './Transfer'; import Transfer from './Transfer';
import PasswordManager from './PasswordManager'; import PasswordManager from './PasswordManager';
import UpgradeParity from './UpgradeParity'; import UpgradeParity from './UpgradeParity';
@ -45,7 +45,7 @@ export {
LoadContract, LoadContract,
SaveContract, SaveContract,
Shapeshift, Shapeshift,
SMSVerification, Verification,
Transfer, Transfer,
PasswordManager, PasswordManager,
UpgradeParity, UpgradeParity,

View File

@ -23,7 +23,7 @@ import ContentSend from 'material-ui/svg-icons/content/send';
import LockIcon from 'material-ui/svg-icons/action/lock'; import LockIcon from 'material-ui/svg-icons/action/lock';
import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals'; import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
import { Actionbar, Button, Page } from '~/ui'; import { Actionbar, Button, Page } from '~/ui';
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png'; import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
@ -32,7 +32,8 @@ import Header from './Header';
import Transactions from './Transactions'; import Transactions from './Transactions';
import { setVisibleAccounts } from '~/redux/providers/personalActions'; import { setVisibleAccounts } from '~/redux/providers/personalActions';
import VerificationStore from '~/modals/SMSVerification/store'; import SMSVerificationStore from '~/modals/Verification/sms-store';
import EmailVerificationStore from '~/modals/Verification/email-store';
import styles from './account.css'; import styles from './account.css';
@ -72,15 +73,6 @@ class Account extends Component {
if (prevAddress !== nextAddress) { if (prevAddress !== nextAddress) {
this.setVisibleAccounts(nextProps); this.setVisibleAccounts(nextProps);
} }
const { isTestnet } = nextProps;
if (typeof isTestnet === 'boolean' && !this.state.verificationStore) {
const { api } = this.context;
const { address } = nextProps.params;
this.setState({
verificationStore: new VerificationStore(api, address, isTestnet)
});
}
} }
componentWillUnmount () { componentWillUnmount () {
@ -228,8 +220,9 @@ class Account extends Component {
const { address } = this.props.params; const { address } = this.props.params;
return ( return (
<SMSVerification <Verification
store={ store } account={ address } store={ store } account={ address }
onSelectMethod={ this.selectVerificationMethod }
onClose={ this.onVerificationClose } onClose={ this.onVerificationClose }
/> />
); );
@ -303,6 +296,22 @@ class Account extends Component {
this.setState({ showVerificationDialog: true }); this.setState({ showVerificationDialog: true });
} }
selectVerificationMethod = (name) => {
const { isTestnet } = this.props;
if (typeof isTestnet !== 'boolean' || this.state.verificationStore) return;
const { api } = this.context;
const { address } = this.props.params;
let verificationStore = null;
if (name === 'sms') {
verificationStore = new SMSVerificationStore(api, address, isTestnet);
} else if (name === 'email') {
verificationStore = new EmailVerificationStore(api, address, isTestnet);
}
this.setState({ verificationStore });
}
onVerificationClose = () => { onVerificationClose = () => {
this.setState({ showVerificationDialog: false }); this.setState({ showVerificationDialog: false });
} }

View File

@ -280,7 +280,7 @@ Footprint Options:
of all accounts and storage keys. Doubles the size of all accounts and storage keys. Doubles the size
of the state database. BOOL may be one of on, off of the state database. BOOL may be one of on, off
or auto. (default: {flag_fat_db}) or auto. (default: {flag_fat_db})
--scale-verifiers Automatically scale amount of verifier threads based on --scale-verifiers Automatically scale amount of verifier threads based on
workload. Not guaranteed to be faster. workload. Not guaranteed to be faster.
(default: {flag_scale_verifiers}) (default: {flag_scale_verifiers})
--num-verifiers INT Amount of verifier threads to use or to begin with, if verifier --num-verifiers INT Amount of verifier threads to use or to begin with, if verifier

View File

@ -49,7 +49,7 @@ impl str::FromStr for SpecType {
let spec = match s { let spec = match s {
"frontier" | "homestead" | "mainnet" => SpecType::Mainnet, "frontier" | "homestead" | "mainnet" => SpecType::Mainnet,
"frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic, "frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic,
"morden" | "testnet" => SpecType::Testnet, "morden" | "testnet" | "classic-testnet" => SpecType::Testnet,
"ropsten" => SpecType::Ropsten, "ropsten" => SpecType::Ropsten,
"olympic" => SpecType::Olympic, "olympic" => SpecType::Olympic,
"expanse" => SpecType::Expanse, "expanse" => SpecType::Expanse,
@ -288,6 +288,8 @@ mod tests {
assert_eq!(SpecType::Testnet, "morden".parse().unwrap()); assert_eq!(SpecType::Testnet, "morden".parse().unwrap());
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap()); assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
assert_eq!(SpecType::Olympic, "olympic".parse().unwrap()); assert_eq!(SpecType::Olympic, "olympic".parse().unwrap());
assert_eq!(SpecType::Classic, "classic".parse().unwrap());
assert_eq!(SpecType::Testnet, "classic-testnet".parse().unwrap());
} }
#[test] #[test]