diff --git a/Cargo.lock b/Cargo.lock index 395ac820b..9ed1025a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,7 +1316,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" 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 = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index 7c1e9454e..a08dde33b 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -10,14 +10,15 @@ "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", - "homesteadTransition": "0x118c30", - "eip150Transition": "0x2625a0", - "eip155Transition": "0x7fffffffffffffff", - "eip160Transition": "0x7fffffffffffffff", + "homesteadTransition": 1150000, + "eip150Transition": 2500000, + "eip155Transition": 3000000, + "eip160Transition": 3000000, + "ecip1010PauseTransition": 3000000, + "ecip1010ContinueTransition": 5000000, + "eip161abcTransition": "0x7fffffffffffffff", - "eip161dTransition": "0x7fffffffffffffff", - "ecip1010PauseTransition": "0x2dc6c0", - "ecip1010ContinueTransition": "0x4c4b40" + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 6e725e8bf..d24e0ec0c 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -9,12 +9,15 @@ "durationLimit": "0x0d", "blockReward": "0x4563918244F40000", "registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d", - "homesteadTransition": "0x789b0", - "eip150Transition": "0x1b34d8", - "eip155Transition": 1885000, - "eip160Transition": 1885000, - "eip161abcTransition": 1885000, - "eip161dTransition": 1885000 + "homesteadTransition": 494000, + "eip150Transition": 1783000, + "eip155Transition": 1915000, + "eip160Transition": 1915000, + "ecip1010PauseTransition": 1915000, + "ecip1010ContinueTransition": 3415000, + + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index e8f4624b7..9028c4801 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be +Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 diff --git a/js/package.json b/js/package.json index 9e7302bf0..0cbcecf2a 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.121", + "version": "0.2.122", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/src/3rdparty/email-verification/index.js b/js/src/3rdparty/email-verification/index.js new file mode 100644 index 000000000..5b81f3f95 --- /dev/null +++ b/js/src/3rdparty/email-verification/index.js @@ -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 . + +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'); + }); + }); +}; diff --git a/js/src/3rdparty/email-verification/terms-of-service.js b/js/src/3rdparty/email-verification/terms-of-service.js new file mode 100644 index 000000000..263b7e8f0 --- /dev/null +++ b/js/src/3rdparty/email-verification/terms-of-service.js @@ -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 . + +import React from 'react'; + +export default ( +
    +
  • todo
  • +
+); diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js index c50b2331a..65761223b 100644 --- a/js/src/3rdparty/sms-verification/index.js +++ b/js/src/3rdparty/sms-verification/index.js @@ -15,17 +15,6 @@ // along with Parity. If not, see . import { stringify } from 'querystring'; -import React from 'react'; - -export const termsOfService = ( -
    -
  • 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.
  • -
  • 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.
  • -
  • You pay a fee for the cost of this service using the account you want to verify.
  • -
  • 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. Twilio’s privacy policy is here: https://www.twilio.com/legal/privacy/developer.
  • -
  • Parity Technology Limited is registered in England and Wales under company number 09760015 and complies with the Data Protection Act 1998 (UK). You may contact us via email at admin@parity.io. Our general privacy policy can be found here: https://ethcore.io/legal.html.
  • -
