@haibun/email-testing 1.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/build/email-testing-stepper.js +125 -0
- package/build/email-testing-stepper.test.js +29 -0
- package/build/email-testingstepper.js +97 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const { authenticate } = require('mailauth');
|
|
4
|
+
const { dkimVerify } = require('mailauth/lib/dkim/verify');
|
|
5
|
+
const { spf } = require('mailauth/lib/spf');
|
|
6
|
+
const { getPolicy, validateMx } = require('mailauth/lib/mta-sts');
|
|
7
|
+
const dmarc = require('dmarc-solution');
|
|
8
|
+
const defs_1 = require("@haibun/core/build/lib/defs");
|
|
9
|
+
const util_1 = require("@haibun/core/build/lib/util");
|
|
10
|
+
const EMAIL_SERVER = 'EMAIL_SERVER';
|
|
11
|
+
const EMAIL_MESSAGE = 'EMAIL_MESSAGE';
|
|
12
|
+
const EmailTestingStepper = class EmailTestingStepper extends defs_1.AStepper {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.knownPolicy = undefined;
|
|
16
|
+
// requireDomains = [EMAIL_SERVER, EMAIL_MESSAGE];
|
|
17
|
+
this.options = {
|
|
18
|
+
[EMAIL_SERVER]: {
|
|
19
|
+
desc: 'Target email server',
|
|
20
|
+
parse: (input) => (0, util_1.stringOrError)(input)
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
this.steps = {
|
|
24
|
+
dmarcExists: {
|
|
25
|
+
gwta: `DMARC record exists`,
|
|
26
|
+
action: async () => {
|
|
27
|
+
try {
|
|
28
|
+
const record = await dmarc.fetch(this.emailServer);
|
|
29
|
+
return defs_1.OK;
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
return (0, util_1.actionNotOK)(e.getMessage());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
dmarcHas: {
|
|
37
|
+
gwta: `DMARC field {field} is defined`,
|
|
38
|
+
action: async ({ field }) => {
|
|
39
|
+
try {
|
|
40
|
+
const record = await dmarc.fetch(this.emailServer);
|
|
41
|
+
if (!record.tags[field]) {
|
|
42
|
+
return (0, util_1.actionNotOK)(`field ${field} doesn't exist`);
|
|
43
|
+
}
|
|
44
|
+
return defs_1.OK;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return (0, util_1.actionNotOK)(e.getMessage());
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
checkMTASTS: {
|
|
52
|
+
gwta: `domain's mail server agent strict transfer security is valid`,
|
|
53
|
+
action: async () => {
|
|
54
|
+
const { policy } = await this.getPolicy();
|
|
55
|
+
for (const mx of policy.mx) {
|
|
56
|
+
const policyMatch = validateMx(mx, policy);
|
|
57
|
+
if (!policy.id) {
|
|
58
|
+
return (0, util_1.actionNotOK)(policyMatch);
|
|
59
|
+
}
|
|
60
|
+
if (policy.mx && !policyMatch) {
|
|
61
|
+
return (0, util_1.actionNotOK)(`unlisted MX ${mx}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (policy.mode !== 'enforce') {
|
|
65
|
+
return (0, util_1.actionNotOK)(`not using enforce mode`);
|
|
66
|
+
}
|
|
67
|
+
return defs_1.OK;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
validateMXHostname: {
|
|
71
|
+
gwta: `domain has a valid mail exchange record`,
|
|
72
|
+
action: async () => {
|
|
73
|
+
const { policy } = await this.getPolicy();
|
|
74
|
+
const res = await validateMx(this.emailServer, policy);
|
|
75
|
+
return res === true ? defs_1.OK : (0, util_1.actionNotOK)(res);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
authenticate: {
|
|
79
|
+
gwta: `authenticate`,
|
|
80
|
+
action: async () => {
|
|
81
|
+
const message = 'hello';
|
|
82
|
+
const { dkim, spf, arc, dmarc, bimi, receivedChain, headers } = await authenticate(message, // either a String, a Buffer or a Readable Stream
|
|
83
|
+
{
|
|
84
|
+
// SMTP transmission options if available
|
|
85
|
+
ip: '217.146.67.33',
|
|
86
|
+
helo: 'uvn-67-33.tll01.zonevs.eu',
|
|
87
|
+
sender: 'andris@ekiri.ee',
|
|
88
|
+
// Uncomment if you do not want to provide ip/helo/sender manually but parse from the message
|
|
89
|
+
//trustReceived: true,
|
|
90
|
+
// Server processing this message, defaults to os.hostname(). Inserted into Authentication headers
|
|
91
|
+
mta: 'mx.ethereal.email',
|
|
92
|
+
// Optional DNS resolver function (defaults to `dns.promises.resolve`)
|
|
93
|
+
// resolver: async (name: any, rr: any) => await dns.promises.resolve(name, rr)
|
|
94
|
+
});
|
|
95
|
+
// output authenticated message
|
|
96
|
+
process.stdout.write(headers); // includes terminating line break
|
|
97
|
+
process.stdout.write(message);
|
|
98
|
+
return defs_1.OK;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
spf: {
|
|
102
|
+
gwta: `spf`,
|
|
103
|
+
action: async () => {
|
|
104
|
+
let result = await spf({
|
|
105
|
+
sender: 'andris@wildduck.email',
|
|
106
|
+
ip: '217.146.76.20',
|
|
107
|
+
helo: 'foo',
|
|
108
|
+
mta: 'mx.myhost.com'
|
|
109
|
+
});
|
|
110
|
+
return defs_1.OK;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async setWorld(world, steppers) {
|
|
116
|
+
super.setWorld(world, steppers);
|
|
117
|
+
this.world = world;
|
|
118
|
+
this.emailServer = (0, util_1.getStepperOption)(this, EMAIL_SERVER, this.world.extraOptions);
|
|
119
|
+
}
|
|
120
|
+
async getPolicy() {
|
|
121
|
+
const res = await getPolicy(this.emailServer, this.knownPolicy);
|
|
122
|
+
return res;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
exports.default = EmailTestingStepper;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const lib_1 = require("@haibun/core/build/lib/test/lib");
|
|
7
|
+
const util_1 = require("@haibun/core/build/lib/util");
|
|
8
|
+
const run_1 = require("@haibun/core/build/lib/run");
|
|
9
|
+
const email_testing_stepper_1 = __importDefault(require("./email-testing-stepper"));
|
|
10
|
+
const domain = 'google.com';
|
|
11
|
+
describe('MTA', () => {
|
|
12
|
+
it('MTA STS', async () => {
|
|
13
|
+
const protoOptions = { extraOptions: { [(0, util_1.getStepperOptionName)(email_testing_stepper_1.default, 'EMAIL_SERVER')]: domain, }, options: run_1.DEF_PROTO_DEFAULT_OPTIONS };
|
|
14
|
+
const { ok } = await (0, lib_1.testWithDefaults)(`domain's mail server agent strict transfer security is valid`, [email_testing_stepper_1.default], protoOptions);
|
|
15
|
+
expect(ok).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe('DMARC', () => {
|
|
19
|
+
it('Has a valid dmarc record', async () => {
|
|
20
|
+
const protoOptions = { extraOptions: { [(0, util_1.getStepperOptionName)(email_testing_stepper_1.default, 'EMAIL_SERVER')]: domain, }, options: run_1.DEF_PROTO_DEFAULT_OPTIONS };
|
|
21
|
+
const { ok } = await (0, lib_1.testWithDefaults)(`DMARC record exists`, [email_testing_stepper_1.default], protoOptions);
|
|
22
|
+
expect(ok).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it('DMARC rua is defined', async () => {
|
|
25
|
+
const protoOptions = { extraOptions: { [(0, util_1.getStepperOptionName)(email_testing_stepper_1.default, 'EMAIL_SERVER')]: domain, }, options: run_1.DEF_PROTO_DEFAULT_OPTIONS };
|
|
26
|
+
const { ok } = await (0, lib_1.testWithDefaults)(`DMARC field rua is defined`, [email_testing_stepper_1.default], protoOptions);
|
|
27
|
+
expect(ok).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const { authenticate } = require('mailauth');
|
|
4
|
+
const { dkimVerify } = require('mailauth/lib/dkim/verify');
|
|
5
|
+
const { spf } = require('mailauth/lib/spf');
|
|
6
|
+
const { getPolicy, validateMx } = require('mailauth/lib/mta-sts');
|
|
7
|
+
const defs_1 = require("@haibun/core/build/lib/defs");
|
|
8
|
+
const util_1 = require("@haibun/core/build/lib/util");
|
|
9
|
+
const EMAIL_SERVER = 'EMAIL_SERVER';
|
|
10
|
+
const EMAIL_MESSAGE = 'EMAIL_MESSAGE';
|
|
11
|
+
const EmailTestingStepper = class EmailTestingStepper extends defs_1.AStepper {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
// requireDomains = [EMAIL_SERVER, EMAIL_MESSAGE];
|
|
15
|
+
this.options = {
|
|
16
|
+
[EMAIL_SERVER]: {
|
|
17
|
+
desc: 'Target email server',
|
|
18
|
+
parse: (input) => (0, util_1.stringOrError)(input)
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
this.steps = {
|
|
22
|
+
checkMTASTS: {
|
|
23
|
+
gwta: `domain's mail server agent strict transfer security is valid`,
|
|
24
|
+
action: async () => {
|
|
25
|
+
const { policy } = await this.getPolicy();
|
|
26
|
+
for (const mx of policy.mx) {
|
|
27
|
+
const policyMatch = validateMx(mx, policy);
|
|
28
|
+
if (!policy.id) {
|
|
29
|
+
return (0, util_1.actionNotOK)(policyMatch);
|
|
30
|
+
}
|
|
31
|
+
if (policy.mx && !policyMatch) {
|
|
32
|
+
return (0, util_1.actionNotOK)(`unlisted MX ${mx}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (policy.mode !== 'enforce') {
|
|
36
|
+
return (0, util_1.actionNotOK)(`not using enforce mode`);
|
|
37
|
+
}
|
|
38
|
+
return defs_1.OK;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
validateMXHostname: {
|
|
42
|
+
gwta: `domain has a valid mail exchange record`,
|
|
43
|
+
action: async () => {
|
|
44
|
+
const { policy } = await this.getPolicy();
|
|
45
|
+
const res = await validateMx(this.emailServer, policy);
|
|
46
|
+
return res === true ? defs_1.OK : (0, util_1.actionNotOK)(res);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
authenticate: {
|
|
50
|
+
gwta: `authenticate`,
|
|
51
|
+
action: async () => {
|
|
52
|
+
const message = 'hello';
|
|
53
|
+
const { dkim, spf, arc, dmarc, bimi, receivedChain, headers } = await authenticate(message, // either a String, a Buffer or a Readable Stream
|
|
54
|
+
{
|
|
55
|
+
// SMTP transmission options if available
|
|
56
|
+
ip: '217.146.67.33',
|
|
57
|
+
helo: 'uvn-67-33.tll01.zonevs.eu',
|
|
58
|
+
sender: 'andris@ekiri.ee',
|
|
59
|
+
// Uncomment if you do not want to provide ip/helo/sender manually but parse from the message
|
|
60
|
+
//trustReceived: true,
|
|
61
|
+
// Server processing this message, defaults to os.hostname(). Inserted into Authentication headers
|
|
62
|
+
mta: 'mx.ethereal.email',
|
|
63
|
+
// Optional DNS resolver function (defaults to `dns.promises.resolve`)
|
|
64
|
+
// resolver: async (name: any, rr: any) => await dns.promises.resolve(name, rr)
|
|
65
|
+
});
|
|
66
|
+
// output authenticated message
|
|
67
|
+
process.stdout.write(headers); // includes terminating line break
|
|
68
|
+
process.stdout.write(message);
|
|
69
|
+
return defs_1.OK;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
spf: {
|
|
73
|
+
gwta: `spf`,
|
|
74
|
+
action: async () => {
|
|
75
|
+
let result = await spf({
|
|
76
|
+
sender: 'andris@wildduck.email',
|
|
77
|
+
ip: '217.146.76.20',
|
|
78
|
+
helo: 'foo',
|
|
79
|
+
mta: 'mx.myhost.com'
|
|
80
|
+
});
|
|
81
|
+
return defs_1.OK;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async setWorld(world, steppers) {
|
|
87
|
+
super.setWorld(world, steppers);
|
|
88
|
+
this.world = world;
|
|
89
|
+
this.emailServer = (0, util_1.getStepperOption)(this, EMAIL_SERVER, this.world.extraOptions);
|
|
90
|
+
}
|
|
91
|
+
async getPolicy() {
|
|
92
|
+
const knownPolicy = undefined;
|
|
93
|
+
const res = await getPolicy(this.emailServer, knownPolicy);
|
|
94
|
+
return res;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
exports.default = EmailTestingStepper;
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haibun/email-testing",
|
|
3
|
+
"version": "1.9.5",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "build/web-http.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"build/"
|
|
8
|
+
],
|
|
9
|
+
"jest": {
|
|
10
|
+
"roots": [
|
|
11
|
+
"<rootDir>/src"
|
|
12
|
+
],
|
|
13
|
+
"testMatch": [
|
|
14
|
+
"**/__tests__ /**/*.+(ts|tsx|js)",
|
|
15
|
+
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
|
16
|
+
],
|
|
17
|
+
"transform": {
|
|
18
|
+
"^.+\\.(ts|tsx)$": "ts-jest"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prepublishOnly": "tsc -b .",
|
|
23
|
+
"tsc-watch": "tsc -b . --watch",
|
|
24
|
+
"test": "jest",
|
|
25
|
+
"test-watch": "jest --watch"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "ISC",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@haibun/core": "^1.2.4",
|
|
32
|
+
"dmarc-solution": "^1.2.5",
|
|
33
|
+
"mailauth": "^4.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/jest": "^28.1.8",
|
|
37
|
+
"@types/node": "^15.0.1",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
|
39
|
+
"@typescript-eslint/parser": "^4.13.0",
|
|
40
|
+
"eslint": "^7.2.0",
|
|
41
|
+
"eslint-config-airbnb-typescript": "^12.0.0",
|
|
42
|
+
"eslint-config-prettier": "^8.3.0",
|
|
43
|
+
"eslint-plugin-import": "^2.22.1",
|
|
44
|
+
"eslint-plugin-prefer-arrow": "^1.2.2",
|
|
45
|
+
"eslint-plugin-prettier": "^3.3.1",
|
|
46
|
+
"jest": "^28.1.3",
|
|
47
|
+
"prettier": "^2.3.1",
|
|
48
|
+
"ts-jest": "^28.0.8",
|
|
49
|
+
"ts-node": "^10.0.0",
|
|
50
|
+
"tslint": "^6.1.3",
|
|
51
|
+
"typescript": "^4.1.3"
|
|
52
|
+
}
|
|
53
|
+
}
|