diff --git a/js/package.json b/js/package.json index e2da3484d..8c2146917 100644 --- a/js/package.json +++ b/js/package.json @@ -139,6 +139,7 @@ "mobx-react": "^3.5.8", "mobx-react-devtools": "^4.2.9", "moment": "^2.14.1", + "phoneformat.js": "^1.0.3", "qs": "^6.3.0", "react": "^15.2.1", "react-ace": "^4.0.0", diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 80f49dc5b..a6a7f0783 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -23,6 +23,7 @@ import githubhint from './githubhint.json'; import owned from './owned.json'; import registry from './registry.json'; import signaturereg from './signaturereg.json'; +import smsverification from './sms-verification.json'; import tokenreg from './tokenreg.json'; import wallet from './wallet.json'; @@ -36,6 +37,7 @@ export { owned, registry, signaturereg, + smsverification, tokenreg, wallet }; diff --git a/js/src/contracts/abi/sms-verification.json b/js/src/contracts/abi/sms-verification.json new file mode 100644 index 000000000..400d22b44 --- /dev/null +++ b/js/src/contracts/abi/sms-verification.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","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":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[],"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":"delegate","outputs":[{"name":"","type":"address"}],"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":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"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"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"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"}] diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index a04321c7b..9d745762c 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -19,6 +19,7 @@ import Registry from './registry'; import SignatureReg from './signaturereg'; import TokenReg from './tokenreg'; import GithubHint from './githubhint'; +import smsVerification from './sms-verification'; let instance = null; @@ -54,6 +55,10 @@ export default class Contracts { return this._githubhint; } + get smsVerification () { + return smsVerification; + } + static create (api) { return new Contracts(api); } diff --git a/js/src/contracts/sms-verification.js b/js/src/contracts/sms-verification.js new file mode 100644 index 000000000..e93d57ffc --- /dev/null +++ b/js/src/contracts/sms-verification.js @@ -0,0 +1,52 @@ +// 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 checkIfVerified = (contract, account) => { + return contract.instance.certified.call({}, [account]); +}; + +export const checkIfRequested = (contract, account) => { + return new Promise((resolve, reject) => { + contract.subscribe('Requested', { + fromBlock: 0, toBlock: 'pending' + }, (err, logs) => { + if (err) { + return reject(err); + } + const e = logs.find((l) => { + return l.type === 'mined' && l.params.who && l.params.who.value === account; + }); + resolve(e ? e.transactionHash : false); + }); + }); +}; + +export const postToServer = (query) => { + query = stringify(query); + return fetch('https://sms-verification.parity.io/?' + 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/modals/SMSVerification/Done/done.css b/js/src/modals/SMSVerification/Done/done.css new file mode 100644 index 000000000..18c410f3e --- /dev/null +++ b/js/src/modals/SMSVerification/Done/done.css @@ -0,0 +1,31 @@ +/* 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 . +*/ +.spacing { + margin-top: 1.5em; +} + +.container { + margin-top: .5em; + display: flex; + align-items: center; +} + +.message { + margin-top: 0; + margin-bottom: 0; + margin-left: .5em; +} diff --git a/js/src/modals/SMSVerification/Done/done.js b/js/src/modals/SMSVerification/Done/done.js new file mode 100644 index 000000000..f3f6fa515 --- /dev/null +++ b/js/src/modals/SMSVerification/Done/done.js @@ -0,0 +1,31 @@ +// 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, { Component } from 'react'; +import SuccessIcon from 'material-ui/svg-icons/navigation/check'; + +import styles from './done.css'; + +export default class Done extends Component { + render () { + return ( +
+ +

Congratulations, your account is verified!

