forked from BeeWell/cross-email-validator
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8bb3a93
commit dd121aa
Showing
5 changed files
with
1,266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
{ | ||
"env": { | ||
"es6": true, | ||
"node": true, | ||
"browser": true | ||
}, | ||
"extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:react-native/all", "plugin:lodash/recommended" ], | ||
"parserOptions": { | ||
"ecmaFeatures": { | ||
"jsx": true | ||
}, | ||
"ecmaVersion": 2018, | ||
"sourceType": "module" | ||
}, | ||
"plugins": [ | ||
"react", | ||
"react-native", | ||
"react-filenames", | ||
"lodash", | ||
"dependencies" | ||
], | ||
"rules": { | ||
"indent": [ "error", 2 ], | ||
"linebreak-style": [ "error", "unix" ], | ||
"semi": [ "warn", "always" ], | ||
"no-await-in-loop": [ "error" ], | ||
"no-prototype-builtins": [ "error" ], | ||
"no-template-curly-in-string": [ "warn" ], | ||
"array-callback-return": [ "error" ], | ||
"block-scoped-var": [ "warn" ], | ||
"complexity": [ "warn" ], | ||
"consistent-return": [ "error" ], | ||
"curly": [ "error", "multi-line" ], | ||
"default-case": [ "error" ], | ||
"dot-location": [ "warn", "object" ], | ||
"dot-notation": [ "warn", {"allowKeywords": true} ], | ||
"eqeqeq": [ "error" ], | ||
"no-alert": [ "error" ], | ||
"no-caller": [ "error" ], | ||
"no-div-regex": [ "error" ], | ||
"no-eq-null": [ "error" ], | ||
"no-eval": [ "error" ], | ||
"no-extra-bind": [ "warn" ], | ||
"no-extra-label": [ "warn" ], | ||
"no-floating-decimal": [ "error" ], | ||
"no-implicit-globals": [ "error" ], | ||
"no-implied-eval": [ "error" ], | ||
"no-invalid-this": [ "error" ], | ||
"no-iterator": [ "error" ], | ||
"no-lone-blocks": [ "warn" ], | ||
"no-loop-func": [ "error" ], | ||
"no-magic-numbers": [ "error", { "ignore": [0,1] } ], | ||
"no-new-func": [ "error" ], | ||
"no-new-wrappers": [ "error" ], | ||
"no-proto": [ "error" ], | ||
"no-return-assign": [ "error" ], | ||
"no-return-await": [ "error" ], | ||
"no-self-compare": [ "error" ], | ||
"no-sequences": [ "error" ], | ||
"no-unmodified-loop-condition": [ "warn" ], | ||
"no-unused-expressions": [ "error", { "allowTernary": true, "allowShortCircuit": true } ], | ||
"no-useless-call": [ "error" ], | ||
"no-useless-return": [ "error" ], | ||
"no-void": [ "error" ], | ||
"no-with": [ "error" ], | ||
"radix": [ "error", "always" ], | ||
"require-await": [ "error" ], | ||
"strict": [ "error", "global" ], | ||
"no-label-var": [ "error" ], | ||
"no-shadow": [ "error" ], | ||
"no-shadow-restricted-names": [ "error" ], | ||
"no-undef-init": [ "error" ], | ||
"no-use-before-define": [ "error" ], | ||
"no-bitwise": [ "error" ], | ||
"no-var": [ "error" ], | ||
"prefer-arrow-callback": [ "warn" ], | ||
"prefer-const": [ "error" ], | ||
"prefer-destructuring": [ | ||
"error", | ||
{"array": false}, | ||
{"enforceForRenamedProperties": false} | ||
], | ||
"prefer-rest-params": [ "warn" ], | ||
"prefer-spread": [ "warn" ], | ||
"prefer-template": [ "warn" ], | ||
"yield-star-spacing": [ "error", "after" ], | ||
"callback-return": [ "error" ], | ||
"handle-callback-err": [ "error" ], | ||
"no-buffer-constructor": [ "error" ], | ||
"no-sync": [ "error", {"allowAtRootLevel": true} ], | ||
"array-bracket-newline": [ "warn", "consistent" ], | ||
"camelcase": [ "warn", { "ignoreDestructuring": true } ], | ||
"comma-dangle": [ | ||
"error", | ||
{ "arrays": "always-multiline", | ||
"objects": "always-multiline", | ||
"imports": "always-multiline", | ||
"exports": "always-multiline", | ||
"functions": "never" | ||
} | ||
], | ||
"function-paren-newline": [ "warn" ], | ||
"implicit-arrow-linebreak": [ "error", "beside" ], | ||
"no-mixed-operators": [ "error" ], | ||
"no-trailing-spaces": [ "warn", { "ignoreComments": true } ], | ||
"no-unneeded-ternary": [ "warn" ], | ||
"prefer-object-spread": [ "error" ], | ||
"arrow-body-style": [ "error", "as-needed" ], | ||
"arrow-parens": [ "error" ], | ||
"generator-star-spacing": [ "error", "after" ], | ||
"no-confusing-arrow": [ "error" ], | ||
"no-duplicate-imports": [ "error", { "includeExports": true } ], | ||
"no-useless-computed-key": [ "warn" ], | ||
"no-useless-rename": [ "error" ], | ||
"no-await-in-loop": [ "error" ], | ||
"no-console": [ "error", { "allow": ["debug","warn","error"] } ], | ||
"lodash/import-scope": [ "off" ], | ||
"lodash/prefer-lodash-method": [ "off" ], | ||
"lodash/prop-shorthand": [ "warn" ], | ||
"lodash/path-style": [ "warn" ], | ||
"template-tag-spacing": [ "warn" ], | ||
"react-native/no-inline-styles": [ "warn" ], | ||
"react/prop-types": [ "warn" ], | ||
"dependencies/case-sensitive": [ "error" ], | ||
"dependencies/no-cycles": [ "warn" ], | ||
"dependencies/no-unresolved": [ "off" ], | ||
"dependencies/require-json-ext": [ "warn" ], | ||
"react-filenames/filename-matches-component": [ "off" ] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,39 @@ | ||
# react-native-email-deep-validator | ||
Goes beyond validating the characters that make up the email, and instead performs checks on the network to ensure that the e-mail address is valid. | ||
|
||
# Synposis | ||
|
||
```bash | ||
yarn add react-native-email-validator | ||
``` | ||
|
||
```javascript | ||
import validateEmail from "react-native-email-validator" | ||
|
||
await validateEmail("developer+rnev@beewell.health"); // returns true | ||
await validateEmail("@beewell.health"); // returns false | ||
await validateEmail("developer+rnev@health"); // returns false | ||
``` | ||
|
||
# Validation Phases | ||
|
||
## Step 1: Syntax | ||
|
||
* It first ensures that the value is a non-empty string, that the character '`@`' is somewhere in that string, and that it is not the first or last character. | ||
* Then uses [validator/lib/isEmail](https://www.npmjs.com/package/validator) for validating the e-mail address format, which amounts to firing a big ugly regexp against it. | ||
|
||
## Step 2: Network Checks | ||
|
||
* Calls out to [the `1.1.1.1` DNS over HTTPS server](https://developers.cloudflare.com/1.1.1.1/dns-over-https/) to ensure that there exists at least one MX record for the domain. | ||
* At the same time, calls out to the [Kickbox "disposable email" API](https://open.kickbox.com/v1/disposable/beewell.health) to validate that the e-mail domain is not a disposable email domain. | ||
* It'd be nice to do an SMTP check, but it's unclear how to do that from within React Native without unlinking. | ||
|
||
Note that any network check that errors out will be counted as a pass. So if there is no internet, the internet connection is very slow, or the server is down, then it is equivalent to the network check returning valid. | ||
|
||
# License | ||
|
||
MIT. See the file named `LICENSE` in this directory for details. | ||
|
||
# Inspiration | ||
|
||
[email-deep-validator](https://github.com/getconversio/email-deep-validator/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
|
||
|
||
const { isNil, isEmpty, isString, merge, not } = require("lodash"); | ||
const looksLikeEmail = require("validator/lib/isEmail"); | ||
const Promise = require("bluebird"); | ||
const URI = require("urijs"); | ||
|
||
const atSymbol = "@"; | ||
const getDnsOverHttpUri = ( | ||
(baseUri) => (domainName) => baseUri.clone().setQuery({name: domainName}) | ||
)(new URI("https://cloudflare-dns.com/dns-query").setQuery({ | ||
'type': 'MX', | ||
'do': true, | ||
'cd': false, | ||
})); | ||
const getBurnerCheckUri = ( | ||
(baseUri) => (domainName) => baseUri.clone().filename(domainName) | ||
)(new URI("https://open.kickbox.com/v1/disposable/beewell.health")); | ||
|
||
const doFetch = (uri, customOptions={}) => Promise.try(() => fetch(uri, merge({ | ||
credentials: 'omit', | ||
mode: 'no-cors', | ||
method: 'GET', | ||
}, customOptions)).tap((response) => { | ||
if(!response.ok) throw new Error("Network error"); | ||
}).get("body").call("json")); | ||
|
||
|
||
module.exports = (addr) => { | ||
const onError = (msg) => (error) => { | ||
console.warn(`Error while ${msg}; returning true by default`, { addr, error}); | ||
return true; | ||
}; | ||
const logResults = (msg) => (result) => console.debug( | ||
`Results from ${msg} => ${result}`, | ||
{ addr, result } | ||
); | ||
|
||
const timeLimitMs = 5000; | ||
|
||
const runCheck = (msg, func) => Promise.try(() => func(addr)).timeout(timeLimitMs).catch(onError(msg)).tap(logResults(msg)); | ||
|
||
const validatingName = "validating email address"; | ||
return runCheck(validatingName, () => { | ||
|
||
// Basic sanity checks. | ||
if(isNil(addr)) return false; | ||
if(!(isString(addr))) return false; | ||
if(isEmpty(addr.trim())) return false; | ||
|
||
// Ensure that '@' is not the first or last char. | ||
if(addr.startsWith(atSymbol)) return false; | ||
if(addr.endsWith(atSymbol)) return false; | ||
|
||
// Now split to get the username and domain name | ||
const [username, domainName, ...addrExtra] = addr.split(atSymbol); | ||
if(!(isNil(addrExtra) || isEmpty(addrExtra))) return false; | ||
if(isNil(username) || isNil(domainName)) return false; | ||
if(isEmpty(username.trim()) || isEmpty(domainName.trim())) return false; | ||
|
||
// Construct the checks. | ||
const syntaxCheckName = "performing the syntax check"; | ||
const syntaxCheck = runCheck( syntaxCheckName, looksLikeEmail ); | ||
|
||
const mxRecordCheckName = "performing the MX record DNS check"; | ||
const mxRecordCheck = runCheck( | ||
mxRecordCheckName, | ||
() => doFetch( | ||
getDnsOverHttpUri(domainName), | ||
{ headers: { accept: 'application/dns-json' } } | ||
).tap((result) => { | ||
if(result.Status !== 0) { | ||
throw new Error(`Bad status code from DNS query: ${result.Status}`); | ||
} | ||
}).get("Answer").then((answer) => { | ||
if(isNil(answer)) { | ||
throw new Error(`No answer returned: ${answer}`); | ||
} | ||
if(isEmpty(answer)) { | ||
return false; | ||
} | ||
return true; | ||
}) | ||
); | ||
|
||
const burnerName = "performing burner e-mail check"; | ||
const burnerCheck = runCheck( | ||
burnerName, | ||
() => doFetch(getBurnerCheckUri(domainName)).get("disposable").then((result) => { | ||
if(isNil(result)) { | ||
throw new Error(`No result returned: ${result}`); | ||
} | ||
return !result; | ||
}) | ||
); | ||
|
||
const compilingName = "compiling results"; | ||
return runCheck(compilingName, () => Promise.filter([syntaxCheck,mxRecordCheck,burnerCheck], not).then((failures) => isNil(failures) || isEmpty(failures))); | ||
|
||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "react-native-email-validator", | ||
"version": "1.0.0", | ||
"description": "Goes beyond validating the characters that make up the email, and additionally performs DNS and mailbox checks to ensure that the email address is valid.", | ||
"main": "index.js", | ||
"repository": "git@github.com:BeeWell/react-native-email-deep-validator.git", | ||
"author": "Robert Fischer <robert+github@getbeewell.com>", | ||
"license": "MIT", | ||
"private": false, | ||
"sideEffects": false, | ||
"dependencies": { | ||
"bluebird": "^3.5.1", | ||
"lodash": "^4.17.10", | ||
"urijs": "^1.19.1", | ||
"validator": "^10.4.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^5.1.0", | ||
"eslint-plugin-dependencies": "^2.4.0", | ||
"eslint-plugin-lodash": "^2.7.0", | ||
"eslint-plugin-react": "^7.10.0", | ||
"eslint-plugin-react-filenames": "^0.0.2", | ||
"eslint-plugin-react-native": "^3.2.1" | ||
}, | ||
"scripts": { | ||
"eslint": "eslint --cache --fix ./index.js 2>&1 1>/dev/null || eslint --cache --color --report-unused-disable-directives ./index.js" | ||
} | ||
} |
Oops, something went wrong.