mailauth 4.13.3 → 5.0.1
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/.ncurc.js +1 -2
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/bin/mailauth.js +261 -401
- package/cli.md +14 -0
- package/docs/dmarc.md +28 -33
- package/index.d.ts +26 -7
- package/lib/arc/index.js +7 -4
- package/lib/commands/seal.js +68 -21
- package/lib/dmarc/get-dmarc-record.js +29 -19
- package/lib/dmarc/verify.js +17 -7
- package/lib/mailauth.js +9 -0
- package/lib/parse-dkim-headers.js +28 -8
- package/lib/parse-received.js +6 -0
- package/lib/safe-keys.js +10 -0
- package/lib/tools.js +27 -10
- package/package.json +22 -22
- package/CLAUDE.md +0 -66
package/lib/tools.js
CHANGED
|
@@ -191,7 +191,7 @@ const formatSignatureHeaderLine = (type, values, folded) => {
|
|
|
191
191
|
const header =
|
|
192
192
|
`${headerKey}: ` +
|
|
193
193
|
Object.keys(values)
|
|
194
|
-
.filter(key => values[key] !== false && typeof values[key] !== 'undefined' && values
|
|
194
|
+
.filter(key => values[key] !== false && typeof values[key] !== 'undefined' && values[key] !== null && keyOrdering.includes(key))
|
|
195
195
|
.sort((a, b) => keyOrdering.indexOf(a) - keyOrdering.indexOf(b))
|
|
196
196
|
.map(key => {
|
|
197
197
|
let val = values[key] || '';
|
|
@@ -471,10 +471,17 @@ const formatDomain = domain => {
|
|
|
471
471
|
} catch (err) {
|
|
472
472
|
// ignore punycode errors
|
|
473
473
|
}
|
|
474
|
-
|
|
474
|
+
// the root label is not part of the name, so "example.com." and "example.com" are the same domain
|
|
475
|
+
return domain.replace(/\.+$/, '');
|
|
475
476
|
};
|
|
476
477
|
|
|
477
478
|
const getAlignment = (fromDomain, domainList, strict) => {
|
|
479
|
+
// This argument used to be an options object. Every object is truthy, so a leftover
|
|
480
|
+
// { strict: false } call would otherwise mean hard strict, the opposite of what it asks for.
|
|
481
|
+
if (strict && typeof strict === 'object') {
|
|
482
|
+
strict = strict.strict;
|
|
483
|
+
}
|
|
484
|
+
|
|
478
485
|
domainList = []
|
|
479
486
|
.concat(domainList || [])
|
|
480
487
|
.map(entry => {
|
|
@@ -486,20 +493,28 @@ const getAlignment = (fromDomain, domainList, strict) => {
|
|
|
486
493
|
.sort((a, b) => (a.underSized || 0) - (b.underSized || 0));
|
|
487
494
|
|
|
488
495
|
if (strict) {
|
|
489
|
-
|
|
496
|
+
// strict alignment: the domains must be identical (RFC 9989 §3.2.10.2)
|
|
497
|
+
let from = formatDomain(fromDomain);
|
|
498
|
+
if (!from) {
|
|
499
|
+
// a bare root label normalizes to an empty string, which must not align with anything
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
490
502
|
for (let entry of domainList) {
|
|
491
|
-
|
|
492
|
-
if (formatDomain(domain) === fromDomain) {
|
|
503
|
+
if (formatDomain(entry.domain) === from) {
|
|
493
504
|
return entry;
|
|
494
505
|
}
|
|
495
506
|
}
|
|
507
|
+
return false;
|
|
496
508
|
}
|
|
497
509
|
|
|
498
|
-
//
|
|
499
|
-
|
|
510
|
+
// relaxed alignment: the domains must share an Organizational Domain (RFC 9989 §3.2.10.1)
|
|
511
|
+
let fromOrg = formatDomain(tldts.getDomain(fromDomain, TLDTS_OPTS) || fromDomain);
|
|
512
|
+
if (!fromOrg) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
500
515
|
for (let entry of domainList) {
|
|
501
|
-
let
|
|
502
|
-
if (
|
|
516
|
+
let entryOrg = formatDomain(tldts.getDomain(entry.domain, TLDTS_OPTS) || entry.domain);
|
|
517
|
+
if (entryOrg === fromOrg) {
|
|
503
518
|
return entry;
|
|
504
519
|
}
|
|
505
520
|
}
|
|
@@ -589,7 +604,9 @@ function parseTagValueRecord(record, options = {}) {
|
|
|
589
604
|
|
|
590
605
|
// Split on semicolons
|
|
591
606
|
const parts = sanitized.split(';');
|
|
592
|
-
|
|
607
|
+
// no prototype, so that a tag named "__proto__" is stored as a normal key instead of
|
|
608
|
+
// replacing the prototype, and so that "toString" and friends are not seen as already set
|
|
609
|
+
const tags = Object.create(null);
|
|
593
610
|
const validPairs = [];
|
|
594
611
|
const errors = [];
|
|
595
612
|
const warnings = [];
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "eslint \"lib/**/*.js\" \"test/**/*.js\" && mocha --recursive \"./test
|
|
8
|
+
"test": "eslint \"lib/**/*.js\" \"test/**/*.js\" && mocha --recursive \"./test/**/*-test.js\" --reporter spec",
|
|
9
9
|
"format": "prettier --write .",
|
|
10
10
|
"build-source": "rm -rf node_modules package-lock.json && npm install && npm run licenses && rm -rf node_modules package-lock.json && npm install --production && rm -rf package-lock.json",
|
|
11
|
-
"build-dist": "
|
|
11
|
+
"build-dist": "pkg --compress Brotli package.json && rm -rf package-lock.json && npm install && node winconf.js",
|
|
12
12
|
"build-dist-fast": "pkg --debug package.json && npm install && node winconf.js",
|
|
13
13
|
"licenses": "license-report --only=prod --output=table --config license-report-config.json > licenses.txt",
|
|
14
14
|
"update": "rm -rf node_modules package-lock.json && npx ncu -u && npm install"
|
|
@@ -35,30 +35,30 @@
|
|
|
35
35
|
"homepage": "https://github.com/postalsys/mailauth",
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"chai": "4.4.1",
|
|
38
|
-
"eslint": "10.
|
|
38
|
+
"eslint": "10.8.0",
|
|
39
39
|
"eslint-config-nodemailer": "1.2.0",
|
|
40
40
|
"eslint-config-prettier": "10.1.8",
|
|
41
|
-
"js-yaml": "
|
|
42
|
-
"license-report": "6.8.
|
|
41
|
+
"js-yaml": "5.2.3",
|
|
42
|
+
"license-report": "6.8.5",
|
|
43
43
|
"mbox-reader": "1.2.0",
|
|
44
|
-
"mocha": "11.
|
|
45
|
-
"prettier": "3.
|
|
44
|
+
"mocha": "11.8.0",
|
|
45
|
+
"prettier": "3.9.6",
|
|
46
46
|
"resedit": "3.0.2"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@postalsys/vmc": "1.1.
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
49
|
+
"@postalsys/vmc": "1.1.5",
|
|
50
|
+
"commander": "15.0.0",
|
|
51
|
+
"fast-xml-parser": "5.10.1",
|
|
52
|
+
"ipaddr.js": "2.5.0",
|
|
53
|
+
"joi": "18.2.3",
|
|
54
|
+
"libmime": "5.4.1",
|
|
55
|
+
"nodemailer": "9.0.4",
|
|
55
56
|
"punycode.js": "2.3.1",
|
|
56
|
-
"tldts": "7.
|
|
57
|
-
"undici": "
|
|
58
|
-
"yargs": "17.7.2"
|
|
57
|
+
"tldts": "7.4.10",
|
|
58
|
+
"undici": "8.10.0"
|
|
59
59
|
},
|
|
60
60
|
"engines": {
|
|
61
|
-
"node": ">=
|
|
61
|
+
"node": ">=22.19.0"
|
|
62
62
|
},
|
|
63
63
|
"bin": {
|
|
64
64
|
"mailauth": "bin/mailauth.js"
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"LICENSE.txt"
|
|
70
70
|
],
|
|
71
71
|
"targets": [
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
72
|
+
"node24-linux-x64",
|
|
73
|
+
"node24-macos-x64",
|
|
74
|
+
"node24-macos-arm64",
|
|
75
|
+
"node24-win-x64"
|
|
76
76
|
],
|
|
77
77
|
"outputPath": "ee-dist"
|
|
78
78
|
}
|
package/CLAUDE.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
mailauth is a Node.js library and CLI tool for email authentication. It implements SPF, DKIM, DMARC, ARC, BIMI, and MTA-STS protocols. Pure JavaScript, no native dependencies. Requires Node.js >= 20.18.1.
|
|
8
|
-
|
|
9
|
-
## Commands
|
|
10
|
-
|
|
11
|
-
- **Run all tests (lint + mocha):** `npm test`
|
|
12
|
-
- **Run a single test file:** `npx mocha test/dkim/body/relaxed-test.js --reporter spec`
|
|
13
|
-
- **Run tests matching a pattern:** `npx mocha --recursive "./test/**/*.js" --reporter spec --grep "pattern"`
|
|
14
|
-
- **Lint only:** `npx eslint "lib/**/*.js" "test/**/*.js"`
|
|
15
|
-
- **Format code:** `npm run format` (uses Prettier)
|
|
16
|
-
|
|
17
|
-
## Code Style
|
|
18
|
-
|
|
19
|
-
- CommonJS (`require`/`module.exports`), not ES modules
|
|
20
|
-
- `'use strict'` at top of every file
|
|
21
|
-
- Mocha + Chai (expect style) for tests
|
|
22
|
-
- Prettier: 160 print width, 4-space indent, single quotes, no trailing commas, LF line endings
|
|
23
|
-
- ESLint: unused vars prefixed with `_`, `no-console` allowed
|
|
24
|
-
|
|
25
|
-
## Architecture
|
|
26
|
-
|
|
27
|
-
### Entry Point
|
|
28
|
-
|
|
29
|
-
`lib/mailauth.js` — exports `authenticate()` (the main all-in-one function) and individual protocol functions. The `authenticate` pipeline runs in order: DKIM verify → SPF → ARC → DMARC → BIMI, each step feeding results to the next.
|
|
30
|
-
|
|
31
|
-
### Protocol Modules
|
|
32
|
-
|
|
33
|
-
Each protocol lives in its own directory under `lib/`:
|
|
34
|
-
|
|
35
|
-
- **`lib/dkim/`** — DKIM signing (`sign.js`, `dkim-signer.js`, `DkimSignStream`) and verification (`verify.js`, `dkim-verifier.js`). Body canonicalization in `body/` (relaxed/simple), header canonicalization in `header/` (relaxed/simple). `message-parser.js` is the streaming RFC822 parser used by both signing and verification.
|
|
36
|
-
- **`lib/spf/`** — SPF verification (`spf-verify.js`), macro expansion (`macro.js`). Entry point `index.js` wraps the verifier.
|
|
37
|
-
- **`lib/dmarc/`** — DMARC verification (`verify.js`) and DNS record fetching (`get-dmarc-record.js`). Takes DKIM and SPF results as input.
|
|
38
|
-
- **`lib/arc/`** — ARC chain validation and seal creation. `trustlist.js` contains known ARC trust anchors.
|
|
39
|
-
- **`lib/bimi/`** — BIMI record resolution and SVG validation (`validate-svg.js`). Depends on DMARC passing.
|
|
40
|
-
- **`lib/mta-sts.js`** — MTA-STS policy fetching and MX validation (standalone, not part of authenticate pipeline).
|
|
41
|
-
|
|
42
|
-
### Shared Utilities
|
|
43
|
-
|
|
44
|
-
- **`lib/tools.js`** — DNS helpers, key parsing, header formatting, stream utilities, domain alignment checks
|
|
45
|
-
- **`lib/parse-dkim-headers.js`** — Parses structured DKIM/ARC header fields
|
|
46
|
-
- **`lib/parse-received.js`** — Parses Received headers
|
|
47
|
-
|
|
48
|
-
### CLI
|
|
49
|
-
|
|
50
|
-
`bin/mailauth.js` — yargs-based CLI with subcommands: `report`, `sign`, `seal`, `spf`, `vmc`, `bodyhash`. Command implementations in `lib/commands/`.
|
|
51
|
-
|
|
52
|
-
### Tests
|
|
53
|
-
|
|
54
|
-
Tests mirror the `lib/` structure under `test/`. Test fixtures in `test/fixtures/` include sample emails, DNS response caches, keys, and RFC test suite YAML files. Many tests use mock DNS resolvers that return fixture data instead of making real DNS queries.
|
|
55
|
-
|
|
56
|
-
### Type Definitions
|
|
57
|
-
|
|
58
|
-
`index.d.ts` and per-module `.d.ts` files provide TypeScript type definitions for consumers.
|
|
59
|
-
|
|
60
|
-
### Key Design Pattern
|
|
61
|
-
|
|
62
|
-
All protocol functions accept an optional `resolver` parameter (async function matching `dns.promises.resolve` signature) for DNS lookups. This enables testing with mock DNS and allows consumers to implement caching or custom resolution.
|
|
63
|
-
|
|
64
|
-
## Releases
|
|
65
|
-
|
|
66
|
-
Version numbers, changelogs, and releases are managed automatically by release-please via GitHub Actions. Do not manually edit `package.json` version, `CHANGELOG.md`, or release-related files.
|