Skip to content

Commit

Permalink
feat: add LogProcessor to the available options
Browse files Browse the repository at this point in the history
  • Loading branch information
loopingz committed Nov 1, 2024
1 parent 8a8eea0 commit 862803e
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ reports
.env
*.eml
test.log
data-headers.txt.raw
27 changes: 27 additions & 0 deletions configs/fake-smtp-log.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Can replace fakeSMTP.jar
{
"$schema": "https://raw.githubusercontent.com/loopingz/smtp-relay/main/config.schema.json",
"flows": {
"localhost": {
"filters": [
// Allow only localhost
{
"type": "whitelist",
"ips": ["127.0.0.1"]
}
],
"outputs": [
{
// We just log to the console
"type": "log"
}
]
}
},
"port": 10026,
"options": {
"disableReverseLookup": false,
// Do not require auth
"authOptional": true
}
}
38 changes: 38 additions & 0 deletions configs/nodemailer.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Can replace fakeSMTP.jar
{
"$schema": "https://raw.githubusercontent.com/loopingz/smtp-relay/main/config.schema.json",
"flows": {
"localhost": {
"filters": [
// Allow only localhost
{
"type": "whitelist",
"ips": ["127.0.0.1"]
}
],
"outputs": [
{
// We just log to the console
"type": "nodemailer",
"nodemailer": {
"host": "localhost",
"port": 10026,
"secure": false,
"auth": {
"user": "user",
"pass": "pass"
},
"tls": {
"rejectUnauthorized": false
}
}
}
]
}
},
"options": {
"disableReverseLookup": false,
// Do not require auth
"authOptional": true
}
}
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { NodeMailerProcessor } from "./processors/nodemailer";
import { SmtpServer } from "./server";
import { HttpAuthFilter } from "./filters/http-auth";
import { HttpFilter } from "./filters/http-filter";
import { LogProcessor } from "./processors/log";
export * from "./cloudevent";

/**
Expand Down Expand Up @@ -48,6 +49,10 @@ export function defaultModules() {
* Send the email using PubSub or store it in a Bucket
*/
SmtpProcessor.register("gcp", GCPProcessor);
/**
* Log the email
*/
SmtpProcessor.register("log", LogProcessor);
}

// Cannot really test main module
Expand Down
10 changes: 9 additions & 1 deletion src/processors/gcp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as sinon from "sinon";
import { SmtpSession } from "../server";
import { getFakeSession } from "../server.spec";
import { GCPProcessor } from "./gcp";
import { readFileSync } from "node:fs";

@suite
class GCPProcessorTest {
Expand Down Expand Up @@ -71,9 +72,16 @@ class GCPProcessorTest {
}
};
});
session.user = "LZ";
await gcp.onMail(session);
delete session.user;
assert.strictEqual(bucket, "test");
assert.strictEqual(filename, "unit-test-fake-path");
assert.ok(filename.endsWith("data-headers.txt.raw"));
const updatedData = readFileSync(filename).toString();
assert.ok(updatedData.includes("X-smtp-relay-MAIL_FROM:[email protected]\n"));
assert.ok(updatedData.includes("X-smtp-relay-CLIENT_HOSTNAME:localhost\n"));
assert.ok(updatedData.includes("X-smtp-relay-HELO:localhost\n"));
assert.ok(updatedData.includes("X-smtp-relay-USER:LZ\n"));
assert.strictEqual(destination, "1234.eml");

bucket = filename = destination = undefined;
Expand Down
7 changes: 4 additions & 3 deletions src/processors/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface LogProcessorConfig extends SmtpComponentConfig {
/**
* Fields to log
*
* @default ["from", "to", "cc", "subject", "text"]
* @default ["from", "to", "cc", "bcc", "subject", "text"]
*/
fields?: string[];
}
Expand All @@ -17,7 +17,7 @@ export class LogProcessor<T extends LogProcessorConfig = LogProcessorConfig> ext
type: string = "log";

init(): void {
this.config.fields ??= ["from", "to", "cc", "subject", "text"];
this.config.fields ??= ["from", "to", "cc", "bcc", "subject", "text"];
}

