Merge pull request #3289 from benjiwheeler/join-flow-validate-email-remotely

Join flow validate email remotely
This commit is contained in:
Benjamin Wheeler 2019-09-10 10:05:27 -04:00 committed by GitHub
commit 8f11de675d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 7 deletions

View file

@ -4,9 +4,9 @@ const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
import {Formik} from 'formik'; import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl'); const {injectIntl, intlShape} = require('react-intl');
const emailValidator = require('email-validator');
const FormattedMessage = require('react-intl').FormattedMessage; const FormattedMessage = require('react-intl').FormattedMessage;
const validate = require('../../lib/validate');
const JoinFlowStep = require('./join-flow-step.jsx'); const JoinFlowStep = require('./join-flow-step.jsx');
const FormikInput = require('../../components/formik-forms/formik-input.jsx'); const FormikInput = require('../../components/formik-forms/formik-input.jsx');
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx'); const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');
@ -75,11 +75,16 @@ class EmailStep extends React.Component {
} }
validateEmail (email) { validateEmail (email) {
if (!email) return this.props.intl.formatMessage({id: 'general.required'}); if (!email) return this.props.intl.formatMessage({id: 'general.required'});
const isValidLocally = emailValidator.validate(email); const localResult = validate.validateEmailLocally(email);
if (isValidLocally) { if (!localResult.valid) return this.props.intl.formatMessage({id: localResult.errMsgId});
return null; // TODO: validate email address remotely return validate.validateEmailRemotely(email).then(
remoteResult => {
if (remoteResult.valid === true) {
return null;
} }
return this.props.intl.formatMessage({id: 'registration.validationEmailInvalid'}); return this.props.intl.formatMessage({id: remoteResult.errMsgId});
}
);
} }
validateForm () { validateForm () {
return {}; return {};
@ -119,6 +124,8 @@ class EmailStep extends React.Component {
handleSubmit, handleSubmit,
isSubmitting, isSubmitting,
setFieldError, setFieldError,
setFieldTouched,
setFieldValue,
validateField validateField
} = props; } = props;
return ( return (
@ -161,7 +168,11 @@ class EmailStep extends React.Component {
validationClassName="validation-full-width-input" validationClassName="validation-full-width-input"
/* eslint-disable react/jsx-no-bind */ /* eslint-disable react/jsx-no-bind */
onBlur={() => validateField('email')} 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 */ /* eslint-enable react/jsx-no-bind */
onSetRef={this.handleSetEmailRef} onSetRef={this.handleSetEmailRef}
/> />

View file

@ -1,5 +1,6 @@
module.exports = {}; module.exports = {};
const api = require('./api'); const api = require('./api');
const emailValidator = require('email-validator');
module.exports.validateUsernameLocally = username => { module.exports.validateUsernameLocally = username => {
if (!username || username === '') { if (!username || username === '') {
@ -67,3 +68,36 @@ module.exports.validatePasswordConfirm = (password, passwordConfirm) => {
} }
return {valid: true}; 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;
}
});
})
);

View file

@ -63,4 +63,52 @@ describe('unit test lib/validate.js', () => {
response = validate.validatePasswordConfirm('', 'abcdefg'); response = validate.validatePasswordConfirm('', 'abcdefg');
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'}); 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 <email@example.com>');
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'});
});
}); });