Skip to content

Commit 1e32009

Browse files
authored
Cleanup and improving bad mx host check (#17)
Reporting on incorrect MX hosts (introducing `misconfigured_mx` in the suggest response). Also: documentation and CI improvements
1 parent a24e708 commit 1e32009

25 files changed

+255
-157
lines changed

.circleci/config.yml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,25 @@ jobs:
77
working_directory: /home/circleci/eri
88
docker:
99
# specify the version
10-
- image: circleci/golang:1.17
10+
- image: cimg/go:1.19
1111

1212
environment:
1313
BINARY_NAME: "eri-linux-amd64"
1414
TEST_RESULTS: "/tmp/test-results"
15+
GOFLAGS: "-buildvcs=false -trimpath"
1516

1617
steps:
1718
- checkout
1819
- run: mkdir -p ${TEST_RESULTS}
1920
- run: go install github.com/jstemmer/go-junit-report@latest
20-
- run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.43.0
21+
- run: go install github.com/mattn/goveralls@latest
22+
- run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.49.0
2123

2224
- run:
2325
name: Build
2426
command: |
2527
TAG=${CIRCLE_TAG:-dev}
26-
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "${BINARY_NAME}" -a -ldflags="-w -s -X main.Version=${TAG}" ./cmd/web
28+
GOFLAGS="-buildvcs=false -trimpath" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "${BINARY_NAME}" -a -ldflags="-w -s -X main.Version=${TAG}" ./cmd/web
2729
2830
- run:
2931
# Check if we have updates to minor/patch level packages we're explicitly referencing
@@ -40,8 +42,7 @@ jobs:
4042
name: Test
4143
command: |
4244
go test -v ./... | go-junit-report > ${TEST_RESULTS}/report.xml
43-
go test -cover -coverprofile=${TEST_RESULTS}/coverage.txt -covermode=atomic ./...
44-
go test -race ./...
45+
go test -cover -race -covermode=atomic -coverprofile=${TEST_RESULTS}/coverage.txt ./...
4546
go tool cover -html=${TEST_RESULTS}/coverage.txt -o ${TEST_RESULTS}/coverage.html
4647
4748
- store_test_results:
@@ -51,16 +52,19 @@ jobs:
5152
path: "/tmp/test-results"
5253

5354
- run:
54-
name: Codecov upload
55+
name: Coveralls upload
5556
command: |
56-
bash <(curl -s https://codecov.io/bash) -f ${TEST_RESULTS}/coverage.txt
57+
goveralls -coverprofile=${TEST_RESULTS}/coverage.txt -service=circle-ci -repotoken=${COVERALLS_TOKEN}
5758
5859
5960
workflows:
6061
version: 2
6162
build-test:
6263
jobs:
6364
- lint-and-test:
65+
context:
66+
- org-global
67+
- "Public repos"
6468
filters:
6569
branches:
6670
only: /.*/

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
# ERI
8-
Email Recipient Inspector is a project for preventing email typos. It's a self-learning service, a library or a command line utility. The services can help your uses to prevent mistakes when entering their email address. The library allows you to incorporate the features in your own business layer and the cli can be used as a convenient way to test domains or e-mail addresses.
8+
Email Recipient Inspector is a project for preventing email typos. It's a self-learning service, a library or a command line utility. The services can help your uses to prevent mistakes when entering their email address. The library allows you to incorporate the features in your own business layer and the cli can be used as a convenient way to test domains or email addresses.
99

1010
# ERI as command line utility
1111
## Installation
@@ -93,9 +93,16 @@ The local part (left of the `@`) remains completely untouched. It's simply echoe
9393
"alternatives": [
9494
"john.doe@example.org"
9595
],
96-
"malformed_syntax": false
96+
"malformed_syntax": false,
97+
"misconfigured_mx": false
9798
}
9899
```
100+
##### The advisory fields
101+
Please take note: These fields are advisory. Email delivery is still possible (even though unlikely) when these advisory fields are false. For example the recipient "root" on a local system is considered invalid. For web-use, however, It'll be mostly correct.
102+
103+
- `malformed_syntax` (bool) is an indication of the syntax. The check is fairly liberal. If `true`, chances are pretty good the email will never work.` _Note: this is permanent_.
104+
- `misconfigured_mx` (bool) is an indication of a misconfigured MX. If `true`, it's unlikely that the host can accept email. _Note: this can be temporary!_.
105+
99106

100107
### /autocomplete
101108
The autocomplete endpoint returns a list of domains matching the prefix. To prevent leaking sensitive information, ERI is configured with a threshold to limit exposure of rarely used domains.
@@ -171,7 +178,7 @@ For more help, see the package: https://github.com/Dynom/ERI-js
171178

172179
# Integration
173180
## Data scrubbing
174-
When integrating ERI in your application, the initial results might be poor. When you change the validation mechanism (to include ERI) your data might still be too "dirty" to work with. After feeding your existing e-mail addresses into ERI you might want to cleanup the data first. The autocomplete endpoint might give odd results (e.g.: hotmail.com.com). Scrubbing this data from ERI's hitlist table and with the new mechanisms in place should prevent those addresses to end up into your backend in the future, but without the scrubbing you'll stay in a less-than-ideal situation.
181+
When integrating ERI in your application, the initial results might be poor. When you change the validation mechanism (to include ERI) your data might still be too "dirty" to work with. After feeding your existing email addresses into ERI you might want to cleanup the data first. The autocomplete endpoint might give odd results (e.g.: hotmail.com.com). Scrubbing this data from ERI's hitlist table and with the new mechanisms in place should prevent those addresses to end up into your backend in the future, but without the scrubbing you'll stay in a less-than-ideal situation.
175182

176183
## To proxy or to expose directly
177184
While ERI is designed to be exposed publicly, you might have different ideas about how to protect your backend services. Adding a proxy is a good alternative, and it allows you to fine-tune the rate-limiter to that specific use-case.
@@ -201,7 +208,7 @@ Mailcheck works completely in JavaScript, with the option to use only known TLDs
201208

202209

203210
# Email delivery nuances
204-
Ever since the first e-mail got sent in 1971 a lot has happened with electronic mail. In modern days email is seen as "the" way to identify and communicate with people online. Because of this, many people will easily give away their email addresses and people receive many, many emails. It's hard to read it all, not even counting the spam. Looking specifically at my own behaviour, I don't even open email unless I think it's important, just by scanning the sender and the subject of the email.
211+
Ever since the first email got sent in 1971 a lot has happened with electronic mail. In modern days email is seen as "the" way to identify and communicate with people online. Because of this, many people will easily give away their email addresses and people receive many, many emails. It's hard to read it all, not even counting the spam. Looking specifically at my own behaviour, I don't even open email unless I think it's important, just by scanning the sender and the subject of the email.
205212

206213
With this in mind, even with a perfect validator, and a brilliantly composed and relevant email, it's still possible your email won't be read. ERI is designed to help out the user willing to trust you with their email address. ERI is not designed as a marketing tool to help optimise email delivery.
207214

cmd/eri-cli/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,8 @@ bzcat emails.bz2 | \
9494
jq .email | \
9595
xargs ./updateStatus.sh
9696
```
97+
98+
Using Shell process substitution
99+
```bash
100+
eri-cli check --input-is-email < <( echo "john@example.org" ) | jq .valid
101+
```

cmd/web/config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"encoding"
55
"errors"
66
"fmt"
7-
"io/ioutil"
7+
"os"
88
"strings"
99
"time"
1010

@@ -26,7 +26,7 @@ func NewConfig(fileName string) (Config, error) {
2626
// Not reading a config file on startup, might not show any feedback
2727
c.Log.Level = logrus.TraceLevel.String()
2828

29-
b, err := ioutil.ReadFile(fileName)
29+
b, err := os.ReadFile(fileName)
3030
if err != nil {
3131
return c, fmt.Errorf("unable to open %q, reason: %w", fileName, err)
3232
}

cmd/web/erihttp/handlers/compression_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package handlers
22

33
import (
4-
"io/ioutil"
4+
"io"
55
"net/http"
66
"net/http/httptest"
77
"strings"
@@ -44,9 +44,9 @@ func TestWithGzipHandler(t *testing.T) {
4444

4545
mux := http.NewServeMux()
4646
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
47-
b, err := ioutil.ReadAll(r.Body)
47+
b, err := io.ReadAll(r.Body)
4848
if err != nil {
49-
t.Errorf("ioutil.ReadAll(r.Body) Setting up the test failed %s", err)
49+
t.Errorf("io.ReadAll(r.Body) Setting up the test failed %s", err)
5050
t.FailNow()
5151
}
5252

@@ -88,9 +88,9 @@ func TestWithGzipHandler(t *testing.T) {
8888
}
8989

9090
defer res.Body.Close()
91-
b, err := ioutil.ReadAll(res.Body)
91+
b, err := io.ReadAll(res.Body)
9292
if err != nil {
93-
t.Errorf("ioutil.ReadAll(res.Body) Setting up the test failed %s", err)
93+
t.Errorf("io.ReadAll(res.Body) Setting up the test failed %s", err)
9494
t.FailNow()
9595
}
9696

cmd/web/erihttp/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func (r *AutoCompleteResponse) PrepareResponse() {
3333
type SuggestResponse struct {
3434
Alternatives []string `json:"alternatives"`
3535
MalformedSyntax bool `json:"malformed_syntax"`
36+
MisconfiguredMX bool `json:"misconfigured_mx"`
3637
Error string `json:"error,omitempty"`
3738
}
3839

cmd/web/erihttp/util.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package erihttp
33
import (
44
"fmt"
55
"io"
6-
"io/ioutil"
76
"net/http"
87
)
98

@@ -37,7 +36,7 @@ func GetBodyFromHTTPRequest(r *http.Request, maxBodySize int64) ([]byte, error)
3736
return empty, fmt.Errorf("%w %q", ErrUnsupportedContentType, ct)
3837
}
3938

40-
b, err := ioutil.ReadAll(io.LimitReader(r.Body, maxBodySize+1))
39+
b, err := io.ReadAll(io.LimitReader(r.Body, maxBodySize+1))
4140
if err != nil {
4241
return empty, ErrInvalidRequest
4342
}

cmd/web/graphql.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ func NewGraphQLSchema(conf config.Config, suggestSvc *services.SuggestSvc, autoc
6969

7070
return erihttp.SuggestResponse{
7171
Alternatives: result.Alternatives,
72-
MalformedSyntax: sugErr == validator.ErrEmailAddressSyntax,
72+
MalformedSyntax: errors.Is(sugErr, validator.ErrEmailAddressSyntax),
73+
MisconfiguredMX: !result.HasValidMX,
7374
}, err
7475
},
7576
Description: "Get suggestions",

cmd/web/handlers.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ const (
2323
failedResponseError = "Generating response failed."
2424
)
2525

26-
func NewAutoCompleteHandler(logger logrus.FieldLogger, svc *services.AutocompleteSvc, maxSuggestions uint64, maxBodySize uint64) http.HandlerFunc {
26+
type marshalFn func(v interface{}) ([]byte, error)
27+
28+
func NewAutoCompleteHandler(logger logrus.FieldLogger, svc *services.AutocompleteSvc, maxSuggestions uint64, maxBodySize uint64, jsonMarshaller marshalFn) http.HandlerFunc {
29+
if jsonMarshaller == nil {
30+
jsonMarshaller = json.Marshal
31+
}
2732

2833
logger = logger.WithField("handler", "auto complete")
2934
return func(w http.ResponseWriter, r *http.Request) {
@@ -81,7 +86,7 @@ func NewAutoCompleteHandler(logger logrus.FieldLogger, svc *services.Autocomplet
8186
return
8287
}
8388

84-
response, err := json.Marshal(erihttp.AutoCompleteResponse{
89+
response, err := jsonMarshaller(erihttp.AutoCompleteResponse{
8590
Suggestions: result.Suggestions,
8691
})
8792

@@ -107,8 +112,12 @@ func NewAutoCompleteHandler(logger logrus.FieldLogger, svc *services.Autocomplet
107112
}
108113
}
109114

110-
// NewSuggestHandler constructs a HTTP handler that deals with suggestion requests
111-
func NewSuggestHandler(logger logrus.FieldLogger, svc *services.SuggestSvc, maxBodySize uint64) http.HandlerFunc {
115+
// NewSuggestHandler constructs an HTTP handler that deals with suggestion requests
116+
func NewSuggestHandler(logger logrus.FieldLogger, svc *services.SuggestSvc, maxBodySize uint64, jsonMarshaller marshalFn) http.HandlerFunc {
117+
if jsonMarshaller == nil {
118+
jsonMarshaller = json.Marshal
119+
}
120+
112121
log := logger.WithField("handler", "suggest")
113122
return func(w http.ResponseWriter, r *http.Request) {
114123
var err error
@@ -136,18 +145,15 @@ func NewSuggestHandler(logger logrus.FieldLogger, svc *services.SuggestSvc, maxB
136145
}
137146

138147
var alts = []string{req.Email}
139-
var sugErr error
140-
{
141-
var result services.SuggestResult
142-
result, sugErr = svc.Suggest(r.Context(), req.Email)
143-
if len(result.Alternatives) > 0 {
144-
alts = append(alts[0:0], result.Alternatives...)
145-
}
148+
result, sugErr := svc.Suggest(r.Context(), req.Email)
149+
if len(result.Alternatives) > 0 {
150+
alts = append(alts[0:0], result.Alternatives...)
146151
}
147152

148153
sr := erihttp.SuggestResponse{
149154
Alternatives: alts,
150155
MalformedSyntax: errors.Is(sugErr, validator.ErrEmailAddressSyntax),
156+
MisconfiguredMX: !result.HasValidMX,
151157
}
152158

153159
if sugErr != nil {
@@ -159,7 +165,7 @@ func NewSuggestHandler(logger logrus.FieldLogger, svc *services.SuggestSvc, maxB
159165
sr.Error = sugErr.Error()
160166
}
161167

162-
response, err := json.Marshal(sr)
168+
response, err := jsonMarshaller(sr)
163169

164170
if err != nil {
165171
log.WithFields(logrus.Fields{

0 commit comments

Comments
 (0)