feat: Implement draft version of reset password email (#949)
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
"graphql-shield": "^7.5.0",
|
||||
"graphql-tools": "^8.2.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"http-errors": "~1.6.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"knex": "^2.4.0",
|
||||
|
@@ -32,6 +32,12 @@ type AppConfig = {
|
||||
bullMQDashboardPassword: string;
|
||||
telemetryEnabled: boolean;
|
||||
requestBodySizeLimit: string;
|
||||
smtpHost: string;
|
||||
smtpPort: number;
|
||||
smtpSecure: boolean;
|
||||
smtpUser: string;
|
||||
smtpPassword: string;
|
||||
fromEmail: string;
|
||||
licenseKey: string;
|
||||
};
|
||||
|
||||
@@ -92,6 +98,12 @@ const appConfig: AppConfig = {
|
||||
webhookUrl,
|
||||
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
|
||||
requestBodySizeLimit: '1mb',
|
||||
smtpHost: process.env.SMTP_HOST,
|
||||
smtpPort: parseInt(process.env.SMTP_PORT || '587'),
|
||||
smtpSecure: process.env.SMTP_SECURE === 'true',
|
||||
smtpUser: process.env.SMTP_USER,
|
||||
smtpPassword: process.env.SMTP_PASSWORD,
|
||||
fromEmail: process.env.FROM_EMAIL,
|
||||
licenseKey: process.env.LICENSE_KEY,
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,9 @@
|
||||
import User from '../../models/user';
|
||||
import emailQueue from '../../queues/email';
|
||||
import {
|
||||
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
} from '../../helpers/remove-job-configuration';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
@@ -16,7 +21,24 @@ const forgotPassword = async (_parent: unknown, params: Params) => {
|
||||
}
|
||||
|
||||
await user.generateResetPasswordToken();
|
||||
// TODO: Send email with reset password link
|
||||
|
||||
const jobName = `Reset Password Email - ${user.id}`;
|
||||
|
||||
const jobPayload = {
|
||||
email: user.email,
|
||||
subject: 'Reset Password',
|
||||
template: 'reset-password-instructions',
|
||||
params: {
|
||||
token: user.resetPasswordToken,
|
||||
},
|
||||
};
|
||||
|
||||
const jobOptions = {
|
||||
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||
};
|
||||
|
||||
await emailQueue.add(jobName, jobPayload, jobOptions);
|
||||
|
||||
return;
|
||||
};
|
||||
|
12
packages/backend/src/helpers/compile-email.ee.ts
Normal file
12
packages/backend/src/helpers/compile-email.ee.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as handlebars from 'handlebars';
|
||||
|
||||
const compileEmail = (emailPath: string, replacements: object = {}): string => {
|
||||
const filePath = path.join(__dirname, `../views/emails/${emailPath}.ee.hbs`);
|
||||
const source = fs.readFileSync(filePath, 'utf-8').toString();
|
||||
const template = handlebars.compile(source);
|
||||
return template(replacements);
|
||||
};
|
||||
|
||||
export default compileEmail;
|
14
packages/backend/src/helpers/mailer.ee.ts
Normal file
14
packages/backend/src/helpers/mailer.ee.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import appConfig from '../config/app';
|
||||
|
||||
const mailer = nodemailer.createTransport({
|
||||
host: appConfig.smtpHost,
|
||||
port: appConfig.smtpPort,
|
||||
secure: appConfig.smtpSecure,
|
||||
auth: {
|
||||
user: appConfig.smtpUser,
|
||||
pass: appConfig.smtpPassword,
|
||||
},
|
||||
});
|
||||
|
||||
export default mailer;
|
25
packages/backend/src/queues/email.ts
Normal file
25
packages/backend/src/queues/email.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import process from 'process';
|
||||
import { Queue } from 'bullmq';
|
||||
import redisConfig from '../config/redis';
|
||||
import logger from '../helpers/logger';
|
||||
|
||||
const CONNECTION_REFUSED = 'ECONNREFUSED';
|
||||
|
||||
const redisConnection = {
|
||||
connection: redisConfig,
|
||||
};
|
||||
|
||||
const emailQueue = new Queue('email', redisConnection);
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await emailQueue.close();
|
||||
});
|
||||
|
||||
emailQueue.on('error', (err) => {
|
||||
if ((err as any).code === CONNECTION_REFUSED) {
|
||||
logger.error('Make sure you have installed Redis and it is running.', err);
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
|
||||
export default emailQueue;
|
@@ -0,0 +1,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
Hello {{ email }}
|
||||
|
||||
Someone has requested a link to change your password, and you can do this through the link below.
|
||||
|
||||
<a href="/reset-password">Change my password</a>
|
||||
|
||||
If you didn't request this, please ignore this email.
|
||||
Your password won't change until you access the link above and create a new one.
|
||||
</body>
|
||||
</html>
|
@@ -3,6 +3,7 @@ import './helpers/check-worker-readiness';
|
||||
import './workers/flow';
|
||||
import './workers/trigger';
|
||||
import './workers/action';
|
||||
import './workers/email';
|
||||
import telemetry from './helpers/telemetry';
|
||||
|
||||
telemetry.setServiceType('worker');
|
||||
|
37
packages/backend/src/workers/email.ts
Normal file
37
packages/backend/src/workers/email.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import redisConfig from '../config/redis';
|
||||
import logger from '../helpers/logger';
|
||||
import mailer from '../helpers/mailer.ee';
|
||||
import compileEmail from '../helpers/compile-email.ee';
|
||||
import appConfig from '../config/app';
|
||||
|
||||
export const worker = new Worker(
|
||||
'email',
|
||||
async (job) => {
|
||||
const { email, subject, templateName, params } = job.data;
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: appConfig.fromEmail,
|
||||
subject: subject,
|
||||
html: compileEmail(templateName, params),
|
||||
});
|
||||
},
|
||||
{ connection: redisConfig }
|
||||
);
|
||||
|
||||
worker.on('completed', (job) => {
|
||||
logger.info(
|
||||
`JOB ID: ${job.id} - ${job.data.subject} email sent to ${job.data.email}!`
|
||||
);
|
||||
});
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
logger.info(
|
||||
`JOB ID: ${job.id} - ${job.data.subject} email to ${job.data.email} has failed to send with ${err.message}`
|
||||
);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
await worker.close();
|
||||
});
|
Reference in New Issue
Block a user