From 4d0c714dbd188f031ca38f7f4f6ca52cf90b4882 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Tue, 3 Nov 2020 10:29:36 +0100 Subject: [PATCH] Render emails using NunjucksComponent's environment --- src/Mail.ts | 25 +++++++++++++------ src/auth/AuthGuard.ts | 9 ++++++- src/auth/MailController.ts | 4 ++- .../magic_link/MagicLinkAuthController.ts | 1 + src/auth/magic_link/MagicLinkController.ts | 4 ++- src/components/NunjucksComponent.ts | 13 +++++----- src/components/WebSocketServerComponent.ts | 2 +- src/helpers/BackendController.ts | 5 ++-- test/_app.ts | 2 +- 9 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/Mail.ts b/src/Mail.ts index b7b8024..820e40d 100644 --- a/src/Mail.ts +++ b/src/Mail.ts @@ -1,7 +1,7 @@ import nodemailer, {SentMessageInfo, Transporter} from "nodemailer"; import config from "config"; import {Options} from "nodemailer/lib/mailer"; -import nunjucks from 'nunjucks'; +import {Environment} from 'nunjucks'; import * as util from "util"; import {WrappingError} from "./Utils"; import mjml2html from "mjml"; @@ -45,9 +45,14 @@ export default class Mail { if (this.transporter) this.transporter.close(); } - public static parse(template: string, data: { [p: string]: unknown }, textOnly: boolean): string { + public static parse( + environment: Environment, + template: string, + data: { [p: string]: unknown }, + textOnly: boolean, + ): string { data.text = textOnly; - const nunjucksResult = nunjucks.render(template, data); + const nunjucksResult = environment.render(template, data); if (textOnly) return nunjucksResult; const mjmlResult = mjml2html(nunjucksResult, {}); @@ -59,11 +64,13 @@ export default class Mail { return mjmlResult.html; } - private readonly template: MailTemplate; private readonly options: Options = {}; - private readonly data: ParsedUrlQueryInput; - public constructor(template: MailTemplate, data: ParsedUrlQueryInput = {}) { + public constructor( + private readonly environment: Environment, + private readonly template: MailTemplate, + private readonly data: ParsedUrlQueryInput = {}, + ) { this.template = template; this.data = data; this.options.subject = this.template.getSubject(data); @@ -106,8 +113,10 @@ export default class Mail { log.debug('Send mail', this.options); // Render email - this.options.html = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, false); - this.options.text = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, true); + this.options.html = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk', + this.data, false); + this.options.text = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk', + this.data, true); // Send email results.push(await Mail.getTransporter().sendMail(this.options)); diff --git a/src/auth/AuthGuard.ts b/src/auth/AuthGuard.ts index 22623ff..4d645a3 100644 --- a/src/auth/AuthGuard.ts +++ b/src/auth/AuthGuard.ts @@ -7,8 +7,15 @@ import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails"; import Mail from "../Mail"; import Controller from "../Controller"; import config from "config"; +import Application from "../Application"; +import NunjucksComponent from "../components/NunjucksComponent"; export default abstract class AuthGuard

