From 689b710c9e61646e62d3aea21d616402665d2f66 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Fri, 20 Jan 2023 10:43:06 -0500 Subject: Fix template rendering and use verify email template email html is weird, some stuff isn't supported. --- assets/email_templates/new_login_location.html | 162 ++++++++++----------- assets/email_templates/password_changed.html | 77 +++++----- assets/email_templates/password_reset_request.html | 145 +++++++++--------- assets/email_templates/phone_removed.html | 85 ++++++----- assets/email_templates/verify_email.html | 146 +++++++++---------- src/api/routes/auth/verify/resend.ts | 2 +- src/util/entities/User.ts | 2 +- src/util/util/Email.ts | 121 +++++++++++++-- 8 files changed, 419 insertions(+), 321 deletions(-) diff --git a/assets/email_templates/new_login_location.html b/assets/email_templates/new_login_location.html index 701196cd..e597ac6c 100644 --- a/assets/email_templates/new_login_location.html +++ b/assets/email_templates/new_login_location.html @@ -4,108 +4,108 @@ + Verify {instanceName} Login from New Location - Branding -
-

+ Branding +

- Hey {username}, -

-

- It looks like someone tried to log into your {instanceName} - account from a new location. If this is you, follow the link - below to authorize logging in from this location on your - account. If this isn't you, we suggest changing your password as - soon as possible. -

-

- IP Address: {ip} -
- Location: {location} -

-
-
- Verify Login +

+ It looks like someone tried to log into your {instanceName} + account from a new location. If this is you, follow the link + below to authorize logging in from this location on your + account. If this isn't you, we suggest changing your + password as soon as possible. +

+

+ IP Address: {ipAddress} +
+ Location: {locationCity}, {locationRegion}, + {locationCountryName} +

+
+
-
-
-
-

- Alternatively, you can directly paste this link into - your browser: -

- {verifyUrl} + Verify Login +
+
+
+

+ Alternatively, you can directly paste this link into + your browser: +

+ {verifyUrl} +
diff --git a/assets/email_templates/password_changed.html b/assets/email_templates/password_changed.html index 3f762702..399108a2 100644 --- a/assets/email_templates/password_changed.html +++ b/assets/email_templates/password_changed.html @@ -4,57 +4,62 @@ + {instanceName} Password Changed - Branding -
-

+ Branding +

- Hey {username}, -

-

Your {instanceName} password has been changed.

-

- If this wasn't done by you, please immediately reset the - password to your {instanceName} account. -

+

+ Hey {userUsername}, +

+

Your {instanceName} password has been changed.

+

+ If this wasn't done by you, please immediately reset the + password to your {instanceName} account. +

+
diff --git a/assets/email_templates/password_reset_request.html b/assets/email_templates/password_reset_request.html index fc77b47b..ab8f4d23 100644 --- a/assets/email_templates/password_reset_request.html +++ b/assets/email_templates/password_reset_request.html @@ -4,103 +4,96 @@ + Password Reset Request for {instanceName} - Branding -
-

+ Branding +

- Hey {username}, -

-

- Your {instanceName} password can be reset by clicking the button - below. If you did not request a new password, please ignore this - email. -

-
- -
-
-

- Alternatively, you can directly paste this link into - your browser: -

- {passwordResetUrl} +

+ Your {instanceName} password can be reset by clicking the + button below. If you did not request a new password, please + ignore this email. +

+
+ +
+
+

+ Alternatively, you can directly paste this link into + your browser: +

+ {passwordResetUrl} +
diff --git a/assets/email_templates/phone_removed.html b/assets/email_templates/phone_removed.html index 1eb52fbe..65807e29 100644 --- a/assets/email_templates/phone_removed.html +++ b/assets/email_templates/phone_removed.html @@ -4,61 +4,66 @@ + Phone Removed From {instanceName} Account - Branding -
-

+ Branding +

- Hey {username}, -

-

- Your phone number ********{phoneNumber} was recently removed - from this account and added to a different {instanceName} - account. -

-

- Please note that your phone number can only be linked to one - {instanceName} account at a time. -

+

+ Hey {userUsername}, +

+

+ Your phone number ********{phoneNumber} was recently removed + from this account and added to a different {instanceName} + account. +

+

+ Please note that your phone number can only be linked to one + {instanceName} account at a time. +

+
diff --git a/assets/email_templates/verify_email.html b/assets/email_templates/verify_email.html index f0c11e52..604242c4 100644 --- a/assets/email_templates/verify_email.html +++ b/assets/email_templates/verify_email.html @@ -4,103 +4,97 @@ + Verify Email Address for {instanceName} - Branding -
-

+ Branding +

- Hey {username}, -

-

- Thanks for registering for an account on {instanceName}! Before - we get started, we just need to confirm that this is you. Click - below to verify your email address: -

-
- -
-
-

- Alternatively, you can directly paste this link into - your browser: -

- {verificationUrl} +

+ Thanks for registering for an account on {instanceName}! + Before we get started, we just need to confirm that this is + you. Click below to verify your email address: +

+
+ +
+
+

