Fix template rendering and use verify email template
email html is weird, some stuff isn't supported.
This commit is contained in:
parent
f337f2e785
commit
689b710c9e
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
})
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user