more permissive verification process (#4317)
* style fixes ✨ * verification: find last request * verification: don't request again if same input * didRequestWithSameValues -> shallRequestAgain * bugfixes 🐛, update SMS verification ABI * verification: hasRequested -> accountHasRequested * verification: hasIsVerified -> accountIsVerified * verification: shallRequestAgain -> shallSkipRequest * verification: show if unable to send req * email verification: check if email already used * address style grumbles 🎨
This commit is contained in:
parent
8e82b2f631
commit
1547af191b
@ -1 +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":[{"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":"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"}]
|
[{"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":[{"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":"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"}]
|
@ -20,23 +20,23 @@ export const checkIfVerified = (contract, account) => {
|
|||||||
return contract.instance.certified.call({}, [account]);
|
return contract.instance.certified.call({}, [account]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkIfRequested = (contract, account) => {
|
export const findLastRequested = (contract, account) => {
|
||||||
let subId = null;
|
let subId = null;
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
contract
|
contract
|
||||||
.subscribe('Requested', {
|
.subscribe('Requested', {
|
||||||
fromBlock: 0, toBlock: 'pending'
|
fromBlock: 0,
|
||||||
|
toBlock: 'pending',
|
||||||
|
limit: 1,
|
||||||
|
topics: [account]
|
||||||
}, (err, logs) => {
|
}, (err, logs) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(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);
|
resolve(logs[0] || null);
|
||||||
resolved = true;
|
resolved = true;
|
||||||
|
|
||||||
if (subId) {
|
if (subId) {
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
margin-left: .5em;
|
margin-left: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
opacity: .7;
|
opacity: .7;
|
||||||
|
@ -31,19 +31,22 @@ import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service'
|
|||||||
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
|
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
|
||||||
import styles from './gatherData.css';
|
import styles from './gatherData.css';
|
||||||
|
|
||||||
|
const boolOfError = PropTypes.oneOfType([ PropTypes.bool, PropTypes.instanceOf(Error) ]);
|
||||||
|
|
||||||
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),
|
||||||
fields: PropTypes.array.isRequired,
|
fields: PropTypes.array.isRequired,
|
||||||
hasRequested: nullableProptype(PropTypes.bool.isRequired),
|
accountHasRequested: nullableProptype(PropTypes.bool.isRequired),
|
||||||
isServerRunning: nullableProptype(PropTypes.bool.isRequired),
|
isServerRunning: nullableProptype(PropTypes.bool.isRequired),
|
||||||
isVerified: nullableProptype(PropTypes.bool.isRequired),
|
isAbleToRequest: nullableProptype(boolOfError.isRequired),
|
||||||
|
accountIsVerified: nullableProptype(PropTypes.bool.isRequired),
|
||||||
method: PropTypes.string.isRequired,
|
method: PropTypes.string.isRequired,
|
||||||
setConsentGiven: PropTypes.func.isRequired
|
setConsentGiven: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { method, isVerified } = this.props;
|
const { method, accountIsVerified } = this.props;
|
||||||
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
|
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
|
||||||
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
|
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
|
||||||
|
|
||||||
@ -55,6 +58,7 @@ export default class GatherData extends Component {
|
|||||||
{ this.renderCertified() }
|
{ this.renderCertified() }
|
||||||
{ this.renderRequested() }
|
{ this.renderRequested() }
|
||||||
{ this.renderFields() }
|
{ this.renderFields() }
|
||||||
|
{ this.renderIfAbleToRequest() }
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className={ styles.spacing }
|
className={ styles.spacing }
|
||||||
label={
|
label={
|
||||||
@ -63,7 +67,7 @@ export default class GatherData extends Component {
|
|||||||
defaultMessage='I agree to the terms and conditions below.'
|
defaultMessage='I agree to the terms and conditions below.'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
disabled={ isVerified }
|
disabled={ accountIsVerified }
|
||||||
onCheck={ this.consentOnChange }
|
onCheck={ this.consentOnChange }
|
||||||
/>
|
/>
|
||||||
<div className={ styles.terms }>{ termsOfService }</div>
|
<div className={ styles.terms }>{ termsOfService }</div>
|
||||||
@ -145,27 +149,27 @@ export default class GatherData extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderCertified () {
|
renderCertified () {
|
||||||
const { isVerified } = this.props;
|
const { accountIsVerified } = this.props;
|
||||||
|
|
||||||
if (isVerified) {
|
if (accountIsVerified) {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<ErrorIcon />
|
<ErrorIcon />
|
||||||
<p className={ styles.message }>
|
<p className={ styles.message }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.verification.gatherData.isVerified.true'
|
id='ui.verification.gatherData.accountIsVerified.true'
|
||||||
defaultMessage='Your account is already verified.'
|
defaultMessage='Your account is already verified.'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (isVerified === false) {
|
} else if (accountIsVerified === false) {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<SuccessIcon />
|
<SuccessIcon />
|
||||||
<p className={ styles.message }>
|
<p className={ styles.message }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.verification.gatherData.isVerified.false'
|
id='ui.verification.gatherData.accountIsVerified.false'
|
||||||
defaultMessage='Your account is not verified yet.'
|
defaultMessage='Your account is not verified yet.'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
@ -175,7 +179,7 @@ export default class GatherData extends Component {
|
|||||||
return (
|
return (
|
||||||
<p className={ styles.message }>
|
<p className={ styles.message }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.verification.gatherData.isVerified.pending'
|
id='ui.verification.gatherData.accountIsVerified.pending'
|
||||||
defaultMessage='Checking if your account is verified…'
|
defaultMessage='Checking if your account is verified…'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
@ -183,33 +187,33 @@ export default class GatherData extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRequested () {
|
renderRequested () {
|
||||||
const { isVerified, hasRequested } = this.props;
|
const { accountIsVerified, accountHasRequested } = this.props;
|
||||||
|
|
||||||
// If the account is verified, don't show that it has requested verification.
|
// If the account is verified, don't show that it has requested verification.
|
||||||
if (isVerified) {
|
if (accountIsVerified) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasRequested) {
|
if (accountHasRequested) {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<InfoIcon />
|
<InfoIcon />
|
||||||
<p className={ styles.message }>
|
<p className={ styles.message }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.verification.gatherData.hasRequested.true'
|
id='ui.verification.gatherData.accountHasRequested.true'
|
||||||
defaultMessage='You already requested verification.'
|
defaultMessage='You already requested verification from this account.'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (hasRequested === false) {
|
} else if (accountHasRequested === false) {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<SuccessIcon />
|
<SuccessIcon />
|
||||||
<p className={ styles.message }>
|
<p className={ styles.message }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.verification.gatherData.hasRequested.false'
|
id='ui.verification.gatherData.accountHasRequested.false'
|
||||||
defaultMessage='You did not request verification yet.'
|
defaultMessage='You did not request verification from this account yet.'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -218,7 +222,7 @@ export default class GatherData extends Component {
|
|||||||
return (
|
return (
|
||||||
<p className={ styles.message }>
|
<p className={ styles.message }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.verification.gatherData.hasRequested.pending'
|
id='ui.verification.gatherData.accountHasRequested.pending'
|
||||||
defaultMessage='Checking if you requested verification…'
|
defaultMessage='Checking if you requested verification…'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
@ -226,7 +230,7 @@ export default class GatherData extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderFields () {
|
renderFields () {
|
||||||
const { isVerified, fields } = this.props;
|
const { accountIsVerified, fields } = this.props;
|
||||||
|
|
||||||
const rendered = fields.map((field) => {
|
const rendered = fields.map((field) => {
|
||||||
const onChange = (_, v) => {
|
const onChange = (_, v) => {
|
||||||
@ -236,11 +240,12 @@ export default class GatherData extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
|
className={ styles.field }
|
||||||
key={ field.key }
|
key={ field.key }
|
||||||
label={ field.label }
|
label={ field.label }
|
||||||
hint={ field.hint }
|
hint={ field.hint }
|
||||||
error={ field.error }
|
error={ field.error }
|
||||||
disabled={ isVerified }
|
disabled={ accountIsVerified }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
onSubmit={ onSubmit }
|
onSubmit={ onSubmit }
|
||||||
/>
|
/>
|
||||||
@ -250,6 +255,36 @@ export default class GatherData extends Component {
|
|||||||
return (<div>{rendered}</div>);
|
return (<div>{rendered}</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderIfAbleToRequest () {
|
||||||
|
const { accountIsVerified, isAbleToRequest } = this.props;
|
||||||
|
|
||||||
|
// If the account is verified, don't show a warning.
|
||||||
|
// If the client is able to send the request, don't show a warning
|
||||||
|
if (accountIsVerified || isAbleToRequest === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAbleToRequest === null) {
|
||||||
|
return (
|
||||||
|
<p className={ styles.message }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.verification.gatherData.isAbleToRequest.pending'
|
||||||
|
defaultMessage='Validating your input…'
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
} else if (isAbleToRequest) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.container }>
|
||||||
|
<ErrorIcon />
|
||||||
|
<p className={ styles.message }>
|
||||||
|
{ isAbleToRequest.message }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
consentOnChange = (_, consentGiven) => {
|
consentOnChange = (_, consentGiven) => {
|
||||||
this.props.setConsentGiven(consentGiven);
|
this.props.setConsentGiven(consentGiven);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { observable, computed, action } from 'mobx';
|
import { observable, computed, action } from 'mobx';
|
||||||
import { sha3 } from '~/api/util/sha3';
|
import { sha3 } from '~/api/util/sha3';
|
||||||
|
import { bytesToHex } from '~/api/util/format';
|
||||||
|
|
||||||
import EmailVerificationABI from '~/contracts/abi/email-verification.json';
|
import EmailVerificationABI from '~/contracts/abi/email-verification.json';
|
||||||
import VerificationStore, {
|
import VerificationStore, {
|
||||||
@ -23,6 +24,8 @@ import VerificationStore, {
|
|||||||
} from './store';
|
} from './store';
|
||||||
import { isServerRunning, hasReceivedCode, postToServer } from '~/3rdparty/email-verification';
|
import { isServerRunning, hasReceivedCode, postToServer } from '~/3rdparty/email-verification';
|
||||||
|
|
||||||
|
const ZERO20 = '0x0000000000000000000000000000000000000000';
|
||||||
|
|
||||||
// name in the `BadgeReg.sol` contract
|
// name in the `BadgeReg.sol` contract
|
||||||
const EMAIL_VERIFICATION = 'emailverification';
|
const EMAIL_VERIFICATION = 'emailverification';
|
||||||
|
|
||||||
@ -44,9 +47,9 @@ export default class EmailVerificationStore extends VerificationStore {
|
|||||||
|
|
||||||
switch (this.step) {
|
switch (this.step) {
|
||||||
case LOADING:
|
case LOADING:
|
||||||
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
|
return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null;
|
||||||
case QUERY_DATA:
|
case QUERY_DATA:
|
||||||
return this.isEmailValid && this.consentGiven;
|
return this.isEmailValid && this.consentGiven && this.isAbleToRequest === true;
|
||||||
case QUERY_CODE:
|
case QUERY_CODE:
|
||||||
return this.requestTx && this.isCodeValid === true;
|
return this.requestTx && this.isCodeValid === true;
|
||||||
case POSTED_CONFIRMATION:
|
case POSTED_CONFIRMATION:
|
||||||
@ -68,8 +71,53 @@ export default class EmailVerificationStore extends VerificationStore {
|
|||||||
return hasReceivedCode(this.email, this.account, this.isTestnet);
|
return hasReceivedCode(this.email, this.account, this.isTestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the email has already been used for verification of another account,
|
||||||
|
// we prevent the user from wasting ETH to request another verification.
|
||||||
|
@action setIfAbleToRequest = () => {
|
||||||
|
const { isEmailValid } = this;
|
||||||
|
|
||||||
|
if (!isEmailValid) {
|
||||||
|
this.isAbleToRequest = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { contract, email } = this;
|
||||||
|
const emailHash = sha3.text(email);
|
||||||
|
|
||||||
|
this.isAbleToRequest = null;
|
||||||
|
contract
|
||||||
|
.instance.reverse
|
||||||
|
.call({}, [ emailHash ])
|
||||||
|
.then((address) => {
|
||||||
|
if (address === ZERO20) {
|
||||||
|
this.isAbleToRequest = true;
|
||||||
|
} else {
|
||||||
|
this.isAbleToRequest = new Error('Another account has been verified using this e-mail.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.error = 'Failed to check if able to send request: ' + err.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the values relevant for checking if the last request contains
|
||||||
|
// the same data as the current one.
|
||||||
requestValues = () => [ sha3.text(this.email) ]
|
requestValues = () => [ sha3.text(this.email) ]
|
||||||
|
|
||||||
|
shallSkipRequest = (currentValues) => {
|
||||||
|
const { accountHasRequested } = this;
|
||||||
|
const lastRequest = this.lastRequestValues;
|
||||||
|
|
||||||
|
if (!accountHasRequested) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
// If the last email verification `request` for the selected address contains
|
||||||
|
// the same email as the current one, don't send another request to save ETH.
|
||||||
|
const skip = currentValues[0] === bytesToHex(lastRequest.emailHash.value);
|
||||||
|
|
||||||
|
return Promise.resolve(skip);
|
||||||
|
}
|
||||||
|
|
||||||
@action setEmail = (email) => {
|
@action setEmail = (email) => {
|
||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ export default class SMSVerificationStore extends VerificationStore {
|
|||||||
|
|
||||||
switch (this.step) {
|
switch (this.step) {
|
||||||
case LOADING:
|
case LOADING:
|
||||||
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
|
return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null;
|
||||||
case QUERY_DATA:
|
case QUERY_DATA:
|
||||||
return this.isNumberValid && this.consentGiven;
|
return this.isNumberValid && this.consentGiven;
|
||||||
case QUERY_CODE:
|
case QUERY_CODE:
|
||||||
@ -67,6 +67,18 @@ export default class SMSVerificationStore extends VerificationStore {
|
|||||||
return hasReceivedCode(this.number, this.account, this.isTestnet);
|
return hasReceivedCode(this.number, this.account, this.isTestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SMS verification events don't contain the phone number, so we will have to
|
||||||
|
// send a new request every single time. See below.
|
||||||
|
@action setIfAbleToRequest = () => {
|
||||||
|
this.isAbleToRequest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMS verification `request` & `confirm` transactions and events don't contain the
|
||||||
|
// phone number, so we will have to send a new request every single time. This may
|
||||||
|
// cost the user more money, but given that it fails otherwise, it seems like a
|
||||||
|
// reasonable tradeoff.
|
||||||
|
shallSkipRequest = () => Promise.resolve(false)
|
||||||
|
|
||||||
@action setNumber = (number) => {
|
@action setNumber = (number) => {
|
||||||
this.number = number;
|
this.number = number;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import { sha3 } from '~/api/util/sha3';
|
|||||||
import Contract from '~/api/contract';
|
import Contract from '~/api/contract';
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
|
|
||||||
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification';
|
import { checkIfVerified, findLastRequested, awaitPuzzle } from '~/contracts/verification';
|
||||||
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
|
import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
|
||||||
|
|
||||||
export const LOADING = 'fetching-contract';
|
export const LOADING = 'fetching-contract';
|
||||||
@ -38,8 +38,10 @@ export default class VerificationStore {
|
|||||||
|
|
||||||
@observable contract = null;
|
@observable contract = null;
|
||||||
@observable fee = null;
|
@observable fee = null;
|
||||||
@observable isVerified = null;
|
@observable accountIsVerified = null;
|
||||||
@observable hasRequested = null;
|
@observable accountHasRequested = null;
|
||||||
|
@observable isAbleToRequest = null;
|
||||||
|
@observable lastRequestValues = null;
|
||||||
@observable isServerRunning = null;
|
@observable isServerRunning = null;
|
||||||
@observable consentGiven = false;
|
@observable consentGiven = false;
|
||||||
@observable requestTx = null;
|
@observable requestTx = null;
|
||||||
@ -68,6 +70,14 @@ export default class VerificationStore {
|
|||||||
console.error('verification: ' + this.error);
|
console.error('verification: ' + this.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
autorun(() => {
|
||||||
|
if (this.step !== QUERY_DATA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIfAbleToRequest();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action load = () => {
|
@action load = () => {
|
||||||
@ -91,19 +101,20 @@ export default class VerificationStore {
|
|||||||
this.error = 'Failed to fetch the fee: ' + err.message;
|
this.error = 'Failed to fetch the fee: ' + err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isVerified = checkIfVerified(contract, account)
|
const accountIsVerified = checkIfVerified(contract, account)
|
||||||
.then((isVerified) => {
|
.then((accountIsVerified) => {
|
||||||
this.isVerified = isVerified;
|
this.accountIsVerified = accountIsVerified;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.error = 'Failed to check if verified: ' + err.message;
|
this.error = 'Failed to check if verified: ' + err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasRequested = checkIfRequested(contract, account)
|
const accountHasRequested = findLastRequested(contract, account)
|
||||||
.then((txHash) => {
|
.then((log) => {
|
||||||
this.hasRequested = !!txHash;
|
this.accountHasRequested = !!log;
|
||||||
if (txHash) {
|
if (log) {
|
||||||
this.requestTx = txHash;
|
this.lastRequestValues = log.params;
|
||||||
|
this.requestTx = log.transactionHash;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -111,7 +122,7 @@ export default class VerificationStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all([ isServerRunning, fee, isVerified, hasRequested ])
|
.all([ isServerRunning, fee, accountIsVerified, accountHasRequested ])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.step = QUERY_DATA;
|
this.step = QUERY_DATA;
|
||||||
});
|
});
|
||||||
@ -150,40 +161,41 @@ export default class VerificationStore {
|
|||||||
requestValues = () => []
|
requestValues = () => []
|
||||||
|
|
||||||
@action sendRequest = () => {
|
@action sendRequest = () => {
|
||||||
const { api, account, contract, fee, hasRequested } = this;
|
const { api, account, contract, fee } = 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();
|
const values = this.requestValues();
|
||||||
|
|
||||||
let chain = Promise.resolve();
|
this.shallSkipRequest(values)
|
||||||
|
.then((skipRequest) => {
|
||||||
|
if (skipRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasRequested) {
|
this.step = POSTING_REQUEST;
|
||||||
this.step = POSTING_REQUEST;
|
return request.estimateGas(options, values)
|
||||||
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, values);
|
||||||
return request.postTransaction(options, values);
|
})
|
||||||
})
|
.then((handle) => {
|
||||||
.then((handle) => {
|
// The "request rejected" error doesn't have any property to distinguish
|
||||||
// TODO: The "request rejected" error doesn't have any property to
|
// it from other errors, so we can't give a meaningful error here.
|
||||||
// distinguish it from other errors, so we can't give a meaningful error here.
|
return api.pollMethod('parity_checkRequest', handle);
|
||||||
return api.pollMethod('parity_checkRequest', handle);
|
})
|
||||||
})
|
.then((txHash) => {
|
||||||
.then((txHash) => {
|
this.requestTx = txHash;
|
||||||
this.requestTx = txHash;
|
return checkIfTxFailed(api, txHash, options.gas)
|
||||||
return checkIfTxFailed(api, txHash, options.gas)
|
.then((hasFailed) => {
|
||||||
.then((hasFailed) => {
|
if (hasFailed) {
|
||||||
if (hasFailed) {
|
throw new Error('Transaction failed, all gas used up.');
|
||||||
throw new Error('Transaction failed, all gas used up.');
|
}
|
||||||
}
|
this.step = POSTED_REQUEST;
|
||||||
this.step = POSTED_REQUEST;
|
return waitForConfirmations(api, txHash, 1);
|
||||||
return waitForConfirmations(api, txHash, 1);
|
});
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
chain
|
|
||||||
.then(() => this.checkIfReceivedCode())
|
.then(() => this.checkIfReceivedCode())
|
||||||
.then((hasReceived) => {
|
.then((hasReceived) => {
|
||||||
if (hasReceived) {
|
if (hasReceived) {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { observable } from 'mobx';
|
import { observable } from 'mobx';
|
||||||
@ -206,7 +207,7 @@ class Verification extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
step,
|
step,
|
||||||
isServerRunning, fee, isVerified, hasRequested,
|
isServerRunning, isAbleToRequest, fee, accountIsVerified, accountHasRequested,
|
||||||
requestTx, isCodeValid, confirmationTx,
|
requestTx, isCodeValid, confirmationTx,
|
||||||
setCode
|
setCode
|
||||||
} = this.store;
|
} = this.store;
|
||||||
@ -223,17 +224,37 @@ class Verification extends Component {
|
|||||||
if (method === 'sms') {
|
if (method === 'sms') {
|
||||||
fields.push({
|
fields.push({
|
||||||
key: 'number',
|
key: 'number',
|
||||||
label: 'phone number in international format',
|
label: (
|
||||||
hint: 'the SMS will be sent to this number',
|
<FormattedMessage
|
||||||
|
id='ui.verification.gatherData.phoneNumber.label'
|
||||||
|
defaultMessage='phone number in international format'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
hint: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.verification.gatherData.phoneNumber.hint'
|
||||||
|
defaultMessage='the SMS will be sent to this number'
|
||||||
|
/>
|
||||||
|
),
|
||||||
error: this.store.isNumberValid ? null : 'invalid number',
|
error: this.store.isNumberValid ? null : 'invalid number',
|
||||||
onChange: this.store.setNumber
|
onChange: this.store.setNumber
|
||||||
});
|
});
|
||||||
} else if (method === 'email') {
|
} else if (method === 'email') {
|
||||||
fields.push({
|
fields.push({
|
||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'email address',
|
label: (
|
||||||
hint: 'the code will be sent to this address',
|
<FormattedMessage
|
||||||
error: this.store.isEmailValid ? null : 'invalid email',
|
id='ui.verification.gatherData.email.label'
|
||||||
|
defaultMessage='e-mail address'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
hint: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.verification.gatherData.email.hint'
|
||||||
|
defaultMessage='the code will be sent to this address'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
error: this.store.isEmailValid ? null : 'invalid e-mail',
|
||||||
onChange: this.store.setEmail
|
onChange: this.store.setEmail
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -241,10 +262,12 @@ class Verification extends Component {
|
|||||||
return (
|
return (
|
||||||
<GatherData
|
<GatherData
|
||||||
fee={ fee }
|
fee={ fee }
|
||||||
hasRequested={ hasRequested }
|
accountHasRequested={ accountHasRequested }
|
||||||
isServerRunning={ isServerRunning }
|
isServerRunning={ isServerRunning }
|
||||||
isVerified={ isVerified }
|
isAbleToRequest={ isAbleToRequest }
|
||||||
method={ method } fields={ fields }
|
accountIsVerified={ accountIsVerified }
|
||||||
|
method={ method }
|
||||||
|
fields={ fields }
|
||||||
setConsentGiven={ setConsentGiven }
|
setConsentGiven={ setConsentGiven }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user