+
+ ); + } +} diff --git a/js/src/modals/SMSVerification/Done/index.js b/js/src/modals/SMSVerification/Done/index.js new file mode 100644 index 000000000..549306dbd --- /dev/null +++ b/js/src/modals/SMSVerification/Done/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './done'; diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.css b/js/src/modals/SMSVerification/GatherData/gatherData.css new file mode 100644 index 000000000..680986981 --- /dev/null +++ b/js/src/modals/SMSVerification/GatherData/gatherData.css @@ -0,0 +1,49 @@ +/* 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 . +*/ + +.list li { + padding: .1em 0; +} + +.spacing { + margin-top: 1.5em; +} + +.container { + margin-top: .5em; + display: flex; + align-items: center; +} +.message { + margin-top: 0; + margin-bottom: 0; + margin-left: .5em; +} + +.terms { + line-height: 1.3; + opacity: .7; + + ul { + padding-left: 1.5em; + } + + li { + margin-top: .2em; + margin-bottom: .2em; + } +} diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.js b/js/src/modals/SMSVerification/GatherData/gatherData.js new file mode 100644 index 000000000..f4036a3bc --- /dev/null +++ b/js/src/modals/SMSVerification/GatherData/gatherData.js @@ -0,0 +1,151 @@ +// 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, { Component, PropTypes } from 'react'; +import nullable from '../../../util/nullable-proptype'; +import BigNumber from 'bignumber.js'; +import { Checkbox } from 'material-ui'; +import InfoIcon from 'material-ui/svg-icons/action/info-outline'; +import SuccessIcon from 'material-ui/svg-icons/navigation/check'; +import ErrorIcon from 'material-ui/svg-icons/navigation/close'; + +import { fromWei } from '../../../api/util/wei'; +import { Form, Input } from '../../../ui'; + +import terms from '../terms-of-service'; +import styles from './gatherData.css'; + +export default class GatherData extends Component { + static propTypes = { + fee: React.PropTypes.instanceOf(BigNumber), + isNumberValid: PropTypes.bool.isRequired, + isVerified: nullable(PropTypes.bool.isRequired), + hasRequested: nullable(PropTypes.bool.isRequired), + setNumber: PropTypes.func.isRequired, + setConsentGiven: PropTypes.func.isRequired + } + + render () { + const { isNumberValid, isVerified } = this.props; + + 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. +
+ { this.renderFee() } + { this.renderCertified() } + { this.renderRequested() } + + +
{ terms }
+ + ); + } + + renderFee () { + const { fee } = this.props; + + if (!fee) { + return (

Fetching the fee…

); + } + return ( +
+ +

The fee is { fromWei(fee).toFixed(3) } ETH.

+
+ ); + } + + renderCertified () { + const { isVerified } = this.props; + + if (isVerified) { + return ( +
+ +

Your account is already verified.

+
+ ); + } else if (isVerified === false) { + return ( +
+ +

Your account is not verified yet.

+
+ ); + } + return ( +

Checking if your account is verified…

+ ); + } + + renderRequested () { + const { isVerified, hasRequested } = this.props; + + // If the account is verified, don't show that it has requested verification. + if (isVerified) { + return null; + } + + if (hasRequested) { + return ( +
+ +

You already requested verification.

+
+ ); + } + if (hasRequested === false) { + return ( +
+ +

You did not request verification yet.

+
+ ); + } + return ( +

Checking if you requested verification…

+ ); + } + + numberOnSubmit = (value) => { + this.props.setNumber(value); + } + + numberOnChange = (_, value) => { + this.props.setNumber(value); + } + + consentOnChange = (_, consentGiven) => { + this.props.setConsentGiven(consentGiven); + } +} diff --git a/js/src/modals/SMSVerification/GatherData/index.js b/js/src/modals/SMSVerification/GatherData/index.js new file mode 100644 index 000000000..1c03d3400 --- /dev/null +++ b/js/src/modals/SMSVerification/GatherData/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './gatherData'; diff --git a/js/src/modals/SMSVerification/QueryCode/index.js b/js/src/modals/SMSVerification/QueryCode/index.js new file mode 100644 index 000000000..539c340f0 --- /dev/null +++ b/js/src/modals/SMSVerification/QueryCode/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './queryCode'; diff --git a/js/src/modals/SMSVerification/QueryCode/queryCode.js b/js/src/modals/SMSVerification/QueryCode/queryCode.js new file mode 100644 index 000000000..9598fe358 --- /dev/null +++ b/js/src/modals/SMSVerification/QueryCode/queryCode.js @@ -0,0 +1,52 @@ +// 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, { Component, PropTypes } from 'react'; + +import { Form, Input } from '../../../ui'; + +export default class QueryCode extends Component { + static propTypes = { + number: PropTypes.string.isRequired, + isCodeValid: PropTypes.bool.isRequired, + setCode: PropTypes.func.isRequired + } + + render () { + const { number, isCodeValid } = this.props; + + return ( +
+

The verification code has been sent to { number }.

+ +
+ ); + } + + onChange = (_, code) => { + this.props.setCode(code.trim()); + } + + onSubmit = (code) => { + this.props.setCode(code.trim()); + } +} diff --git a/js/src/modals/SMSVerification/SMSVerification.js b/js/src/modals/SMSVerification/SMSVerification.js new file mode 100644 index 000000000..4ec0b608d --- /dev/null +++ b/js/src/modals/SMSVerification/SMSVerification.js @@ -0,0 +1,176 @@ +// 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, { Component, PropTypes } from 'react'; +import { observer } from 'mobx-react'; +import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; +import ContentClear from 'material-ui/svg-icons/content/clear'; + +import { Button, IdentityIcon, Modal } from '../../ui'; + +import { + LOADING, + QUERY_DATA, + POSTING_REQUEST, POSTED_REQUEST, + REQUESTING_SMS, REQUESTED_SMS, + POSTING_CONFIRMATION, POSTED_CONFIRMATION, + DONE +} from './store'; + +import GatherData from './GatherData'; +import SendRequest from './SendRequest'; +import QueryCode from './QueryCode'; +import SendConfirmation from './SendConfirmation'; +import Done from './Done'; + +@observer +export default class SMSVerification extends Component { + static propTypes = { + store: PropTypes.any.isRequired, + account: PropTypes.string.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, + [REQUESTED_SMS]: 3, + [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, + [DONE]: 5 + } + + render () { + const phase = SMSVerification.phases[this.props.store.step]; + const { error, isStepValid } = this.props.store; + + return ( + + { this.renderStep(phase, error) } + + ); + } + + renderDialogActions (phase, error, isStepValid) { + const { store, account, onClose } = this.props; + + const cancel = ( +