swaf/src/components/MailComponent.ts

116 lines
4.0 KiB
TypeScript

import config from "config";
import {Express} from "express";
import nodemailer, {SentMessageInfo, Transporter} from "nodemailer";
import util from "util";
import ApplicationComponent from "../ApplicationComponent.js";
import {route} from "../common/Routing.js";
import MailViewEngine from "../frontend/MailViewEngine.js";
import {logger} from "../Logger.js";
import Mail from "../mail/Mail.js";
import MailError from "../mail/MailError.js";
import SecurityError from "../SecurityError.js";
import FrontendToolsComponent from "./FrontendToolsComponent.js";
export default class MailComponent extends ApplicationComponent {
private transporter?: Transporter;
public constructor(
private readonly viewEngine: MailViewEngine,
) {
super();
}
public async checkSecuritySettings(): Promise<void> {
if (!config.get<boolean>('mail.secure')) {
throw new SecurityError('Cannot set mail.secure (starttls) to false');
}
if (config.get<boolean>('mail.allow_invalid_tls')) {
throw new SecurityError('Cannot set mail.allow_invalid_tls (ignore tls failure) to true');
}
}
public async start(app: Express): Promise<void> {
await this.prepare('Mail connection', async () => {
const transporter = nodemailer.createTransport({
host: config.get('mail.host'),
port: config.get('mail.port'),
requireTLS: config.get('mail.secure'), // STARTTLS
auth: {
user: config.get('mail.username'),
pass: config.get('mail.password'),
},
tls: {
rejectUnauthorized: !config.get('mail.allow_invalid_tls'),
},
});
try {
await util.promisify(transporter.verify)();
this.transporter = transporter;
} catch (e) {
throw new MailError('Connection to mail service unsuccessful.', e);
}
logger.info(`Mail ready to be distributed via ${config.get('mail.host')}:${config.get('mail.port')}`);
});
await this.viewEngine.setup(app, false);
}
public async stop(): Promise<void> {
if (this.transporter) {
this.transporter.close();
this.transporter = undefined;
}
await this.viewEngine.stop();
}
public async sendMail(mail: Mail, ...to: string[]): Promise<SentMessageInfo[]> {
if (to.length === 0) throw new Error('Cannot send an email to recipient. (to is empty)');
const results = [];
for (const destEmail of to) {
const template = mail.getTemplate();
const locals = mail.getData();
const options = mail.getOptions();
// Reset options
options.html = options.text = undefined;
// Set options
options.to = destEmail;
options.from = {
name: config.get('mail.from_name'),
address: config.get('mail.from'),
};
// Set locals
locals.mail_subject = options.subject;
locals.mail_to = options.to;
locals.mail_link = config.get<string>('public_url') +
route('mail', [template.template], locals);
Object.assign(locals, this.getApp().as(FrontendToolsComponent).getGlobals().get());
// Log
logger.debug(`Send mail from ${options.from.address} to ${options.to}`);
// Render email
options.html = await this.viewEngine.render('mails/' + template.template + '.mnjk', locals, false);
options.text = await this.viewEngine.render('mails/' + template.template + '.mnjk', locals, true);
// Send email
results.push(await this.getTransporter().sendMail(options));
}
return results;
}
private getTransporter(): Transporter {
if (!this.transporter) throw new MailError('Mail system was not prepared.');
return this.transporter;
}
}