diff options
author | Puyodead1 <puyodead@proton.me> | 2023-01-20 10:43:06 -0500 |
---|---|---|
committer | Puyodead1 <puyodead@protonmail.com> | 2023-02-23 21:35:54 -0500 |
commit | 689b710c9e61646e62d3aea21d616402665d2f66 (patch) | |
tree | 6a63cf20ae97601bd2ee90067c47f358a0dd2ad0 | |
parent | Add other email templates (diff) | |
download | server-689b710c9e61646e62d3aea21d616402665d2f66.tar.xz |
Fix template rendering and use verify email template
email html is weird, some stuff isn't supported.
-rw-r--r-- | assets/email_templates/new_login_location.html | 162 | ||||
-rw-r--r-- | assets/email_templates/password_changed.html | 77 | ||||
-rw-r--r-- | assets/email_templates/password_reset_request.html | 145 | ||||
-rw-r--r-- | assets/email_templates/phone_removed.html | 85 | ||||
-rw-r--r-- | assets/email_templates/verify_email.html | 146 | ||||
-rw-r--r-- | src/api/routes/auth/verify/resend.ts | 2 | ||||
-rw-r--r-- | src/util/entities/User.ts | 2 | ||||
-rw-r--r-- | 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 @@ <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html charset=UTF-8" /> <title>Verify {instanceName} Login from New Location</title> <style> * { font-size: 16px; line-height: 24px; - } - body { - color: white; font-family: Arial, Helvetica, sans-serif; - background-color: #202225; } - .btn { - font-size: 15px; - border: none; - border-radius: 3px; - text-decoration: none; + p { color: white; - cursor: pointer; - padding: 15px 19px; - background-color: #ff5f00; - border-radius: 5px; - box-shadow: 0 0 10px rgba(255, 61, 0, 0.1); } - .btn:hover { - background-color: hsl(22.4, 80%, 50%); - } - .btn:active { - background-color: hsl(22.4, 60%, 50%); + .ExternalClass { + width: 100%; } </style> </head> <body> - <img - src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" - alt="Branding" - style=" - width: 100%; - max-width: 200px; - margin: 0 auto; - display: block; - padding: 20px; - " - /> - <div - style=" - width: 100%; - max-width: 500px; - margin: 0 auto; - padding: 40px 50px; - background-color: rgba(50, 53, 59, 1); - border-radius: 5px; - " - > - <p + <div style="background-color: #202225;"> + <img + src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" + alt="Branding" style=" - font-weight: 600; - font-size: 20px; - letter-spacing: 0.27px; - line-height: 24px; + width: 100%; + max-width: 200px; + margin: 0 auto; + display: block; + padding: 20px; + " + /> + <div + style=" + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 40px 50px; + background-color: #32353b; + border-radius: 5px; " > - Hey {username}, - </p> - <p> - 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. - </p> - <p> - <strong>IP Address:</strong> {ip} - <br /> - <strong>Location:</strong> {location} - </p> - <div> - <div + <p style=" - display: flex; - justify-content: center; - padding-bottom: 10px; + font-weight: 600; + font-size: 20px; + letter-spacing: 0.27px; + line-height: 24px; " > - <a class="btn" href="{verifyUrl}" target="_blank" - >Verify Login</a + Hey {userUsername}, + </p> + <p> + 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. + </p> + <p> + <strong>IP Address:</strong> {ipAddress} + <br /> + <strong>Location:</strong> {locationCity}, {locationRegion}, + {locationCountryName} + </p> + <div> + <div + style=" + text-align: center; + justify-content: center; + padding-bottom: 10px; + " > - </div> - <hr /> - <div - style=" - display: flex; - justify-content: center; - flex-direction: column; - text-align: center; - " - > - <p> - Alternatively, you can directly paste this link into - your browser: - </p> - <a href="{verifyUrl}" target="_blank">{verifyUrl}</a> + <a + href="{verifyUrl}" + target="_blank" + style=" + font-size: 15px; + border: none; + border-radius: 3px; + text-decoration: none; + color: white; + cursor: pointer; + padding: 15px 19px; + background-color: #ff5f00; + border-radius: 5px; + " + >Verify Login</a + > + </div> + <hr /> + <div + style=" + text-align: center; + justify-content: center; + padding-bottom: 10px; + " + > + <p> + Alternatively, you can directly paste this link into + your browser: + </p> + <a href="{verifyUrl}" target="_blank">{verifyUrl}</a> + </div> </div> </div> </div> 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 @@ <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html charset=UTF-8" /> <title>{instanceName} Password Changed</title> <style> * { font-size: 16px; line-height: 24px; + font-family: Arial, Helvetica, sans-serif; } - body { + p { color: white; - font-family: Arial, Helvetica, sans-serif; - background-color: #202225; + } + .ExternalClass { + width: 100%; } </style> </head> <body> - <img - src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" - alt="Branding" - style=" - width: 100%; - max-width: 200px; - margin: 0 auto; - display: block; - padding: 20px; - " - /> - <div - style=" - width: 100%; - max-width: 500px; - margin: 0 auto; - padding: 40px 50px; - background-color: rgba(50, 53, 59, 1); - border-radius: 5px; - " - > - <p + <div style="background-color: #202225;"> + <img + src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" + alt="Branding" + style=" + width: 100%; + max-width: 200px; + margin: 0 auto; + display: block; + padding: 20px; + " + /> + <div style=" - font-weight: 600; - font-size: 20px; - letter-spacing: 0.27px; - line-height: 24px; + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 40px 50px; + background-color: #32353b; + border-radius: 5px; " > - Hey {username}, - </p> - <p>Your {instanceName} password has been changed.</p> - <p> - If this wasn't done by you, please immediately reset the - password to your {instanceName} account. - </p> + <p + style=" + font-weight: 600; + font-size: 20px; + letter-spacing: 0.27px; + line-height: 24px; + " + > + Hey {userUsername}, + </p> + <p>Your {instanceName} password has been changed.</p> + <p> + If this wasn't done by you, please immediately reset the + password to your {instanceName} account. + </p> + </div> </div> </body> </html> 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 @@ <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html charset=UTF-8" /> <title>Password Reset Request for {instanceName}</title> <style> * { font-size: 16px; line-height: 24px; - } - body { - color: white; font-family: Arial, Helvetica, sans-serif; - background-color: #202225; } - .btn { - font-size: 15px; - border: none; - border-radius: 3px; - text-decoration: none; + p { color: white; - cursor: pointer; - padding: 15px 19px; - background-color: #ff5f00; - border-radius: 5px; - box-shadow: 0 0 10px rgba(255, 61, 0, 0.1); } - .btn:hover { - background-color: hsl(22.4, 80%, 50%); - } - .btn:active { - background-color: hsl(22.4, 60%, 50%); + .ExternalClass { + width: 100%; } </style> </head> <body> - <img - src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" - alt="Branding" - style=" - width: 100%; - max-width: 200px; - margin: 0 auto; - display: block; - padding: 20px; - " - /> - <div - style=" - width: 100%; - max-width: 500px; - margin: 0 auto; - padding: 40px 50px; - background-color: rgba(50, 53, 59, 1); - border-radius: 5px; - " - > - <p + <div style="background-color: #202225;"> + <img + src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" + alt="Branding" style=" - font-weight: 600; - font-size: 20px; - letter-spacing: 0.27px; - line-height: 24px; + width: 100%; + max-width: 200px; + margin: 0 auto; + display: block; + padding: 20px; + " + /> + <div + style=" + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 40px 50px; + background-color: #32353b; + border-radius: 5px; " > - Hey {username}, - </p> - <p> - Your {instanceName} password can be reset by clicking the button - below. If you did not request a new password, please ignore this - email. - </p> - <div> - <div - style=" - display: flex; - justify-content: center; - padding-bottom: 10px; - " - > - <a class="btn" href="{passwordResetUrl}" target="_blank" - >Reset Password</a - > - </div> - <hr /> - <div + <p style=" - display: flex; - justify-content: center; - flex-direction: column; - text-align: center; + font-weight: 600; + font-size: 20px; + letter-spacing: 0.27px; + line-height: 24px; " > - <p> - Alternatively, you can directly paste this link into - your browser: - </p> - <a href="{passwordResetUrl}" target="_blank" - >{passwordResetUrl}</a + Hey {userUsername}, + </p> + <p> + Your {instanceName} password can be reset by clicking the + button below. If you did not request a new password, please + ignore this email. + </p> + <div> + <div + style=" + text-align: center; + justify-content: center; + padding-bottom: 10px; + " > + <a + href="{passwordResetUrl}" + target="_blank" + style=" + font-size: 15px; + border: none; + border-radius: 3px; + text-decoration: none; + color: white; + cursor: pointer; + padding: 15px 19px; + background-color: #ff5f00; + border-radius: 5px; + " + >Reset Password</a + > + </div> + <hr /> + <div style="text-align: center"> + <p> + Alternatively, you can directly paste this link into + your browser: + </p> + <a href="{passwordResetUrl}" target="_blank" + >{passwordResetUrl}</a + > + </div> </div> </div> </div> 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 @@ <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html charset=UTF-8" /> <title>Phone Removed From {instanceName} Account</title> <style> * { font-size: 16px; line-height: 24px; + font-family: Arial, Helvetica, sans-serif; } - body { + p { color: white; - font-family: Arial, Helvetica, sans-serif; - background-color: #202225; + } + .ExternalClass { + width: 100%; } </style> </head> <body> - <img - src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" - alt="Branding" - style=" - width: 100%; - max-width: 200px; - margin: 0 auto; - display: block; - padding: 20px; - " - /> - <div - style=" - width: 100%; - max-width: 500px; - margin: 0 auto; - padding: 40px 50px; - background-color: rgba(50, 53, 59, 1); - border-radius: 5px; - " - > - <p + <div style="background-color: #202225;"> + <img + src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" + alt="Branding" + style=" + width: 100%; + max-width: 200px; + margin: 0 auto; + display: block; + padding: 20px; + " + /> + <div style=" - font-weight: 600; - font-size: 20px; - letter-spacing: 0.27px; - line-height: 24px; + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 40px 50px; + background-color: #32353b; + border-radius: 5px; " > - Hey {username}, - </p> - <p> - Your phone number ********{phoneNumber} was recently removed - from this account and added to a different {instanceName} - account. - </p> - <p> - Please note that your phone number can only be linked to one - {instanceName} account at a time. - </p> + <p + style=" + font-weight: 600; + font-size: 20px; + letter-spacing: 0.27px; + line-height: 24px; + " + > + Hey {userUsername}, + </p> + <p> + Your phone number ********{phoneNumber} was recently removed + from this account and added to a different {instanceName} + account. + </p> + <p> + Please note that your phone number can only be linked to one + {instanceName} account at a time. + </p> + </div> </div> </body> </html> 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 @@ <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html charset=UTF-8" /> <title>Verify Email Address for {instanceName}</title> <style> * { font-size: 16px; line-height: 24px; - } - body { - color: white; font-family: Arial, Helvetica, sans-serif; - background-color: #202225; } - .btn { - font-size: 15px; - border: none; - border-radius: 3px; - text-decoration: none; + p { color: white; - cursor: pointer; - padding: 15px 19px; - background-color: #ff5f00; - border-radius: 5px; - box-shadow: 0 0 10px rgba(255, 61, 0, 0.1); } - .btn:hover { - background-color: hsl(22.4, 80%, 50%); - } - .btn:active { - background-color: hsl(22.4, 60%, 50%); + .ExternalClass { + width: 100%; } </style> </head> <body> - <img - src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" - alt="Branding" - style=" - width: 100%; - max-width: 200px; - margin: 0 auto; - display: block; - padding: 20px; - " - /> - <div - style=" - width: 100%; - max-width: 500px; - margin: 0 auto; - padding: 40px 50px; - background-color: rgba(50, 53, 59, 1); - border-radius: 5px; - " - > - <p + <div style="background-color: #202225;"> + <img + src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg" + alt="Branding" style=" - font-weight: 600; - font-size: 20px; - letter-spacing: 0.27px; - line-height: 24px; + width: 100%; + max-width: 200px; + margin: 0 auto; + display: block; + padding: 20px; + " + /> + <div + style=" + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 40px 50px; + background-color: #32353b; + border-radius: 5px; " > - Hey {username}, - </p> - <p> - 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: - </p> - <div> - <div - style=" - display: flex; - justify-content: center; - padding-bottom: 10px; - " - > - <a class="btn" href="{verificationUrl}" target="_blank" - >Verify Email</a - > - </div> - <hr /> - <div + <p style=" - display: flex; - justify-content: center; - flex-direction: column; - text-align: center; + font-weight: 600; + font-size: 20px; + letter-spacing: 0.27px; + line-height: 24px; " > - <p> - Alternatively, you can directly paste this link into - your browser: - </p> - <a href="{verificationUrl}" target="_blank" - >{verificationUrl}</a + Hey {userUsername}, + </p> + <p> + 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: + </p> + <div> + <div + style=" + text-align: center; + justify-content: center; + padding-bottom: 10px; + " > + <a + class="btn" + href="{emailVerificationUrl}" + target="_blank" + style=" + font-size: 15px; + border: none; + border-radius: 3px; + text-decoration: none; + color: white; + cursor: pointer; + padding: 15px 19px; + background-color: #ff5f00; + border-radius: 5px; + " + >Verify Email</a + > + </div> + <hr /> + <div style="text-align: center"> + <p> + Alternatively, you can directly paste this link into + your browser: + </p> + <a href="{emailVerificationUrl}" target="_blank" + >{emailVerificationUrl}</a + > + </div> </div> </div> </div> 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 <https://www.gnu.org/licenses/>. */ +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<void>; - sendVerificationEmail: (id: string, email: string) => Promise<any>; + generateVerificationLink: (id: string, email: string) => Promise<string>; + sendVerificationEmail: (user: User, email: string) => Promise<any>; + 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<any> { - 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<any> { + 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>(.*)<\/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); }, }; |