fix security issues with smtp
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc",
|
||||
"build": "tsup",
|
||||
"start": "node dist/server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -23,6 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/nodemailer": "^6.4.15",
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,98 +1,130 @@
|
||||
import { SMTPServer, SMTPServerOptions } from 'smtp-server';
|
||||
import { Readable } from 'stream';
|
||||
import dotenv from 'dotenv';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import { SMTPServer, SMTPServerOptions, SMTPServerSession } from "smtp-server";
|
||||
import { Readable } from "stream";
|
||||
import dotenv from "dotenv";
|
||||
import { simpleParser } from "mailparser";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const AUTH_USERNAME = process.env.SMTP_AUTH_USERNAME!;
|
||||
const UNSEND_BASE_URL = process.env.UNSEND_BASE_URL!;
|
||||
let API_KEY = '';
|
||||
const AUTH_USERNAME = process.env.SMTP_AUTH_USERNAME ?? "unsend";
|
||||
const UNSEND_BASE_URL = process.env.UNSEND_BASE_URL ?? "https://app.unsend.dev";
|
||||
const SSL_KEY_PATH = process.env.UNSEND_API_KEY_PATH;
|
||||
const SSL_CERT_PATH = process.env.UNSEND_API_CERT_PATH;
|
||||
|
||||
const ports = [25, 465, 2465, 587, 2587]; // Array of ports to listen on
|
||||
|
||||
const serverOptions: SMTPServerOptions = {
|
||||
onData(stream: Readable, session: any, callback: (error?: Error) => void) {
|
||||
console.log('Receiving email data...'); // Debug statement
|
||||
simpleParser(stream, (err, parsed) => {
|
||||
if (err) {
|
||||
console.error('Failed to parse email data:', err.message);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const emailObject: any = {
|
||||
to: Array.isArray(parsed.to) ? parsed.to.map(addr => addr.text).join(', ') : parsed.to?.text,
|
||||
from: Array.isArray(parsed.from) ? parsed.from.map(addr => addr.text).join(', ') : parsed.from?.text,
|
||||
subject: parsed.subject,
|
||||
text: parsed.text,
|
||||
html: parsed.html,
|
||||
};
|
||||
|
||||
console.log('Parsed email data:', emailObject); // Debug statement
|
||||
|
||||
sendEmailToUnsend(emailObject)
|
||||
.then(() => callback())
|
||||
.catch((error) => {
|
||||
console.error('Failed to send email:', error.message);
|
||||
callback(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
onAuth(auth: any, session: any, callback: (error?: Error, user?: any) => void) {
|
||||
API_KEY = auth.password;
|
||||
if (auth.username === AUTH_USERNAME) {
|
||||
console.log('Authenticated successfully'); // Debug statement
|
||||
callback(undefined, { user: AUTH_USERNAME });
|
||||
} else {
|
||||
console.error('Invalid username or password');
|
||||
callback(new Error('Invalid username or password'));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function sendEmailToUnsend(emailData: any) {
|
||||
async function sendEmailToUnsend(emailData: any, apiKey: string) {
|
||||
try {
|
||||
const apiEndpoint = '/api/v1/emails';
|
||||
const apiEndpoint = "/api/v1/emails";
|
||||
const url = new URL(apiEndpoint, UNSEND_BASE_URL); // Combine base URL with endpoint
|
||||
console.log('Sending email to Unsend API at:', url.href); // Debug statement
|
||||
console.log("Sending email to Unsend API at:", url.href); // Debug statement
|
||||
|
||||
const response = await fetch(url.href, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(emailData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('Unsend API error response:', errorData);
|
||||
throw new Error(`Failed to send email: ${errorData.message || 'Unknown error from server'}`);
|
||||
console.error("Unsend API error response:", errorData);
|
||||
throw new Error(
|
||||
`Failed to send email: ${errorData.error.message || "Unknown error from server"}`
|
||||
);
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
console.log('Unsend API response:', responseData);
|
||||
console.log("Unsend API response:", responseData);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Error message:', error.message);
|
||||
console.error("Error message:", error.message);
|
||||
throw new Error(`Failed to send email: ${error.message}`);
|
||||
} else {
|
||||
console.error('Unexpected error:', error);
|
||||
throw new Error('Failed to send email: Unexpected error occurred');
|
||||
console.error("Unexpected error:", error);
|
||||
throw new Error("Failed to send email: Unexpected error occurred");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverOptions: SMTPServerOptions = {
|
||||
secure: false,
|
||||
key: SSL_KEY_PATH ? readFileSync(SSL_KEY_PATH) : undefined,
|
||||
cert: SSL_CERT_PATH ? readFileSync(SSL_CERT_PATH) : undefined,
|
||||
onData(
|
||||
stream: Readable,
|
||||
session: SMTPServerSession,
|
||||
callback: (error?: Error) => void
|
||||
) {
|
||||
console.log("Receiving email data..."); // Debug statement
|
||||
simpleParser(stream, (err, parsed) => {
|
||||
if (err) {
|
||||
console.error("Failed to parse email data:", err.message);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!session.user) {
|
||||
console.error("No API key found in session");
|
||||
return callback(new Error("No API key found in session"));
|
||||
}
|
||||
|
||||
const emailObject = {
|
||||
to: Array.isArray(parsed.to)
|
||||
? parsed.to.map((addr) => addr.text).join(", ")
|
||||
: parsed.to?.text,
|
||||
from: Array.isArray(parsed.from)
|
||||
? parsed.from.map((addr) => addr.text).join(", ")
|
||||
: parsed.from?.text,
|
||||
subject: parsed.subject,
|
||||
text: parsed.text,
|
||||
html: parsed.html,
|
||||
};
|
||||
|
||||
console.log("Parsed email data:", emailObject); // Debug statement
|
||||
|
||||
sendEmailToUnsend(emailObject, session.user)
|
||||
.then(() => callback())
|
||||
.catch((error) => {
|
||||
console.error("Failed to send email:", error.message);
|
||||
callback(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
onAuth(auth, session: any, callback: (error?: Error, user?: any) => void) {
|
||||
if (auth.username === AUTH_USERNAME && auth.password) {
|
||||
console.log("Authenticated successfully"); // Debug statement
|
||||
callback(undefined, { user: auth.password });
|
||||
} else {
|
||||
console.error("Invalid username or password");
|
||||
callback(new Error("Invalid username or password"));
|
||||
}
|
||||
},
|
||||
size: 10485760,
|
||||
};
|
||||
|
||||
function startServers() {
|
||||
ports.forEach(port => {
|
||||
// Implicit SSL/TLS for ports 465 and 2465
|
||||
[465, 2465].forEach((port) => {
|
||||
const server = new SMTPServer({ ...serverOptions, secure: true });
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`Implicit SSL/TLS SMTP server is listening on port ${port}`);
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
console.error(`Error occurred on port ${port}:`, err);
|
||||
});
|
||||
});
|
||||
|
||||
// STARTTLS for ports 25, 587, and 2587
|
||||
[25, 587, 2587].forEach((port) => {
|
||||
const server = new SMTPServer(serverOptions);
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`SMTP server is listening on port ${port}`);
|
||||
console.log(`STARTTLS SMTP server is listening on port ${port}`);
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
server.on("error", (err) => {
|
||||
console.error(`Error occurred on port ${port}:`, err);
|
||||
});
|
||||
});
|
||||
|
@@ -1,37 +1,30 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: "localhost",
|
||||
port: 2587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: "unsend",
|
||||
pass: "us_38de56vwa7_cc90a91b01a402de0c15516b3554adc1",
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
host: "localhost",
|
||||
port: 2587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: "unsend",
|
||||
pass: "us_38de56vwa7_cc90a91b01a402de0c15516b3554adc1",
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
to: "harsh121102@gmail.com",
|
||||
from: "hello@support.harshbhat.me",
|
||||
subject: "Testing SMTP",
|
||||
html: "<strong>THIS IS USING SMTP,</strong><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",
|
||||
text: "hello,\n\nUnsend is the best open source sending platform",
|
||||
to: "harsh121102@gmail.com",
|
||||
from: "hello@support.harshbhat.me",
|
||||
subject: "Testing SMTP",
|
||||
html: "<strong>THIS IS USING SMTP,</strong><p>Unsend is the best open source sending platform<p><p>check out <a href='https://unsend.dev'>unsend.dev</a>",
|
||||
text: "hello,\n\nUnsend is the best open source sending platform",
|
||||
};
|
||||
|
||||
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
if (error) {
|
||||
console.error('Error sending email:', error);
|
||||
console.error("Error sending email:", error);
|
||||
} else {
|
||||
console.log('Email sent successfully:', info.response);
|
||||
console.log("Email sent successfully:", info.response);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
13
apps/smtp-server/tsup.config.ts
Normal file
13
apps/smtp-server/tsup.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { defineConfig, Options } from "tsup";
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default defineConfig((options: Options) => ({
|
||||
entry: ["src/server.ts"],
|
||||
format: ["cjs"],
|
||||
dts: true,
|
||||
minify: true,
|
||||
clean: true,
|
||||
injectStyle: true,
|
||||
...options,
|
||||
}));
|
Reference in New Issue
Block a user