feat: check wildcard addresses, retry several times to make sure that it really doesn't exist

This commit is contained in:
orion 2023-12-06 20:17:26 -06:00
parent 825c35969c
commit 687a9c76b8
Signed by: orion
GPG Key ID: 6D4165AE4C928719
2 changed files with 41 additions and 7 deletions

View File

@ -3,6 +3,7 @@ package:
- aff - aff
- aff-promise - aff-promise
- arrays - arrays
- bifunctors
- effect - effect
- either - either
- foldable-traversable - foldable-traversable
@ -11,6 +12,7 @@ package:
- nullable - nullable
- prelude - prelude
- strings - strings
- tailrec
- transformers - transformers
- typelevel-prelude - typelevel-prelude
name: email-address name: email-address

View File

@ -1,13 +1,16 @@
module Effect.Email (EmailError(..), emailErrorToString, deliverable) where module Effect.Email (EmailError(..), emailErrorCode, emailErrorToString, deliverable) where
import Prelude import Prelude
import Control.Monad.Cont (lift) import Control.Monad.Cont (lift)
import Control.Monad.Error.Class (throwError) import Control.Monad.Error.Class (liftEither, throwError)
import Control.Monad.Except (ExceptT) import Control.Monad.Except (ExceptT(..), runExceptT)
import Control.Monad.Rec.Class (Step(..), tailRecM)
import Control.Promise (Promise) import Control.Promise (Promise)
import Control.Promise as Promise import Control.Promise as Promise
import Data.Array as Array import Data.Array as Array
import Data.Bifunctor (lmap)
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)
@ -25,14 +28,25 @@ foreign import checkEmailImpl :: String -> Effect $ Promise $ QEDResult
data EmailError data EmailError
= EmailUnreachable Email = EmailUnreachable Email
| EmailWildcardExists Email
| EmailSyntaxInvalid Email | EmailSyntaxInvalid Email
| EmailMXInvalid Email | EmailMXInvalid Email
| EmailMXNoRecords Email | EmailMXNoRecords Email
| EmailSMTPInvalid Email | EmailSMTPInvalid Email
| EmailSMTPError Email String | EmailSMTPError Email String
emailErrorCode :: EmailError -> String
emailErrorCode (EmailUnreachable _) = "email_unreachable"
emailErrorCode (EmailWildcardExists _) = "email_wildcard"
emailErrorCode (EmailSyntaxInvalid _) = "email_invalid"
emailErrorCode (EmailMXInvalid _) = "email_mx_bad"
emailErrorCode (EmailMXNoRecords _) = "email_mx_missing"
emailErrorCode (EmailSMTPInvalid _) = "email_smtp_bad"
emailErrorCode (EmailSMTPError _ _) = "email_smtp_error"
emailErrorToString :: EmailError -> String emailErrorToString :: EmailError -> String
emailErrorToString (EmailUnreachable e) = "Email <" <> Email.toString e <> "> is unreachable" emailErrorToString (EmailUnreachable e) = "Email <" <> Email.toString e <> "> is unreachable"
emailErrorToString (EmailWildcardExists e) = "Wildcard address <*." <> Email.domain e <> "> is registered; cannot definitively say if <" <> Email.toString e <> "> exists."
emailErrorToString (EmailSyntaxInvalid e) = "Email <" <> Email.toString e <> "> is invalid" emailErrorToString (EmailSyntaxInvalid e) = "Email <" <> Email.toString e <> "> is invalid"
emailErrorToString (EmailMXInvalid e) = "Email <" <> Email.toString e <> "> domain has invalid MX record" emailErrorToString (EmailMXInvalid e) = "Email <" <> Email.toString e <> "> domain has invalid MX record"
emailErrorToString (EmailMXNoRecords e) = "Email <" <> Email.toString e <> "> domain has no MX records" emailErrorToString (EmailMXNoRecords e) = "Email <" <> Email.toString e <> "> domain has no MX records"
@ -64,10 +78,9 @@ type QEDResult =
} }
} }
deliverable :: Email -> ExceptT EmailError Aff Email deliverableOnce :: Email -> ExceptT EmailError Aff Email
deliverable email' = do deliverableOnce email' = do
let email = Email.toString email' let email = Email.toString email'
{ reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email { reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email
when (not reachable) $ throwError $ EmailUnreachable email' when (not reachable) $ throwError $ EmailUnreachable email'
when (not syntax.valid) $ throwError $ EmailSyntaxInvalid email' when (not syntax.valid) $ throwError $ EmailSyntaxInvalid email'
@ -75,5 +88,24 @@ deliverable email' = do
when (not smtp.valid) $ throwError $ EmailSMTPInvalid email' when (not smtp.valid) $ throwError $ EmailSMTPInvalid 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'
for_ (Nullable.toMaybe $ smtp.error) $ throwError <<< EmailSMTPError email' for_ (Nullable.toMaybe $ smtp.error) $ throwError <<< EmailSMTPError email'
pure email' pure email'
deliverable :: Email -> ExceptT EmailError Aff Email
deliverable email' =
let
attempt n
| n < 10 =
do
res <- runExceptT $ deliverableOnce email'
case res of
Left (EmailUnreachable _) -> pure $ Loop $ n + 1
Left e -> pure $ Done $ Left e
Right ok -> pure $ Done $ Right ok
| otherwise = pure $ Done $ Left $ EmailUnreachable email'
in
do
wildcard <- liftEither $ lmap (const $ EmailSyntaxInvalid email') $ Email.parse $ "*@" <> Email.domain email'
res <- lift $ runExceptT $ deliverableOnce wildcard
case res of
Right _ -> throwError $ EmailWildcardExists email'
_ -> ExceptT $ tailRecM attempt 0