Comparing version 1.0.21 to 1.0.22
@@ -55,3 +55,3 @@ 'use strict'; | ||
try { | ||
let res = await getPublicKey('AS', queryDomain, resolver); | ||
let res = await getPublicKey('AS', queryDomain, opts.minBitLength, resolver); | ||
publicKey = res?.publicKey; | ||
@@ -307,2 +307,9 @@ } catch (err) { | ||
Object.defineProperty(result, 'chain', { | ||
enumerable: false, | ||
configurable: false, | ||
writable: false, | ||
value: data.chain | ||
}); | ||
try { | ||
@@ -327,2 +334,3 @@ if (data.error) { | ||
result.authenticationResults = data?.lastEntry?.['arc-authentication-results']?.parsed; | ||
if (result.authenticationResults) { | ||
@@ -332,9 +340,13 @@ delete result.authenticationResults.i; | ||
result.authenticationResults.mta = result.authenticationResults.value; | ||
delete result.authenticationResults.value; | ||
if (result.authenticationResults.value) { | ||
let mta = result.authenticationResults.value; | ||
delete result.authenticationResults.value; | ||
result.authenticationResults = Object.assign({ mta }, result.authenticationResults); | ||
} | ||
['arc', 'spf', 'dmarc'].forEach(key => { | ||
if (result.authenticationResults[key]) { | ||
result.authenticationResults[key].result = result.authenticationResults[key].value; | ||
let res = result.authenticationResults[key].value; | ||
delete result.authenticationResults[key].value; | ||
result.authenticationResults[key] = Object.assign({ result: res }, result.authenticationResults[key]); | ||
} | ||
@@ -344,8 +356,11 @@ }); | ||
if (result.authenticationResults.dkim && result.authenticationResults.dkim.length) { | ||
result.authenticationResults.dkim.forEach(entry => { | ||
entry.result = entry.value; | ||
result.authenticationResults.dkim = result.authenticationResults.dkim.map(entry => { | ||
let result = entry.value; | ||
delete entry.value; | ||
return Object.assign({ result }, entry); | ||
}); | ||
} | ||
} | ||
status.result = 'pass'; | ||
@@ -352,0 +367,0 @@ } else { |
@@ -17,2 +17,3 @@ 'use strict'; | ||
this.resolver = this.options.resolver; | ||
this.minBitLength = this.options.minBitLength; | ||
@@ -192,4 +193,3 @@ this.results = []; | ||
b: signatureHeader.parsed?.b?.value ? `${signatureHeader.parsed?.b?.value.substr(0, 8)}` : false | ||
}, | ||
policy: {} | ||
} | ||
}; | ||
@@ -210,2 +210,3 @@ | ||
`${signatureHeader.selector}._domainkey.${signatureHeader.signingDomain}`, | ||
this.minBitLength, | ||
this.resolver | ||
@@ -262,2 +263,5 @@ ); | ||
status.result = 'policy'; | ||
if (!status.policy) { | ||
status.policy = {}; | ||
} | ||
status.policy['dkim-rules'] = `weak-key`; | ||
@@ -292,2 +296,6 @@ break; | ||
if (typeof result.status.comment === 'boolean') { | ||
delete result.status.comment; | ||
} | ||
switch (signatureHeader.type) { | ||
@@ -294,0 +302,0 @@ case 'ARC': |
@@ -102,2 +102,7 @@ 'use strict'; | ||
response.info = formatAuthHeaderRow('dmarc', response.status); | ||
if (typeof response.status.comment === 'boolean') { | ||
delete response.status.comment; | ||
} | ||
return response; | ||
@@ -104,0 +109,0 @@ }; |
@@ -11,2 +11,3 @@ 'use strict'; | ||
const os = require('os'); | ||
const { isIP } = require('net'); | ||
@@ -23,2 +24,3 @@ /** | ||
* @param {String} [opts.mta] MTA/MX hostname (defaults to os.hostname) | ||
* @param {Number} [opts.minBitLength=1024] Minimal allowed length of public keys. If DKIM/ARC key is smaller, then verification fails | ||
* @param {Object} [opts.seal] ARC sealing options | ||
@@ -41,3 +43,4 @@ * @param {String} [opts.seal.signingDomain] ARC key domain name | ||
sender: opts.sender, // defaults to Return-Path header | ||
seal: opts.seal | ||
seal: opts.seal, | ||
minBitLength: opts.minBitLength | ||
}); | ||
@@ -64,3 +67,4 @@ | ||
if (helo && !opts.helo) { | ||
if (helo && !opts.helo && !opts.ip) { | ||
// if IP was provided then do not use helo even if it is missing | ||
opts.helo = helo; | ||
@@ -80,2 +84,11 @@ } | ||
if (!opts.helo && opts.ip) { | ||
opts.helo = opts.ip; | ||
} | ||
if (opts.helo && isIP(opts.helo)) { | ||
// use the bracket syntax | ||
opts.helo = `[${opts.helo}]`; | ||
} | ||
const spfResult = await spf(opts); | ||
@@ -86,3 +99,4 @@ | ||
arcResult = await arc(dkimResult.arc, { | ||
resolver: opts.resolver | ||
resolver: opts.resolver, | ||
minBitLength: opts.minBitLength | ||
}); | ||
@@ -164,3 +178,2 @@ } | ||
return { | ||
receivedChain, | ||
dkim: dkimResult, | ||
@@ -171,2 +184,3 @@ spf: spfResult, | ||
bimi: bimiResult || false, | ||
receivedChain, | ||
headers: headers.join('\r\n') + '\r\n' | ||
@@ -173,0 +187,0 @@ }; |
@@ -134,3 +134,4 @@ 'use strict'; | ||
value: '', | ||
comment: '' | ||
comment: '', | ||
hasValue: false | ||
}; | ||
@@ -155,2 +156,3 @@ parts.push(part); | ||
state = 'value'; | ||
curPart.hasValue = true; | ||
break; | ||
@@ -235,3 +237,5 @@ } | ||
for (let key of Object.keys(parts[i])) { | ||
parts[i][key] = parts[i][key].replace(/\s+/g, ' ').trim(); | ||
if (typeof parts[i][key] === 'string') { | ||
parts[i][key] = parts[i][key].replace(/\s+/g, ' ').trim(); | ||
} | ||
} | ||
@@ -258,7 +262,9 @@ | ||
let defaultPos = headerKey === 'arc-authentication-results' ? 1 : 0; | ||
if (parts[defaultPos].key && !parts[defaultPos].value && !/^arc-/i.test(headerKey)) { | ||
result.value = parts[defaultPos].key; | ||
parts.splice(defaultPos, 1); | ||
for (let i = 0; i < parts.length; i++) { | ||
// find the first entry with key only and use it as the default value | ||
if (parts[i].key && !parts[i].hasValue) { | ||
result.value = parts[i].key; | ||
parts.splice(i, 1); | ||
break; | ||
} | ||
} | ||
@@ -265,0 +271,0 @@ |
@@ -150,14 +150,21 @@ 'use strict'; | ||
for (let val of values) { | ||
if (val.comment) { | ||
val.comment = val.comment.replace(/\s+/g, ' ').trim(); | ||
} | ||
if (val.key) { | ||
let key = val.key.toLowerCase(); | ||
if (key !== 'from' && !result.tls && /tls|cipher=/i.test(val.comment)) { | ||
result.tls = { value: '', comment: val.comment }; | ||
if (key !== 'from' && !result.tls && /tls|cipher=|Google Transport Security/i.test(val.comment)) { | ||
result.tls = { value: '', ...(val.comment && { comment: val.comment }) }; | ||
val.comment = ''; | ||
} | ||
result[key] = { value: val.value, comment: val.comment }; | ||
} else if (!result.tls && /tls|cipher=/i.test(val.comment)) { | ||
result.tls = { value: val.value, comment: val.comment }; | ||
result[key] = { value: val.value, ...(val.comment && { comment: val.comment }) }; | ||
} else if (!result.tls && /tls|cipher=|Google Transport Security/i.test(val.comment)) { | ||
result.tls = { value: val.value, ...(val.comment && { comment: val.comment }) }; | ||
} | ||
} | ||
if (!result.tls && /SMTPS/.test(result?.with?.value)) { | ||
result.tls = { value: '', comment: result?.with?.value }; | ||
} | ||
if (timestamp) { | ||
@@ -164,0 +171,0 @@ result.timestamp = timestamp; |
@@ -225,2 +225,6 @@ 'use strict'; | ||
if (typeof response.status.comment === 'boolean') { | ||
delete response.status.comment; | ||
} | ||
return response; | ||
@@ -227,0 +231,0 @@ }; |
@@ -234,3 +234,4 @@ /* eslint no-control-regex: 0 */ | ||
const getPublicKey = async (type, name, resolver) => { | ||
const getPublicKey = async (type, name, minBitLength, resolver) => { | ||
minBitLength = minBitLength || 1024; | ||
resolver = resolver || dns.resolve; | ||
@@ -237,0 +238,0 @@ |
{ | ||
"name": "mailauth", | ||
"version": "1.0.21", | ||
"version": "1.0.22", | ||
"description": "Email authentication library for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "lib/mailauth.js", |
@@ -24,2 +24,28 @@ ![](https://github.com/andris9/mailauth/raw/master/assets/mailauth.png) | ||
``` | ||
await authenticate(message [,options]) -> | ||
{ dkim, spf, arc, dmarc, bimi, receivedChain, headers } | ||
``` | ||
Where | ||
- **message** is either a String, a Buffer or a Readable stream that represents an email message | ||
- **options** (_object_) is an optional options object | ||
- **sender** (_string_) is the email address from MAIL FROM command (aka Return-Path address) | ||
- **ip** (_string_) is the IP of remote client that sent this message | ||
- **helo** (_string_) is the hostname value from HELO/EHLO command | ||
- **trustReceived** (_boolean_) if true then parses values for `ip` and `helo` from latest `Received` header if you have not set these values yourself | ||
- **mta** (_string_) is the hostname of the server performing the authentication (defaults to `os.hostname()`) | ||
- **minBitLength** (_number_) is the minimum allowed bits of RSA public keys (defaults to 1024). If a DKIM or ARC key has less bits, then validation is conisdered as failed | ||
- **disableArc** (_boolean_) if true then skip ARC checks | ||
- **disableDmarc** (_boolean_) if true then skip DMARC checks. This also disables checks that are dependent on DMARC (eg. BIMI) | ||
- **disableBimi** (_boolean_) if true then skip BIMI checks | ||
- **seal** (_object_) if set and message does not have a broken ARC chain, then seals the message using these values | ||
- **signingDomain** (_string_) ARC key domain name | ||
- **selector** (_string_) ARC key selector | ||
- **privateKey** (_string_ or _buffer_) Private key for signing. Can be a RSA or an Ed25519 key | ||
- **resolver** (_async function_) is an optional async function for DNS requests. Defaults to dns.promises.resolve](https://nodejs.org/api/dns.html#dns_dnspromises_resolve_hostname_rrtype) | ||
**Example** | ||
```js | ||
@@ -26,0 +52,0 @@ const { authenticate } = require('mailauth'); |
240348
3855
556
12