/**
Expand All @@ -30,6 +30,7 @@ export class LogProcessor<T extends LogProcessorConfig = LogProcessorConfig> ext
let content = `Email received ${new Date().toISOString()} from ${session.remoteAddress}
${"-".repeat(80)}
`;

this.config.fields
.filter(f => email[f] !== undefined)
.forEach(f => {
Expand All @@ -40,6 +41,6 @@ ${"-".repeat(80)}
content += `${f}: ${value}\n`;
});
content += `${"-".repeat(80)}\n`;
this.logger.log("INFO", content);
(this.logger ?? console).log("INFO", content);
}
}
52 changes: 52 additions & 0 deletions src/processors/nodemailer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,56 @@ class NodeMailerProcessorTest {
await nodemailer.onMail(session);
assert.notStrictEqual(msg, undefined);
}

@test
async bccResolution() {
const session = getFakeSession();
let nodemailer = new NodeMailerProcessor(
undefined,
{
type: "nodemailer",
nodemailer: {
host: "smtp.example.com",
port: 587,
secure: false, // upgrade later with STARTTLS
auth: {
user: "username",
pass: "password"
}
}
},
new WorkerOutput()
);
session.envelope.rcptTo = [
{ address: "[email protected]", args: [] },
{ address: "[email protected]", args: [] },
{ address: "[email protected]", args: [] }
];
session.email.cc = [
{
html: "",
text: "",
value: [{ name: "", address: "[email protected]" }]
}
];
session.email.to = [
{
html: "",
text: "",
value: [{ name: "", address: "[email protected]" }]
}
];
session.email.headerLines = [{ key: "plop", line: "test" }];
NodeMailerProcessor.transformEmail(session);
assert.deepStrictEqual(session.email.bcc, {
html: "[email protected]",
text: "[email protected]",
value: [
{
address: "[email protected]",
name: ""
}
]
});
}
}
4 changes: 2 additions & 2 deletions src/processors/nodemailer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AddressObject, ParsedMail } from "mailparser";
import * as nodemailer from "nodemailer";
import Mail from "nodemailer/lib/mailer";
import AddressParser from "nodemailer/lib/addressparser";
import AddressParser from "nodemailer/lib/addressparser/index";
import { SmtpComponentConfig } from "../component";
import { SmtpProcessor } from "../processor";
import { mapAddressObjects, SmtpSession } from "../server";
Expand Down Expand Up @@ -36,7 +36,7 @@ export class NodeMailerProcessor<
return a.value.map(ad => ad.address);
};
// If no headers we do not fill the bcc as we have no to,cc headers
if (session.email.headerLines.length && !session.email.bcc) {
if (session.email.headerLines?.length && !session.email.bcc) {
const noBcc = [session.email.cc, session.email.to].flat().filter(a => a);
const bcc: AddressObject[] = session.envelope.rcptTo
.filter(a => {
Expand Down
19 changes: 16 additions & 3 deletions src/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SmtpFilter } from "./filter";
import { SmtpFlow } from "./flow";
import { SmtpProcessor } from "./processor";
import { SmtpServer, SmtpSession, mapAddressObjects } from "./server";
import { readdirSync, unlinkSync } from "node:fs";

export class SmtpTest {
sock: Socket;
Expand Down Expand Up @@ -168,6 +169,12 @@ class OpenFilter extends SmtpFilter {

@suite
class SmtpServerTest {
static after() {
readdirSync(".")
.filter(f => f.startsWith(".email_") && f.endsWith(".eml"))
.forEach(f => unlinkSync(f));
}

@test
async middlewareChaining() {
defaultModules();
Expand All @@ -185,7 +192,10 @@ class SmtpServerTest {
);
server.close();
// cov
assert.ok(Array.isArray(mapAddressObjects({value: [], text: "", html: ""}, () => {})), "Should always return an array");
assert.ok(
Array.isArray(mapAddressObjects({ value: [], text: "", html: "" }, () => {})),
"Should always return an array"
);
// @ts-ignore
server.addFlow({ name: "Test" });
// @ts-ignore
Expand Down Expand Up @@ -252,7 +262,10 @@ class SmtpServerTest {
]
};
// @ts-ignore
await server.onDataRead({ flows: { fake: "PENDING" }, envelope: { mailFrom: {address: "[email protected]", args: {}}, rcptTo: [{address: "ok.com", args: {}}] } });
await server.onDataRead({
flows: { fake: "PENDING" },
envelope: { mailFrom: { address: "[email protected]", args: {} }, rcptTo: [{ address: "ok.com", args: {} }] }
});
}

@test
Expand Down Expand Up @@ -318,7 +331,7 @@ export function getFakeSession(): SmtpSession {
secure: false,
transmissionType: "TEST",
time: new Date(),
emailPath: "unit-test-fake-path",
emailPath: import.meta.dirname + "/../tests/data-headers.txt",
envelope: {
mailFrom: {
address: "[email protected]",
Expand Down

0 comments on commit 862803e

Please sign in to comment.