+ Alternatively, you can directly paste this link into + your browser: +

+ {emailVerificationUrl} +
diff --git a/src/api/routes/auth/verify/resend.ts b/src/api/routes/auth/verify/resend.ts index 0c8c4ed9..d9a9cda5 100644 --- a/src/api/routes/auth/verify/resend.ts +++ b/src/api/routes/auth/verify/resend.ts @@ -33,7 +33,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { throw new HTTPError("User does not have an email address", 400); } - await Email.sendVerificationEmail(req.user_id, user.email) + await Email.sendVerificationEmail(user, user.email) .then((info) => { console.log("Message sent: %s", info.messageId); return res.sendStatus(204); diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index 66e10297..4a399ed9 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -386,7 +386,7 @@ export class User extends BaseClass { // send verification email if users aren't verified by default and we have an email if (!Config.get().defaults.user.verified && email) { - await Email.sendVerificationEmail(user.id, email) + await Email.sendVerificationEmail(user, email) .then((info) => { console.log("Message sent: %s", info.messageId); }) diff --git a/src/util/util/Email.ts b/src/util/util/Email.ts index 371ba827..9688c3c5 100644 --- a/src/util/util/Email.ts +++ b/src/util/util/Email.ts @@ -16,10 +16,14 @@ along with this program. If not, see . */ +import fs from "node:fs"; +import path from "node:path"; import nodemailer, { Transporter } from "nodemailer"; +import { User } from "../entities"; import { Config } from "./Config"; import { generateToken } from "./Token"; +const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets"); export const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; @@ -51,7 +55,20 @@ export function adjustEmail(email?: string): string | undefined { export const Email: { transporter: Transporter | null; init: () => Promise; - sendVerificationEmail: (id: string, email: string) => Promise; + generateVerificationLink: (id: string, email: string) => Promise; + sendVerificationEmail: (user: User, email: string) => Promise; + doReplacements: ( + template: string, + user: User, + emailVerificationUrl?: string, + passwordResetUrl?: string, + ipInfo?: { + ip: string; + city: string; + region: string; + country_name: string; + }, + ) => string; } = { transporter: null, init: async function () { @@ -78,25 +95,109 @@ export const Email: { console.log(`[SMTP] Ready`); }); }, - sendVerificationEmail: async function ( - id: string, - email: string, - ): Promise { - if (!this.transporter) return; + /** + * Replaces all placeholders in an email template with the correct values + */ + doReplacements: function ( + template: string, + user: User, + emailVerificationUrl?: string, + passwordResetUrl?: string, + ipInfo?: { + ip: string; + city: string; + region: string; + country_name: string; + }, + ) { + const { instanceName } = Config.get().general; + template = template.replaceAll("{instanceName}", instanceName); + template = template.replaceAll("{userUsername}", user.username); + template = template.replaceAll( + "{userDiscriminator}", + user.discriminator, + ); + template = template.replaceAll("{userId}", user.id); + if (user.phone) + template = template.replaceAll( + "{phoneNumber}", + user.phone.slice(-4), + ); + if (user.email) + template = template.replaceAll("{userEmail}", user.email); + + // template specific replacements + if (emailVerificationUrl) + template = template.replaceAll( + "{emailVerificationUrl}", + emailVerificationUrl, + ); + if (passwordResetUrl) + template = template.replaceAll( + "{passwordResetUrl}", + passwordResetUrl, + ); + if (ipInfo) { + template = template.replaceAll("{ipAddress}", ipInfo.ip); + template = template.replaceAll("{locationCity}", ipInfo.city); + template = template.replaceAll("{locationRegion}", ipInfo.region); + template = template.replaceAll( + "{locationCountryName}", + ipInfo.country_name, + ); + } + + return template; + }, + /** + * + * @param id user id + * @param email user email + * @returns a verification link for the user + */ + generateVerificationLink: async function (id: string, email: string) { const token = (await generateToken(id, email)) as string; const instanceUrl = Config.get().general.frontPage || "http://localhost:3001"; const link = `${instanceUrl}/verify#token=${token}`; + return link; + }, + sendVerificationEmail: async function ( + user: User, + email: string, + ): Promise { + if (!this.transporter) return; + + // generate a verification link for the user + const verificationLink = await this.generateVerificationLink( + user.id, + email, + ); + // load the email template + const rawTemplate = fs.readFileSync( + path.join( + ASSET_FOLDER_PATH, + "email_templates", + "verify_email.html", + ), + { encoding: "utf-8" }, + ); + // replace email template placeholders + const html = this.doReplacements(rawTemplate, user, verificationLink); + + // extract the title from the email template to use as the email subject + const subject = html.match(/(.*)<\/title>/)?.[1] || ""; + + // // construct the email const message = { from: Config.get().general.correspondenceEmail || "noreply@localhost", to: email, - subject: `Verify Email Address for ${ - Config.get().general.instanceName - }`, - html: `Please verify your email address by clicking the following link: <a href="${link}">Verify Email</a>`, + subject, + html, }; + // // send the email return this.transporter.sendMail(message); }, }; -- cgit 1.4.1