mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
[N8N-4355] Use safer templating for UserManagement emails (#3893)
This commit is contained in:
parent
dc8f8b7874
commit
c65458c154
1
package-lock.json
generated
1
package-lock.json
generated
|
@ -53645,6 +53645,7 @@
|
||||||
"fast-glob": "^3.2.5",
|
"fast-glob": "^3.2.5",
|
||||||
"flatted": "^3.2.4",
|
"flatted": "^3.2.4",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
|
"handlebars": "4.7.7",
|
||||||
"inquirer": "^7.0.1",
|
"inquirer": "^7.0.1",
|
||||||
"json-diff": "^0.5.4",
|
"json-diff": "^0.5.4",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
|
|
|
@ -129,6 +129,7 @@
|
||||||
"fast-glob": "^3.2.5",
|
"fast-glob": "^3.2.5",
|
||||||
"flatted": "^3.2.4",
|
"flatted": "^3.2.4",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
|
"handlebars": "4.7.7",
|
||||||
"inquirer": "^7.0.1",
|
"inquirer": "^7.0.1",
|
||||||
"json-diff": "^0.5.4",
|
"json-diff": "^0.5.4",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
/* eslint-disable import/no-cycle */
|
import Handlebars from 'handlebars';
|
||||||
import { existsSync, readFileSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { IDataObject } from 'n8n-workflow';
|
import { readFile } from 'fs/promises';
|
||||||
import { join as pathJoin } from 'path';
|
import { join as pathJoin } from 'path';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { GenericHelpers } from '../..';
|
import { GenericHelpers } from '../..';
|
||||||
import * as config from '../../../config';
|
import * as config from '../../../config';
|
||||||
import {
|
import {
|
||||||
|
@ -12,35 +13,33 @@ import {
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
import { NodeMailer } from './NodeMailer';
|
import { NodeMailer } from './NodeMailer';
|
||||||
|
|
||||||
// TODO: make function fully async (remove sync functions)
|
type Template = HandlebarsTemplateDelegate<unknown>;
|
||||||
async function getTemplate(configKeyName: string, defaultFilename: string) {
|
type TemplateName = 'invite' | 'passwordReset';
|
||||||
const templateOverride = (await GenericHelpers.getConfigValue(
|
|
||||||
`userManagement.emails.templates.${configKeyName}`,
|
|
||||||
)) as string;
|
|
||||||
|
|
||||||
let template;
|
const templates: Partial<Record<TemplateName, Template>> = {};
|
||||||
if (templateOverride && existsSync(templateOverride)) {
|
|
||||||
template = readFileSync(templateOverride, {
|
async function getTemplate(
|
||||||
encoding: 'utf-8',
|
templateName: TemplateName,
|
||||||
});
|
defaultFilename = `${templateName}.html`,
|
||||||
} else {
|
): Promise<Template> {
|
||||||
template = readFileSync(pathJoin(__dirname, `templates/${defaultFilename}`), {
|
let template = templates[templateName];
|
||||||
encoding: 'utf-8',
|
if (!template) {
|
||||||
});
|
const templateOverride = (await GenericHelpers.getConfigValue(
|
||||||
|
`userManagement.emails.templates.${templateName}`,
|
||||||
|
)) as string;
|
||||||
|
|
||||||
|
let markup;
|
||||||
|
if (templateOverride && existsSync(templateOverride)) {
|
||||||
|
markup = await readFile(templateOverride, 'utf-8');
|
||||||
|
} else {
|
||||||
|
markup = await readFile(pathJoin(__dirname, `templates/${defaultFilename}`), 'utf-8');
|
||||||
|
}
|
||||||
|
template = Handlebars.compile(markup);
|
||||||
|
templates[templateName] = template;
|
||||||
}
|
}
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStrings(template: string, data: IDataObject) {
|
|
||||||
let output = template;
|
|
||||||
const keys = Object.keys(data);
|
|
||||||
keys.forEach((key) => {
|
|
||||||
const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
|
|
||||||
output = output.replace(regex, data[key] as string);
|
|
||||||
});
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserManagementMailer {
|
export class UserManagementMailer {
|
||||||
private mailer: UserManagementMailerImplementation | undefined;
|
private mailer: UserManagementMailerImplementation | undefined;
|
||||||
|
|
||||||
|
@ -58,13 +57,13 @@ export class UserManagementMailer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async invite(inviteEmailData: InviteEmailData): Promise<SendEmailResult> {
|
async invite(inviteEmailData: InviteEmailData): Promise<SendEmailResult> {
|
||||||
let template = await getTemplate('invite', 'invite.html');
|
if (!this.mailer) return Promise.reject();
|
||||||
template = replaceStrings(template, inviteEmailData);
|
|
||||||
|
|
||||||
const result = await this.mailer?.sendMail({
|
const template = await getTemplate('invite');
|
||||||
|
const result = await this.mailer.sendMail({
|
||||||
emailRecipients: inviteEmailData.email,
|
emailRecipients: inviteEmailData.email,
|
||||||
subject: 'You have been invited to n8n',
|
subject: 'You have been invited to n8n',
|
||||||
body: template,
|
body: template(inviteEmailData),
|
||||||
});
|
});
|
||||||
|
|
||||||
// If mailer does not exist it means mail has been disabled.
|
// If mailer does not exist it means mail has been disabled.
|
||||||
|
@ -72,14 +71,13 @@ export class UserManagementMailer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async passwordReset(passwordResetData: PasswordResetData): Promise<SendEmailResult> {
|
async passwordReset(passwordResetData: PasswordResetData): Promise<SendEmailResult> {
|
||||||
let template = await getTemplate('passwordReset', 'passwordReset.html');
|
if (!this.mailer) return Promise.reject();
|
||||||
template = replaceStrings(template, passwordResetData);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
const template = await getTemplate('passwordReset');
|
||||||
const result = await this.mailer?.sendMail({
|
const result = await this.mailer.sendMail({
|
||||||
emailRecipients: passwordResetData.email,
|
emailRecipients: passwordResetData.email,
|
||||||
subject: 'n8n password reset',
|
subject: 'n8n password reset',
|
||||||
body: template,
|
body: template(passwordResetData),
|
||||||
});
|
});
|
||||||
|
|
||||||
// If mailer does not exist it means mail has been disabled.
|
// If mailer does not exist it means mail has been disabled.
|
||||||
|
|
Loading…
Reference in a new issue