diff --git a/src/components/join-flow/email-step.jsx b/src/components/join-flow/email-step.jsx index 88a24c97a..5e42edeac 100644 --- a/src/components/join-flow/email-step.jsx +++ b/src/components/join-flow/email-step.jsx @@ -4,9 +4,9 @@ const React = require('react'); const PropTypes = require('prop-types'); import {Formik} from 'formik'; const {injectIntl, intlShape} = require('react-intl'); -const emailValidator = require('email-validator'); const FormattedMessage = require('react-intl').FormattedMessage; +const validate = require('../../lib/validate'); const JoinFlowStep = require('./join-flow-step.jsx'); const FormikInput = require('../../components/formik-forms/formik-input.jsx'); const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx'); @@ -75,11 +75,16 @@ class EmailStep extends React.Component { } validateEmail (email) { if (!email) return this.props.intl.formatMessage({id: 'general.required'}); - const isValidLocally = emailValidator.validate(email); - if (isValidLocally) { - return null; // TODO: validate email address remotely - } - return this.props.intl.formatMessage({id: 'registration.validationEmailInvalid'}); + const localResult = validate.validateEmailLocally(email); + if (!localResult.valid) return this.props.intl.formatMessage({id: localResult.errMsgId}); + return validate.validateEmailRemotely(email).then( + remoteResult => { + if (remoteResult.valid === true) { + return null; + } + return this.props.intl.formatMessage({id: remoteResult.errMsgId}); + } + ); } validateForm () { return {}; @@ -119,6 +124,8 @@ class EmailStep extends React.Component { handleSubmit, isSubmitting, setFieldError, + setFieldTouched, + setFieldValue, validateField } = props; return ( @@ -161,7 +168,11 @@ class EmailStep extends React.Component { validationClassName="validation-full-width-input" /* eslint-disable react/jsx-no-bind */ onBlur={() => validateField('email')} - onFocus={() => setFieldError('email', null)} + onChange={e => { + setFieldValue('email', e.target.value); + setFieldTouched('email'); + setFieldError('email', null); + }} /* eslint-enable react/jsx-no-bind */ onSetRef={this.handleSetEmailRef} /> diff --git a/src/lib/validate.js b/src/lib/validate.js index b4fd84f31..5df20b0e4 100644 --- a/src/lib/validate.js +++ b/src/lib/validate.js @@ -1,5 +1,6 @@ module.exports = {}; const api = require('./api'); +const emailValidator = require('email-validator'); module.exports.validateUsernameLocally = username => { if (!username || username === '') { @@ -67,3 +68,36 @@ module.exports.validatePasswordConfirm = (password, passwordConfirm) => { } return {valid: true}; }; + +module.exports.validateEmailLocally = email => { + if (!email || email === '') { + return {valid: false, errMsgId: 'general.required'}; + } else if (emailValidator.validate(email)) { + return {valid: true}; + } + return ({valid: false, errMsgId: 'registration.validationEmailInvalid'}); +}; + +module.exports.validateEmailRemotely = email => ( + new Promise(resolve => { + api({ + host: '', // not handled by API; use existing infrastructure + params: {email: email}, + uri: '/accounts/check_email/' + }, (err, body, res) => { + if (err || res.statusCode !== 200 || !body || body.length < 1 || !body[0].msg) { + resolve({valid: false, errMsgId: 'general.apiError'}); + } + switch (body[0].msg) { + case 'valid email': + resolve({valid: true}); + break; + case 'Scratch is not allowed to send email to this address.': // e.g., bad TLD or block-listed + case 'Enter a valid email address.': + default: + resolve({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + break; + } + }); + }) +); diff --git a/test/unit/lib/validate.test.js b/test/unit/lib/validate.test.js index 03922882f..a051e1faa 100644 --- a/test/unit/lib/validate.test.js +++ b/test/unit/lib/validate.test.js @@ -63,4 +63,52 @@ describe('unit test lib/validate.js', () => { response = validate.validatePasswordConfirm('', 'abcdefg'); expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'}); }); + + test('validate email address locally', () => { + let response; + expect(typeof validate.validateEmailLocally).toBe('function'); + + // permitted addresses: + response = validate.validateEmailLocally('abc@def.com'); + expect(response).toEqual({valid: true}); + response = validate.validateEmailLocally('abcdefghijklmnopqrst@abcdefghijklmnopqrst.info'); + expect(response).toEqual({valid: true}); + response = validate.validateEmailLocally('abc-def-ghi@jkl-mno.org'); + expect(response).toEqual({valid: true}); + response = validate.validateEmailLocally('_______@example.com'); + expect(response).toEqual({valid: true}); + response = validate.validateEmailLocally('email@example.museum'); + expect(response).toEqual({valid: true}); + response = validate.validateEmailLocally('email@example.co.jp'); + expect(response).toEqual({valid: true}); + + // non-permitted addresses: + response = validate.validateEmailLocally(''); + expect(response).toEqual({valid: false, errMsgId: 'general.required'}); + response = validate.validateEmailLocally('a'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('abc@def'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('abc@def.c'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('abcπŸ˜„def@emoji.pizza'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('γ‚γ„γ†γˆγŠ@example.com'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('Abc..123@example.com'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('Joe Smith '); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('email@example@example.com'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('email@example..com'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + + // edge cases: + // these are strictly legal according to email addres spec, but rejected by library we use: + response = validate.validateEmailLocally('email@123.123.123.123'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + response = validate.validateEmailLocally('much."more unusual"@example.com'); + expect(response).toEqual({valid: false, errMsgId: 'registration.validationEmailInvalid'}); + }); });