-); export const postToServer = (query, isTestnet = false) => { const port = isTestnet ? 8443 : 443; diff --git a/js/src/3rdparty/sms-verification/terms-of-service.js b/js/src/3rdparty/sms-verification/terms-of-service.js new file mode 100644 index 000000000..f61b3c97d --- /dev/null +++ b/js/src/3rdparty/sms-verification/terms-of-service.js @@ -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 . + +import React from 'react'; + +export default ( +
    +
  • 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.
  • +
  • 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.
  • +
  • You pay a fee for the cost of this service using the account you want to verify.
  • +
  • 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. Twilio’s privacy policy is here: https://www.twilio.com/legal/privacy/developer.
  • +
  • Parity Technology Limited is registered in England and Wales under company number 09760015 and complies with the Data Protection Act 1998 (UK). You may contact us via email at admin@parity.io. Our general privacy policy can be found here: https://ethcore.io/legal.html.
  • +
+); diff --git a/js/src/contracts/abi/email-verification.json b/js/src/contracts/abi/email-verification.json new file mode 100644 index 000000000..6a7f5a6d0 --- /dev/null +++ b/js/src/contracts/abi/email-verification.json @@ -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"}] \ No newline at end of file diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 35f96b924..ec5b49383 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -19,6 +19,7 @@ import basiccoin from './basiccoin.json'; import basiccoinmanager from './basiccoinmanager.json'; import dappreg from './dappreg.json'; import eip20 from './eip20.json'; +import emailverification from './email-verification.json'; import gavcoin from './gavcoin.json'; import githubhint from './githubhint.json'; import owned from './owned.json'; @@ -34,6 +35,7 @@ export { basiccoinmanager, dappreg, eip20, + emailverification, gavcoin, githubhint, owned, diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index 8cab3252d..a1a81a8ee 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -19,7 +19,7 @@ import Registry from './registry'; import SignatureReg from './signaturereg'; import TokenReg from './tokenreg'; import GithubHint from './githubhint'; -import * as smsVerification from './sms-verification'; +import * as verification from './verification'; import BadgeReg from './badgereg'; let instance = null; @@ -58,7 +58,11 @@ export default class Contracts { } get smsVerification () { - return smsVerification; + return verification; + } + + get emailVerification () { + return verification; } static create (api) { diff --git a/js/src/contracts/sms-verification.js b/js/src/contracts/verification.js similarity index 100% rename from js/src/contracts/sms-verification.js rename to js/src/contracts/verification.js diff --git a/js/src/modals/SMSVerification/Done/done.css b/js/src/modals/Verification/Done/done.css similarity index 100% rename from js/src/modals/SMSVerification/Done/done.css rename to js/src/modals/Verification/Done/done.css diff --git a/js/src/modals/SMSVerification/Done/done.js b/js/src/modals/Verification/Done/done.js similarity index 100% rename from js/src/modals/SMSVerification/Done/done.js rename to js/src/modals/Verification/Done/done.js diff --git a/js/src/modals/SMSVerification/Done/index.js b/js/src/modals/Verification/Done/index.js similarity index 100% rename from js/src/modals/SMSVerification/Done/index.js rename to js/src/modals/Verification/Done/index.js diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.css b/js/src/modals/Verification/GatherData/gatherData.css similarity index 96% rename from js/src/modals/SMSVerification/GatherData/gatherData.css rename to js/src/modals/Verification/GatherData/gatherData.css index 680986981..13563b6a5 100644 --- a/js/src/modals/SMSVerification/GatherData/gatherData.css +++ b/js/src/modals/Verification/GatherData/gatherData.css @@ -15,10 +15,6 @@ /* along with Parity. If not, see . */ -.list li { - padding: .1em 0; -} - .spacing { margin-top: 1.5em; } diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.js b/js/src/modals/Verification/GatherData/gatherData.js similarity index 77% rename from js/src/modals/SMSVerification/GatherData/gatherData.js rename to js/src/modals/Verification/GatherData/gatherData.js index 289374846..57386ed20 100644 --- a/js/src/modals/SMSVerification/GatherData/gatherData.js +++ b/js/src/modals/Verification/GatherData/gatherData.js @@ -25,41 +25,33 @@ import { fromWei } from '~/api/util/wei'; import { Form, Input } from '~/ui'; 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'; export default class GatherData extends Component { static propTypes = { fee: React.PropTypes.instanceOf(BigNumber), - isNumberValid: PropTypes.bool.isRequired, + method: PropTypes.string.isRequired, + fields: PropTypes.array.isRequired, isVerified: nullableProptype(PropTypes.bool.isRequired), hasRequested: nullableProptype(PropTypes.bool.isRequired), - setNumber: PropTypes.func.isRequired, setConsentGiven: PropTypes.func.isRequired } render () { - const { isNumberValid, isVerified } = this.props; + const { method, isVerified } = this.props; + const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService; + const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks; return (
-

The following steps will let you prove that you control both an account and a phone number.

-
    -
  1. You send a verification request to a specific contract.
  2. -
  3. Our server puts a puzzle into this contract.
  4. -
  5. The code you receive via SMS is the solution to this puzzle.
  6. -
+ { howItWorks } { this.renderFee() } { this.renderCertified() } { this.renderRequested() } - + { this.renderFields() } { - this.props.setNumber(value); - } + renderFields () { + const { isVerified, fields } = this.props; - numberOnChange = (_, value) => { - this.props.setNumber(value); + const rendered = fields.map((field) => { + const onChange = (_, v) => { + field.onChange(v); + }; + const onSubmit = field.onChange; + return ( + + ); + }); + + return (
{rendered}
); } consentOnChange = (_, consentGiven) => { diff --git a/js/src/modals/SMSVerification/GatherData/index.js b/js/src/modals/Verification/GatherData/index.js similarity index 100% rename from js/src/modals/SMSVerification/GatherData/index.js rename to js/src/modals/Verification/GatherData/index.js diff --git a/js/src/modals/SMSVerification/QueryCode/index.js b/js/src/modals/Verification/QueryCode/index.js similarity index 100% rename from js/src/modals/SMSVerification/QueryCode/index.js rename to js/src/modals/Verification/QueryCode/index.js diff --git a/js/src/modals/SMSVerification/QueryCode/queryCode.js b/js/src/modals/Verification/QueryCode/queryCode.js similarity index 82% rename from js/src/modals/SMSVerification/QueryCode/queryCode.js rename to js/src/modals/Verification/QueryCode/queryCode.js index bd0cd9d61..68d4d322b 100644 --- a/js/src/modals/SMSVerification/QueryCode/queryCode.js +++ b/js/src/modals/Verification/QueryCode/queryCode.js @@ -20,20 +20,25 @@ import { Form, Input } from '~/ui'; export default class QueryCode extends Component { static propTypes = { - number: PropTypes.string.isRequired, + receiver: PropTypes.string.isRequired, + hint: PropTypes.string, isCodeValid: PropTypes.bool.isRequired, setCode: PropTypes.func.isRequired } + static defaultProps = { + hint: 'Enter the code you received.' + } + render () { - const { number, isCodeValid } = this.props; + const { receiver, hint, isCodeValid } = this.props; return ( -

The verification code has been sent to { number }.

+

The verification code has been sent to { receiver }.

); - case REQUESTING_SMS: + case REQUESTING_CODE: return ( -

Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.

+

Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.

); default: diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js new file mode 100644 index 000000000..3d7faa9e1 --- /dev/null +++ b/js/src/modals/Verification/email-store.js @@ -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 . + +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); + } +} diff --git a/js/src/modals/Verification/how-it-works.js b/js/src/modals/Verification/how-it-works.js new file mode 100644 index 000000000..6abdc80dd --- /dev/null +++ b/js/src/modals/Verification/how-it-works.js @@ -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 . + +import React from 'react'; + +import styles from './verification.css'; + +export const howSMSVerificationWorks = ( +
+

The following steps will let you prove that you control both an account and a phone number.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via SMS is the solution to this puzzle.
  6. +
+
+); + +export const howEmailVerificationWorks = ( +
+

The following steps will let you prove that you control both an account and an e-mail address.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via e-mail is the solution to this puzzle.
  6. +
+
+); diff --git a/js/src/modals/SMSVerification/index.js b/js/src/modals/Verification/index.js similarity index 87% rename from js/src/modals/SMSVerification/index.js rename to js/src/modals/Verification/index.js index e26958f8c..9c29a7165 100644 --- a/js/src/modals/SMSVerification/index.js +++ b/js/src/modals/Verification/index.js @@ -1,4 +1,4 @@ -// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// Copyright 2015, 2016 Ethcore (UK) Ltd. // This file is part of Parity. // 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 // along with Parity. If not, see . -export default from './SMSVerification'; +export default from './verification'; diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js new file mode 100644 index 000000000..44c5aa39c --- /dev/null +++ b/js/src/modals/Verification/sms-store.js @@ -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 . + +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); + } +} diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/Verification/store.js similarity index 79% rename from js/src/modals/SMSVerification/store.js rename to js/src/modals/Verification/store.js index f97e84ade..dcbfbf698 100644 --- a/js/src/modals/SMSVerification/store.js +++ b/js/src/modals/Verification/store.js @@ -14,21 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { observable, computed, autorun, action } from 'mobx'; -import phone from 'phoneformat.js'; +import { observable, autorun, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; - +import Contract from '~/api/contract'; import Contracts from '~/contracts'; -import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; -import { postToServer } from '~/3rdparty/sms-verification'; +import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; export const LOADING = 'fetching-contract'; export const QUERY_DATA = 'query-data'; export const POSTING_REQUEST = 'posting-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 POSTING_CONFIRMATION = 'posting-confirmation'; export const POSTED_CONFIRMATION = 'posted-confirmation'; @@ -43,56 +41,30 @@ export default class VerificationStore { @observable isVerified = null; @observable hasRequested = null; @observable consentGiven = false; - @observable number = ''; @observable requestTx = null; @observable code = ''; @observable isCodeValid = null; @observable confirmationTx = null; - @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) { + constructor (api, abi, name, account, isTestnet) { this.api = api; this.account = account; this.isTestnet = isTestnet; this.step = LOADING; - Contracts.create(api).registry.getContract('smsverification') - .then((contract) => { - this.contract = contract; + Contracts.get().badgeReg.fetchCertifier(name) + .then(({ address }) => { + this.contract = new Contract(api, abi).at(address); this.load(); }) .catch((err) => { + console.error('error', err); this.error = 'Failed to fetch the contract: ' + err.message; }); autorun(() => { 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) => { this.consentGiven = consentGiven; } @@ -166,19 +134,22 @@ export default class VerificationStore { }); } + requestValues = () => [] + @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 options = { from: account, value: fee.toString() }; + const values = this.requestValues(); let chain = Promise.resolve(); if (!hasRequested) { this.step = POSTING_REQUEST; - chain = request.estimateGas(options, []) + chain = request.estimateGas(options, values) .then((gas) => { options.gas = gas.mul(1.2).toFixed(0); - return request.postTransaction(options, []); + return request.postTransaction(options, values); }) .then((handle) => { // TODO: The "request rejected" error doesn't have any property to @@ -200,18 +171,15 @@ export default class VerificationStore { chain .then(() => { - return api.parity.netChain(); - }) - .then((chain) => { - this.step = REQUESTING_SMS; - return postToServer({ number, address: account }, this.isTestnet); + this.step = REQUESTING_CODE; + return this.requestCode(); }) .then(() => awaitPuzzle(api, contract, account)) .then(() => { this.step = QUERY_CODE; }) .catch((err) => { - this.error = 'Failed to request a confirmation SMS: ' + err.message; + this.error = 'Failed to request a confirmation code: ' + err.message; }); } diff --git a/js/src/modals/Verification/verification.css b/js/src/modals/Verification/verification.css new file mode 100644 index 000000000..968671e63 --- /dev/null +++ b/js/src/modals/Verification/verification.css @@ -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 . +*/ + +.noSpacing { + margin-top: 0; + margin-bottom: 0; +} + +.list li { + padding: .1em 0; +} diff --git a/js/src/modals/SMSVerification/SMSVerification.js b/js/src/modals/Verification/verification.js similarity index 52% rename from js/src/modals/SMSVerification/SMSVerification.js rename to js/src/modals/Verification/verification.js index f37c80330..d1ea53798 100644 --- a/js/src/modals/SMSVerification/SMSVerification.js +++ b/js/src/modals/Verification/verification.js @@ -20,12 +20,27 @@ import DoneIcon from 'material-ui/svg-icons/action/done-all'; import CancelIcon from 'material-ui/svg-icons/content/clear'; 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: (

It will be stored on the blockchain that you control a phone number (not which).

) + }, + email: { + label: 'E-mail Verification', key: 1, value: 'email', + description: (

The hash of the e-mail address you prove control over will be stored on the blockchain.

) + } +}; import { LOADING, QUERY_DATA, POSTING_REQUEST, POSTED_REQUEST, - REQUESTING_SMS, QUERY_CODE, + REQUESTING_CODE, QUERY_CODE, POSTING_CONFIRMATION, POSTED_CONFIRMATION, DONE } from './store'; @@ -37,34 +52,44 @@ import SendConfirmation from './SendConfirmation'; import Done from './Done'; @observer -export default class SMSVerification extends Component { +export default class Verification extends Component { static propTypes = { - store: PropTypes.any.isRequired, + store: nullableProptype(PropTypes.object.isRequired), account: PropTypes.string.isRequired, + onSelectMethod: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired } static phases = { // mapping (store steps -> steps) - [LOADING]: 0, - [QUERY_DATA]: 1, - [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2, + [LOADING]: 1, [QUERY_DATA]: 1, + [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2, [QUERY_CODE]: 3, [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, [DONE]: 5 } + state = { + method: 'sms' + }; + render () { - const phase = SMSVerification.phases[this.props.store.step]; - const { error, isStepValid } = this.props.store; + const { store } = this.props; + let phase = 0; let error = false; let isStepValid = true; + + if (store) { + phase = Verification.phases[store.step]; + error = store.error; + isStepValid = store.isStepValid; + } return ( { this.renderStep(phase, error) } @@ -101,6 +126,13 @@ export default class SMSVerification extends Component { let action = () => {}; switch (phase) { + case 0: + action = () => { + const { onSelectMethod } = this.props; + const { method } = this.state; + onSelectMethod(method); + }; + break; case 1: action = store.sendRequest; break; @@ -133,26 +165,58 @@ export default class SMSVerification extends Component { return (

{ error }

); } + const { method } = this.state; + if (phase === 0) { + const values = Object.values(methods); + const value = values.findIndex((v) => v.value === method); + return ( + + ); + } + const { step, - fee, number, isNumberValid, isVerified, hasRequested, + fee, isVerified, hasRequested, requestTx, isCodeValid, confirmationTx, setCode } = this.props.store; switch (phase) { - case 0: - return ( -

Loading SMS Verification.

- ); - case 1: - const { setNumber, setConsentGiven } = this.props.store; + if (step === LOADING) { + return (

Loading verification data.

); + } + + 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 ( ); @@ -162,9 +226,19 @@ export default class SMSVerification extends Component { ); 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 ( ); @@ -183,4 +257,8 @@ export default class SMSVerification extends Component { return null; } } + + selectMethod = (choice, i) => { + this.setState({ method: choice.value }); + } } diff --git a/js/src/modals/index.js b/js/src/modals/index.js index 373772757..4534c8aba 100644 --- a/js/src/modals/index.js +++ b/js/src/modals/index.js @@ -26,7 +26,7 @@ import FirstRun from './FirstRun'; import LoadContract from './LoadContract'; import SaveContract from './SaveContract'; import Shapeshift from './Shapeshift'; -import SMSVerification from './SMSVerification'; +import Verification from './Verification'; import Transfer from './Transfer'; import PasswordManager from './PasswordManager'; import UpgradeParity from './UpgradeParity'; @@ -45,7 +45,7 @@ export { LoadContract, SaveContract, Shapeshift, - SMSVerification, + Verification, Transfer, PasswordManager, UpgradeParity, diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 9e4c56166..38c712622 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -23,7 +23,7 @@ import ContentSend from 'material-ui/svg-icons/content/send'; import LockIcon from 'material-ui/svg-icons/action/lock'; 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 shapeshiftBtn from '~/../assets/images/shapeshift-btn.png'; @@ -32,7 +32,8 @@ import Header from './Header'; import Transactions from './Transactions'; 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'; @@ -72,15 +73,6 @@ class Account extends Component { if (prevAddress !== nextAddress) { 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 () { @@ -228,8 +220,9 @@ class Account extends Component { const { address } = this.props.params; return ( - ); @@ -303,6 +296,22 @@ class Account extends Component { 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 = () => { this.setState({ showVerificationDialog: false }); } diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index cc7736d31..09d7df7e7 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -280,7 +280,7 @@ Footprint Options: of all accounts and storage keys. Doubles the size of the state database. BOOL may be one of on, off 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. (default: {flag_scale_verifiers}) --num-verifiers INT Amount of verifier threads to use or to begin with, if verifier diff --git a/parity/params.rs b/parity/params.rs index 81db4165d..aaaa12085 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -49,7 +49,7 @@ impl str::FromStr for SpecType { let spec = match s { "frontier" | "homestead" | "mainnet" => SpecType::Mainnet, "frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic, - "morden" | "testnet" => SpecType::Testnet, + "morden" | "testnet" | "classic-testnet" => SpecType::Testnet, "ropsten" => SpecType::Ropsten, "olympic" => SpecType::Olympic, "expanse" => SpecType::Expanse, @@ -288,6 +288,8 @@ mod tests { assert_eq!(SpecType::Testnet, "morden".parse().unwrap()); assert_eq!(SpecType::Ropsten, "ropsten".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]