fix: randomly choose a local address to query from

This commit is contained in:
orion 2024-02-07 15:50:11 -06:00
parent a91ed02d7d
commit e304960ee5
Signed by: orion
GPG Key ID: 6D4165AE4C928719
12 changed files with 6443 additions and 141 deletions

2
.gitignore vendored
View File

@ -11,3 +11,5 @@
.log .log
.purs-repl .purs-repl
.env .env
qed-mail/node_modules/
qed-mail/dist/

BIN
bun.lockb

Binary file not shown.

View File

@ -14,5 +14,5 @@
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"
}, },
"dependencies": { "qed-mail": "^1.0.2" } "dependencies": { "qed-mail": "./qed-mail/qed-mail-1.0.1@beta-1.tgz" }
} }

130
qed-mail/.gitignore vendored
View File

@ -1,130 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View File

@ -1,6 +1,6 @@
{ {
"name": "qed-mail", "name": "qed-mail",
"version": "1.0.1", "version": "1.0.1@beta-1",
"description": "📮 A NodeJS library for checking if an email address exists without sending any email.", "description": "📮 A NodeJS library for checking if an email address exists without sending any email.",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

Binary file not shown.

View File

@ -11,7 +11,7 @@ const DEFAULT_RESULT = {
smtp: { valid: false }, smtp: { valid: false },
}; };
async function checkEmail(email: string): Promise<Result> { async function checkEmail(email: string, {fromAddr}: {fromAddr?: string} = {}): Promise<Result> {
const result: Result = { email, ...DEFAULT_RESULT }; const result: Result = { email, ...DEFAULT_RESULT };
const syntax = checkSyntax(email); const syntax = checkSyntax(email);
@ -25,7 +25,7 @@ async function checkEmail(email: string): Promise<Result> {
} }
const records = mx.mxRecords.sort((a, b) => a.priority - b.priority); const records = mx.mxRecords.sort((a, b) => a.priority - b.priority);
const smtp = await checkSMTP(email, records[0]!.exchange, 25); const smtp = await checkSMTP(email, records[0]!.exchange, 25, {fromAddr});
if (!smtp.valid) { if (!smtp.valid) {
return { ...result, syntax, mx, smtp }; return { ...result, syntax, mx, smtp };
} }

View File

@ -1,16 +1,18 @@
import { Client } from "smtp-fetch"; import { Client } from "smtp-fetch";
import { SMTPResult } from "../types"; import { SMTPResult } from "../types";
import { TcpNetConnectOpts } from "net";
async function checkSMTP( async function checkSMTP(
to: string, to: string,
host: string, host: string,
port: number, port: number,
timeout: number = 5000 {fromAddr, timeout = 5000}: {timeout?: number, fromAddr?: string} = {},
): Promise<SMTPResult> { ): Promise<SMTPResult> {
const c = new Client(host, port); const c = new Client(host, port);
try { try {
await c.connect({ timeout }); const options: Omit<TcpNetConnectOpts, 'host' | 'port'> = { timeout, localAddress: fromAddr };
await c.connect(options);
await c.mail(""); await c.mail("");
await c.rcpt(to); await c.rcpt(to);

6416
spago.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,14 @@ package:
- bifunctors - bifunctors
- effect - effect
- either - either
- filterable
- foldable-traversable - foldable-traversable
- maybe - maybe
- newtype - newtype
- node-os
- nullable - nullable
- prelude - prelude
- random
- strings - strings
- tailrec - tailrec
- transformers - transformers

View File

@ -1,4 +1,4 @@
import { checkEmail } from 'qed-mail' import { checkEmail } from 'qed-mail'
/** @type {(_: string) => () => Promise<import('qed-mail/src/types.js').Result>} */ /** @type {(email: string) => (addr: string | null) => () => Promise<import('qed-mail/src/types.js').Result>} */
export const checkEmailImpl = s => () => checkEmail(s) export const checkEmailImpl = s => addr => () => checkEmail(s, {fromAddr: addr || undefined})

View File

@ -14,6 +14,8 @@ import Data.Either (Either(..))
import Data.Email (Email) import Data.Email (Email)
import Data.Email as Email import Data.Email as Email
import Data.Eq.Generic (genericEq) import Data.Eq.Generic (genericEq)
import Data.Filterable (filter)
import Data.Foldable (fold)
import Data.Generic.Rep (class Generic) import Data.Generic.Rep (class Generic)
import Data.Int as Int import Data.Int as Int
import Data.Maybe (maybe) import Data.Maybe (maybe)
@ -24,9 +26,12 @@ import Data.Show.Generic (genericShow)
import Data.Traversable (for_) import Data.Traversable (for_)
import Effect (Effect) import Effect (Effect)
import Effect.Aff (Aff, delay) import Effect.Aff (Aff, delay)
import Effect.Class (liftEffect)
import Effect.Random (randomInt)
import Node.OS as Node.OS
import Type.Function (type ($)) import Type.Function (type ($))
foreign import checkEmailImpl :: String -> Effect $ Promise $ QEDResult foreign import checkEmailImpl :: String -> Nullable String -> Effect $ Promise $ QEDResult
data EmailError data EmailError
= EmailUnreachable Email = EmailUnreachable Email
@ -82,8 +87,12 @@ type QEDResult =
deliverableOnce :: Email -> ExceptT EmailError Aff Email deliverableOnce :: Email -> ExceptT EmailError Aff Email
deliverableOnce email' = do deliverableOnce email' = do
let email = Email.toString email' addrs <- liftEffect $ filter (not <<< _.internal) <$> fold <$> Node.OS.networkInterfaces
{ reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email chosen <- liftEffect $ randomInt 0 (Array.length addrs - 1)
let
addr = _.address <$> Array.index addrs chosen
email = Email.toString email'
{ reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email (Nullable.toNullable addr)
when (not syntax.valid) $ throwError $ EmailSyntaxInvalid email' when (not syntax.valid) $ throwError $ EmailSyntaxInvalid email'
when (not mx.valid) $ throwError $ EmailMXInvalid email' when (not mx.valid) $ throwError $ EmailMXInvalid email'
when (maybe true Array.null $ Nullable.toMaybe $ mx.mxRecords) $ throwError $ EmailMXNoRecords email' when (maybe true Array.null $ Nullable.toMaybe $ mx.mxRecords) $ throwError $ EmailMXNoRecords email'