Fix template rendering and use verify email template

email html is weird, some stuff isn't supported.
This commit is contained in:
Puyodead1 2023-01-20 10:43:06 -05:00 committed by Puyodead1
parent f337f2e785
commit 689b710c9e
8 changed files with 421 additions and 323 deletions

View File

@ -4,39 +4,25 @@
<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>
<div style="background-color: #202225;">
<img
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
alt="Branding"
@ -54,7 +40,7 @@
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: rgba(50, 53, 59, 1);
background-color: #32353b;
border-radius: 5px;
"
>
@ -66,39 +52,52 @@
line-height: 24px;
"
>
Hey {username},
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.
account. If this isn't you, we suggest changing your
password as soon as possible.
</p>
<p>
<strong>IP Address:</strong> {ip}
<strong>IP Address:</strong> {ipAddress}
<br />
<strong>Location:</strong> {location}
<strong>Location:</strong> {locationCity}, {locationRegion},
{locationCountryName}
</p>
<div>
<div
style="
display: flex;
text-align: center;
justify-content: center;
padding-bottom: 10px;
"
>
<a class="btn" href="{verifyUrl}" target="_blank"
<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="
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
justify-content: center;
padding-bottom: 10px;
"
>
<p>
@ -109,5 +108,6 @@
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -4,21 +4,25 @@
<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;
}
body {
color: white;
font-family: Arial, Helvetica, sans-serif;
background-color: #202225;
}
p {
color: white;
}
.ExternalClass {
width: 100%;
}
</style>
</head>
<body>
<div style="background-color: #202225;">
<img
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
alt="Branding"
@ -36,7 +40,7 @@
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: rgba(50, 53, 59, 1);
background-color: #32353b;
border-radius: 5px;
"
>
@ -48,7 +52,7 @@
line-height: 24px;
"
>
Hey {username},
Hey {userUsername},
</p>
<p>Your {instanceName} password has been changed.</p>
<p>
@ -56,5 +60,6 @@
password to your {instanceName} account.
</p>
</div>
</div>
</body>
</html>

View File

@ -4,39 +4,25 @@
<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>
<div style="background-color: #202225;">
<img
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
alt="Branding"
@ -54,7 +40,7 @@
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: rgba(50, 53, 59, 1);
background-color: #32353b;
border-radius: 5px;
"
>
@ -66,34 +52,40 @@
line-height: 24px;
"
>
Hey {username},
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.
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;
text-align: center;
justify-content: center;
padding-bottom: 10px;
"
>
<a class="btn" href="{passwordResetUrl}" target="_blank"
<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="
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
"
>
<div style="text-align: center">
<p>
Alternatively, you can directly paste this link into
your browser:
@ -104,5 +96,6 @@
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -4,21 +4,25 @@
<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;
}
body {
color: white;
font-family: Arial, Helvetica, sans-serif;
background-color: #202225;
}
p {
color: white;
}
.ExternalClass {
width: 100%;
}
</style>
</head>
<body>
<div style="background-color: #202225;">
<img
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
alt="Branding"
@ -36,7 +40,7 @@
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: rgba(50, 53, 59, 1);
background-color: #32353b;
border-radius: 5px;
"
>
@ -48,7 +52,7 @@
line-height: 24px;
"
>
Hey {username},
Hey {userUsername},
</p>
<p>
Your phone number ********{phoneNumber} was recently removed
@ -60,5 +64,6 @@
{instanceName} account at a time.
</p>
</div>
</div>
</body>
</html>

View File

@ -4,39 +4,25 @@
<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>
<div style="background-color: #202225;">
<img
src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg"
alt="Branding"
@ -54,7 +40,7 @@
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: rgba(50, 53, 59, 1);
background-color: #32353b;
border-radius: 5px;
"
>
@ -66,43 +52,51 @@
line-height: 24px;
"
>
Hey {username},
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:
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;
text-align: center;
justify-content: center;
padding-bottom: 10px;
"
>
<a class="btn" href="{verificationUrl}" target="_blank"
<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="
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
"
>
<div style="text-align: center">
<p>
Alternatively, you can directly paste this link into
your browser:
</p>
<a href="{verificationUrl}" target="_blank"
>{verificationUrl}</a
<a href="{emailVerificationUrl}" target="_blank"
>{emailVerificationUrl}</a
>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -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);

View File

@ -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);
})

View File

@ -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);
},
};