mailauth
Email authentication library for Node.js (work in progress)
Setup
Free, AGPL-licensed version
First install the module from npm:
$ npm install mailauth
next import any method you want to use from mailauth package into your script:
const { authenticate } = require('mailauth');
MIT version
MIT-licensed version is available for Postal Systems subscribers.
First install the module from Postal Systems private registry:
$ npm install @postalsys/mailauth
next import any method you want to use from mailauth package into your script:
const { authenticate } = require('@postalsys/mailauth');
If you have already built your application using the free version of "mailauth" and do not want to modify require statements in your code, you can install the MIT-licensed version as an alias for "mailauth".
$ npm install mailauth@npm:@postalsys/mailauth
This way you can keep using the old module name
const { authenticate } = require('mailauth');
Usage
Authentication
Validate DKIM signatures, SPF, DMARC and ARC for an email.
const { authenticate } = require('mailauth');
const { dkim, spf, arc, dmarc, headers } = await authenticate(
message,
{
ip: '217.146.67.33',
helo: 'uvn-67-33.tll01.zonevs.eu',
mta: 'mx.ethereal.email',
sender: 'andris@ekiri.ee',
resolver: async (name, rr) => await dns.promises.resolve(name, rr)
}
);
process.stdout.write(headers);
process.stdout.write(message);
Example output:
Received-SPF: pass (mx.ethereal.email: domain of andris@ekiri.ee designates 217.146.67.33 as permitted sender) client-ip=217.146.67.33;
Authentication-Results: mx.ethereal.email;
dkim=pass header.i=@ekiri.ee header.s=default header.a=rsa-sha256 header.b=TXuCNlsq;
spf=pass (mx.ethereal.email: domain of andris@ekiri.ee designates 217.146.67.33 as permitted sender) smtp.mailfrom=andris@ekiri.ee
smtp.helo=uvn-67-33.tll01.zonevs.eu;
arc=pass (i=2 spf=neutral dkim=pass dkdomain=ekiri.ee);
dmarc=none header.from=ekiri.ee
From: ...
You can see full output (structured data for DKIM, SPF, DMARC and ARC) from this example.
DKIM
Signing
const { dkimSign } = require('mailauth/lib/dkim/sign');
const signResult = await dkimSign(
message,
{
canonicalization: 'relaxed/relaxed',
algorithm: 'rsa-sha256',
signTime: new Date(),
signatureData: [
{
signingDomain: 'tahvel.info',
selector: 'test.rsa',
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'),
algorithm: 'rsa-sha256',
canonicalization: 'relaxed/relaxed'
}
]
}
);
if (signResult.errors.length) {
console.log(signResult.errors);
}
process.stdout.write(signResult.signatures);
process.stdout.write(message);
Example output:
DKIM-Signature: a=rsa-sha256; v=1; c=relaxed/relaxed; d=tahvel.info;
s=test.rsa; b=...
From: ...
Verifying
const { dkimVerify } = require('mailauth/lib/dkim/verify');
const result = await dkimVerify(message);
for (let { info } of result.results) {
console.log(info);
}
Example output:
dkim=neutral (invalid public key) header.i=@tahvel.info header.s=test.invalid header.b="b85yao+1"
dkim=pass header.i=@tahvel.info header.s=test.rsa header.b="BrEgDN4A"
dkim=policy policy.dkim-rules=weak-key header.i=@tahvel.info header.s=test.small header.b="d0jjgPun"
SPF
Verifying
const { spf } = require('mailauth/lib/spf');
let result = await spf({
sender: 'andris@wildduck.email',
ip: '217.146.76.20',
helo: 'foo',
mta: 'mx.myhost.com'
});
console.log(result.header);
Example output:
Received-SPF: pass (mx.myhost.com: domain of andris@wildduck.email
designates 217.146.76.20 as permitted sender) client-ip=217.146.76.20;
envelope-from="andris@wildduck.email";
ARC
Validation
ARC seals are automatically validated during the authentication step.
const { authenticate } = require('mailauth');
const { arc } = await authenticate(
message,
{
ip: '217.146.67.33',
helo: 'uvn-67-33.tll01.zonevs.eu',
mta: 'mx.ethereal.email',
sender: 'andris@ekiri.ee'
}
);
console.log(arc);
Output being something like this:
{
"status": {
"result": "pass",
"comment": "i=2 spf=neutral dkim=pass dkdomain=zonevs.eu dkim=pass dkdomain=srs3.zonevs.eu dmarc=fail fromdomain=zone.ee"
},
"i": 2,
...
}
Sealing
During authentication
You can seal messages with ARC automatically in the authentication step by providing the sealing key. In this case you can not modify the message anymore as this would break the seal.
const { authenticate } = require('mailauth');
const { headers } = await authenticate(
message,
{
ip: '217.146.67.33',
helo: 'uvn-67-33.tll01.zonevs.eu',
mta: 'mx.ethereal.email',
sender: 'andris@ekiri.ee',
seal: {
signingDomain: 'tahvel.info',
selector: 'test.rsa',
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem')
}
}
);
process.stdout.write(headers);
process.stdout.write(message);
After modifications
If you want to modify the message before sealing then you have to authenticate the message first and then use authentication results as input for the sealing step.
const { authenticate, sealMessage } = require('@postalsys/mailauth');
const { arc, headers } = await authenticate(
message,
{
ip: '217.146.67.33',
helo: 'uvn-67-33.tll01.zonevs.eu',
mta: 'mx.ethereal.email',
sender: 'andris@ekiri.ee'
}
);
const sealHeaders = await sealMessage(message, {
signingDomain: 'tahvel.info',
selector: 'test.rsa',
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'),
authResults: arc.authResults,
cv: arc.status.result
});
process.stdout.write(sealHeaders);
process.stdout.write(headers);
process.stdout.write(message);
License
© 2020 Andris Reinman
Licensed under GNU Affero General Public License v3.0 or later.
MIT-licensed version of mailauth is available for Postal Systems subscribers.