> { + public constructor( + private readonly app: Application, + ) { + } + protected abstract async getProofForSession(session: Express.Session): Promise

; protected async getProofForRequest(_req: Request): Promise

{ @@ -77,7 +84,7 @@ export default abstract class AuthGuard

> { } if (!user.isApproved()) { - await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, { + await new Mail(this.app.as(NunjucksComponent).getEnvironment(), PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, { username: (await user.mainEmail.get())?.getOrFail('email'), link: config.get('base_url') + Controller.route('accounts-approval'), }).send(config.get('app.contact_email')); diff --git a/src/auth/MailController.ts b/src/auth/MailController.ts index 517bddb..a31560e 100644 --- a/src/auth/MailController.ts +++ b/src/auth/MailController.ts @@ -1,6 +1,7 @@ import {Request, Response} from "express"; import Controller from "../Controller"; import Mail from "../Mail"; +import NunjucksComponent from "../components/NunjucksComponent"; export default class MailController extends Controller { public routes(): void { @@ -9,6 +10,7 @@ export default class MailController extends Controller { protected async getMail(request: Request, response: Response): Promise { const template = request.params['template']; - response.send(Mail.parse(`mails/${template}.mjml.njk`, request.query, false)); + response.send(Mail.parse(this.getApp().as(NunjucksComponent).getEnvironment(), + `mails/${template}.mjml.njk`, request.query, false)); } } diff --git a/src/auth/magic_link/MagicLinkAuthController.ts b/src/auth/magic_link/MagicLinkAuthController.ts index c344e32..7d44f4f 100644 --- a/src/auth/magic_link/MagicLinkAuthController.ts +++ b/src/auth/magic_link/MagicLinkAuthController.ts @@ -99,6 +99,7 @@ export default abstract class MagicLinkAuthController extends AuthController { // Register (email link) const geo = geoip.lookup(req.ip); await MagicLinkController.sendMagicLink( + this.getApp(), req.getSession().id, isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType, Controller.route('auth', undefined, { diff --git a/src/auth/magic_link/MagicLinkController.ts b/src/auth/magic_link/MagicLinkController.ts index 549ac6d..b26de2a 100644 --- a/src/auth/magic_link/MagicLinkController.ts +++ b/src/auth/magic_link/MagicLinkController.ts @@ -8,9 +8,11 @@ import MagicLink from "../models/MagicLink"; import config from "config"; import Application from "../../Application"; import {ParsedUrlQueryInput} from "querystring"; +import NunjucksComponent from "../../components/NunjucksComponent"; export default abstract class MagicLinkController extends Controller { public static async sendMagicLink( + app: Application, sessionId: string, actionType: string, original_url: string, @@ -32,7 +34,7 @@ export default abstract class MagicLinkController extends await link.save(); // Send email - await new Mail(mailTemplate, Object.assign(data, { + await new Mail(app.as(NunjucksComponent).getEnvironment(), mailTemplate, Object.assign(data, { link: `${config.get('base_url')}${Controller.route('magic_link', undefined, { id: link.id, token: token, diff --git a/src/components/NunjucksComponent.ts b/src/components/NunjucksComponent.ts index 8f681bb..df62556 100644 --- a/src/components/NunjucksComponent.ts +++ b/src/components/NunjucksComponent.ts @@ -13,7 +13,7 @@ import Middleware from "../Middleware"; export default class NunjucksComponent extends ApplicationComponent { private readonly viewsPath: string[]; - private env?: Environment; + private environment?: Environment; public constructor(viewsPath: string[] = ['views']) { super(); @@ -32,7 +32,7 @@ export default class NunjucksComponent extends ApplicationComponent { log.warn('Couldn\'t determine coreVersion.', e); } - this.env = new nunjucks.Environment([ + this.environment = new nunjucks.Environment([ ...this.viewsPath.map(path => new nunjucks.FileSystemLoader(path)), new nunjucks.FileSystemLoader(path.join(__dirname, '../../../views')), new nunjucks.FileSystemLoader(path.join(__dirname, '../views')), @@ -60,7 +60,7 @@ export default class NunjucksComponent extends ApplicationComponent { .addFilter('hex', (v: number) => { return v.toString(16); }); - this.env.express(app); + this.environment.express(app); app.set('view engine', 'njk'); } @@ -68,8 +68,9 @@ export default class NunjucksComponent extends ApplicationComponent { this.use(NunjucksMiddleware); } - public getEnv(): Environment | undefined { - return this.env; + public getEnvironment(): Environment { + if (!this.environment) throw new Error('Environment not initialized.'); + return this.environment; } } @@ -77,7 +78,7 @@ export class NunjucksMiddleware extends Middleware { private env?: Environment; protected async handle(req: Request, res: Response, next: NextFunction): Promise { - this.env = this.app.as(NunjucksComponent).getEnv(); + this.env = this.app.as(NunjucksComponent).getEnvironment(); res.locals.url = req.url; res.locals.params = req.params; res.locals.query = req.query; diff --git a/src/components/WebSocketServerComponent.ts b/src/components/WebSocketServerComponent.ts index 58f4888..e719538 100644 --- a/src/components/WebSocketServerComponent.ts +++ b/src/components/WebSocketServerComponent.ts @@ -71,7 +71,7 @@ export default class WebSocketServerComponent extends ApplicationComponent { }); }); - const env = this.nunjucksComponent?.getEnv(); + const env = this.nunjucksComponent?.getEnvironment(); if (env) { env.addGlobal('websocketUrl', config.get('public_websocket_url')); } diff --git a/src/helpers/BackendController.ts b/src/helpers/BackendController.ts index f1901ee..52c8607 100644 --- a/src/helpers/BackendController.ts +++ b/src/helpers/BackendController.ts @@ -8,6 +8,7 @@ import {ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE} from "../Mails"; import UserEmail from "../auth/models/UserEmail"; import UserApprovedComponent from "../auth/models/UserApprovedComponent"; import {RequireAdminMiddleware, RequireAuthMiddleware} from "../auth/AuthComponent"; +import NunjucksComponent from "../components/NunjucksComponent"; export default class BackendController extends Controller { private static readonly menu: BackendMenuElement[] = []; @@ -75,7 +76,7 @@ export default class BackendController extends Controller { await account.save(); if (email && email.email) { - await new Mail(ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, { + await new Mail(this.getApp().as(NunjucksComponent).getEnvironment(), ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, { approved: true, link: config.get('base_url') + Controller.route('auth'), }).send(email.email); @@ -91,7 +92,7 @@ export default class BackendController extends Controller { await account.delete(); if (email && email.email) { - await new Mail(ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, { + await new Mail(this.getApp().as(NunjucksComponent).getEnvironment(), ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, { approved: false, }).send(email.email); } diff --git a/test/_app.ts b/test/_app.ts index 8f38fc5..7ae48dc 100644 --- a/test/_app.ts +++ b/test/_app.ts @@ -99,7 +99,7 @@ export class TestApp extends Application { public async getProofForSession(session: Express.Session): Promise { return await MagicLink.bySessionId(session.id, ['login', 'register']); } - })); + }(this))); // Utils this.use(new FormHelperComponent());