Refactor email sending + remove email verification if mail sending is not set up
This commit is contained in:
parent
ecb227105a
commit
d18584f8e9
@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
@ -13,45 +14,41 @@
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ExternalClass {
|
.ExternalClass {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
|
<body>
|
||||||
<div style="background-color: #202225;">
|
<div style="background-color: #202225;">
|
||||||
<img
|
<img src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
|
||||||
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
|
alt="Branding" style="
|
||||||
alt="Branding"
|
|
||||||
style="
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
"
|
" />
|
||||||
/>
|
<div style="
|
||||||
<div
|
|
||||||
style="
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 40px 50px;
|
padding: 40px 50px;
|
||||||
background-color: #32353b;
|
background-color: #32353b;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
"
|
">
|
||||||
>
|
<p style="
|
||||||
<p
|
|
||||||
style="
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
letter-spacing: 0.27px;
|
letter-spacing: 0.27px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
"
|
">
|
||||||
>
|
|
||||||
Hey {userUsername},
|
Hey {userUsername},
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@ -68,17 +65,12 @@
|
|||||||
{locationCountryName}
|
{locationCountryName}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div style="
|
||||||
style="
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
"
|
">
|
||||||
>
|
<a href="{actionUrl}" target="_blank" style="
|
||||||
<a
|
|
||||||
href="{verifyUrl}"
|
|
||||||
target="_blank"
|
|
||||||
style="
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -88,26 +80,23 @@
|
|||||||
padding: 15px 19px;
|
padding: 15px 19px;
|
||||||
background-color: #0185ff;
|
background-color: #0185ff;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
"
|
">Verify Login</a>
|
||||||
>Verify Login</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div
|
<div style="
|
||||||
style="
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
"
|
">
|
||||||
>
|
|
||||||
<p>
|
<p>
|
||||||
Alternatively, you can directly paste this link into
|
Alternatively, you can directly paste this link into
|
||||||
your browser:
|
your browser:
|
||||||
</p>
|
</p>
|
||||||
<a href="{verifyUrl}" target="_blank" style="word-wrap: break-word;">{verifyUrl}</a>
|
<a href="{actionUrl}" target="_blank" style="word-wrap: break-word;">{actionUrl}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
@ -13,45 +14,41 @@
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ExternalClass {
|
.ExternalClass {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
|
<body>
|
||||||
<div style="background-color: #202225;">
|
<div style="background-color: #202225;">
|
||||||
<img
|
<img src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
|
||||||
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
|
alt="Branding" style="
|
||||||
alt="Branding"
|
|
||||||
style="
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
"
|
" />
|
||||||
/>
|
<div style="
|
||||||
<div
|
|
||||||
style="
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 40px 50px;
|
padding: 40px 50px;
|
||||||
background-color: #32353b;
|
background-color: #32353b;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
"
|
">
|
||||||
>
|
<p style="
|
||||||
<p
|
|
||||||
style="
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
letter-spacing: 0.27px;
|
letter-spacing: 0.27px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
"
|
">
|
||||||
>
|
|
||||||
Hey {userUsername},
|
Hey {userUsername},
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@ -60,17 +57,12 @@
|
|||||||
ignore this email.
|
ignore this email.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div style="
|
||||||
style="
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
"
|
">
|
||||||
>
|
<a href="{actionUrl}" target="_blank" style="
|
||||||
<a
|
|
||||||
href="{passwordResetUrl}"
|
|
||||||
target="_blank"
|
|
||||||
style="
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -80,9 +72,7 @@
|
|||||||
padding: 15px 19px;
|
padding: 15px 19px;
|
||||||
background-color: #ff5f00;
|
background-color: #ff5f00;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
"
|
">Reset Password</a>
|
||||||
>Reset Password</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
@ -90,12 +80,11 @@
|
|||||||
Alternatively, you can directly paste this link into
|
Alternatively, you can directly paste this link into
|
||||||
your browser:
|
your browser:
|
||||||
</p>
|
</p>
|
||||||
<a href="{passwordResetUrl}" target="_blank" style="word-wrap: break-word;"
|
<a href="{actionUrl}" target="_blank" style="word-wrap: break-word;">{actionUrl}</a>
|
||||||
>{passwordResetUrl}</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -18,14 +18,13 @@
|
|||||||
|
|
||||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
adjustEmail,
|
|
||||||
Config,
|
Config,
|
||||||
FieldErrors,
|
FieldErrors,
|
||||||
generateToken,
|
|
||||||
generateWebAuthnTicket,
|
|
||||||
LoginSchema,
|
LoginSchema,
|
||||||
User,
|
User,
|
||||||
WebAuthn,
|
WebAuthn,
|
||||||
|
generateToken,
|
||||||
|
generateWebAuthnTicket,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
@ -50,7 +49,6 @@ router.post(
|
|||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { login, password, captcha_key, undelete } =
|
const { login, password, captcha_key, undelete } =
|
||||||
req.body as LoginSchema;
|
req.body as LoginSchema;
|
||||||
const email = adjustEmail(login);
|
|
||||||
|
|
||||||
const config = Config.get();
|
const config = Config.get();
|
||||||
|
|
||||||
@ -76,7 +74,7 @@ router.post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findOneOrFail({
|
const user = await User.findOneOrFail({
|
||||||
where: [{ phone: login }, { email: email }],
|
where: [{ phone: login }, { email: login }],
|
||||||
select: [
|
select: [
|
||||||
"data",
|
"data",
|
||||||
"id",
|
"id",
|
||||||
|
@ -30,7 +30,6 @@ import {
|
|||||||
RegisterSchema,
|
RegisterSchema,
|
||||||
User,
|
User,
|
||||||
ValidRegistrationToken,
|
ValidRegistrationToken,
|
||||||
adjustEmail,
|
|
||||||
generateToken,
|
generateToken,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
@ -76,9 +75,6 @@ router.post(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
|
|
||||||
const email = adjustEmail(body.email);
|
|
||||||
|
|
||||||
// check if registration is allowed
|
// check if registration is allowed
|
||||||
if (!regTokenUsed && !register.allowNewRegistration) {
|
if (!regTokenUsed && !register.allowNewRegistration) {
|
||||||
throw FieldErrors({
|
throw FieldErrors({
|
||||||
@ -161,6 +157,7 @@ router.post(
|
|||||||
// TODO: gift_code_sku_id?
|
// TODO: gift_code_sku_id?
|
||||||
// TODO: check password strength
|
// TODO: check password strength
|
||||||
|
|
||||||
|
const email = body.email;
|
||||||
if (email) {
|
if (email) {
|
||||||
// replace all dots and chars after +, if its a gmail.com email
|
// replace all dots and chars after +, if its a gmail.com email
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
import { route } from "@spacebar/api";
|
import { route } from "@spacebar/api";
|
||||||
import {
|
import {
|
||||||
adjustEmail,
|
|
||||||
Config,
|
Config,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
FieldErrors,
|
FieldErrors,
|
||||||
@ -111,7 +110,6 @@ router.patch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body.email) {
|
if (body.email) {
|
||||||
body.email = adjustEmail(body.email);
|
|
||||||
if (!body.email && Config.get().register.email.required)
|
if (!body.email && Config.get().register.email.required)
|
||||||
throw FieldErrors({
|
throw FieldErrors({
|
||||||
email: {
|
email: {
|
||||||
|
@ -25,14 +25,7 @@ import {
|
|||||||
OneToMany,
|
OneToMany,
|
||||||
OneToOne,
|
OneToOne,
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
import {
|
import { Config, Email, FieldErrors, Snowflake, trimSpecial } from "..";
|
||||||
Config,
|
|
||||||
Email,
|
|
||||||
FieldErrors,
|
|
||||||
Snowflake,
|
|
||||||
adjustEmail,
|
|
||||||
trimSpecial,
|
|
||||||
} from "..";
|
|
||||||
import { BitField } from "../util/BitField";
|
import { BitField } from "../util/BitField";
|
||||||
import { BaseClass } from "./BaseClass";
|
import { BaseClass } from "./BaseClass";
|
||||||
import { ConnectedAccount } from "./ConnectedAccount";
|
import { ConnectedAccount } from "./ConnectedAccount";
|
||||||
@ -240,18 +233,6 @@ export class User extends BaseClass {
|
|||||||
|
|
||||||
// TODO: I don't like this method?
|
// TODO: I don't like this method?
|
||||||
validate() {
|
validate() {
|
||||||
if (this.email) {
|
|
||||||
this.email = adjustEmail(this.email);
|
|
||||||
if (!this.email)
|
|
||||||
throw FieldErrors({
|
|
||||||
email: { message: "Invalid email", code: "EMAIL_INVALID" },
|
|
||||||
});
|
|
||||||
if (!this.email.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g))
|
|
||||||
throw FieldErrors({
|
|
||||||
email: { message: "Invalid email", code: "EMAIL_INVALID" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.discriminator) {
|
if (this.discriminator) {
|
||||||
const discrim = Number(this.discriminator);
|
const discrim = Number(this.discriminator);
|
||||||
if (
|
if (
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from "node:fs";
|
import fs from "fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { SentMessageInfo, Transporter } from "nodemailer";
|
import { SentMessageInfo, Transporter } from "nodemailer";
|
||||||
import { User } from "../../entities";
|
import { User } from "../../entities";
|
||||||
@ -24,8 +24,8 @@ import { Config } from "../Config";
|
|||||||
import { generateToken } from "../Token";
|
import { generateToken } from "../Token";
|
||||||
import MailGun from "./transports/MailGun";
|
import MailGun from "./transports/MailGun";
|
||||||
import MailJet from "./transports/MailJet";
|
import MailJet from "./transports/MailJet";
|
||||||
import SendGrid from "./transports/SendGrid";
|
|
||||||
import SMTP from "./transports/SMTP";
|
import SMTP from "./transports/SMTP";
|
||||||
|
import SendGrid from "./transports/SendGrid";
|
||||||
|
|
||||||
const ASSET_FOLDER_PATH = path.join(
|
const ASSET_FOLDER_PATH = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
@ -35,32 +35,11 @@ const ASSET_FOLDER_PATH = path.join(
|
|||||||
"..",
|
"..",
|
||||||
"assets",
|
"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,}))$/;
|
|
||||||
|
|
||||||
export function adjustEmail(email?: string): string | undefined {
|
enum MailTypes {
|
||||||
if (!email) return email;
|
verify = "verify",
|
||||||
// body parser already checked if it is a valid email
|
reset = "reset",
|
||||||
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
|
pwchange = "pwchange",
|
||||||
if (!parts || parts.length < 5) return undefined;
|
|
||||||
|
|
||||||
return email;
|
|
||||||
// // TODO: The below code doesn't actually do anything.
|
|
||||||
// const domain = parts[5];
|
|
||||||
// const user = parts[1];
|
|
||||||
|
|
||||||
// // TODO: check accounts with uncommon email domains
|
|
||||||
// if (domain === "gmail.com" || domain === "googlemail.com") {
|
|
||||||
// // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
|
|
||||||
// const v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (domain === "google.com") {
|
|
||||||
// // replace .dots and +alternatives -> Google Staff GMail Dot Trick
|
|
||||||
// const v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return email;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const transporters: {
|
const transporters: {
|
||||||
@ -76,10 +55,15 @@ export const Email: {
|
|||||||
transporter: Transporter | null;
|
transporter: Transporter | null;
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
generateLink: (
|
generateLink: (
|
||||||
type: "verify" | "reset",
|
type: Omit<MailTypes, "pwchange">,
|
||||||
id: string,
|
id: string,
|
||||||
email: string,
|
email: string,
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
|
sendMail: (
|
||||||
|
type: MailTypes,
|
||||||
|
user: User,
|
||||||
|
email: string,
|
||||||
|
) => Promise<SentMessageInfo>;
|
||||||
sendVerifyEmail: (user: User, email: string) => Promise<SentMessageInfo>;
|
sendVerifyEmail: (user: User, email: string) => Promise<SentMessageInfo>;
|
||||||
sendResetPassword: (user: User, email: string) => Promise<SentMessageInfo>;
|
sendResetPassword: (user: User, email: string) => Promise<SentMessageInfo>;
|
||||||
sendPasswordChanged: (
|
sendPasswordChanged: (
|
||||||
@ -89,8 +73,7 @@ export const Email: {
|
|||||||
doReplacements: (
|
doReplacements: (
|
||||||
template: string,
|
template: string,
|
||||||
user: User,
|
user: User,
|
||||||
emailVerificationUrl?: string,
|
actionUrl?: string,
|
||||||
passwordResetUrl?: string,
|
|
||||||
ipInfo?: {
|
ipInfo?: {
|
||||||
ip: string;
|
ip: string;
|
||||||
city: string;
|
city: string;
|
||||||
@ -119,8 +102,7 @@ export const Email: {
|
|||||||
doReplacements: function (
|
doReplacements: function (
|
||||||
template,
|
template,
|
||||||
user,
|
user,
|
||||||
emailVerificationUrl?,
|
actionUrl?,
|
||||||
passwordResetUrl?,
|
|
||||||
ipInfo?: {
|
ipInfo?: {
|
||||||
ip: string;
|
ip: string;
|
||||||
city: string;
|
city: string;
|
||||||
@ -137,8 +119,7 @@ export const Email: {
|
|||||||
["{userId}", user.id],
|
["{userId}", user.id],
|
||||||
["{phoneNumber}", user.phone?.slice(-4)],
|
["{phoneNumber}", user.phone?.slice(-4)],
|
||||||
["{userEmail}", user.email],
|
["{userEmail}", user.email],
|
||||||
["{emailVerificationUrl}", emailVerificationUrl],
|
["{actionUrl}", actionUrl],
|
||||||
["{passwordResetUrl}", passwordResetUrl],
|
|
||||||
["{ipAddress}", ipInfo?.ip],
|
["{ipAddress}", ipInfo?.ip],
|
||||||
["{locationCity}", ipInfo?.city],
|
["{locationCity}", ipInfo?.city],
|
||||||
["{locationRegion}", ipInfo?.region],
|
["{locationRegion}", ipInfo?.region],
|
||||||
@ -165,32 +146,45 @@ export const Email: {
|
|||||||
const link = `${instanceUrl}/${type}#token=${token}`;
|
const link = `${instanceUrl}/${type}#token=${token}`;
|
||||||
return link;
|
return link;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an email to the user with a link to verify their email address
|
*
|
||||||
|
* @param type the MailType to send
|
||||||
|
* @param user the user to address it to
|
||||||
|
* @param email the email to send it to
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
sendVerifyEmail: async function (user, email) {
|
sendMail: async function (type, user, email) {
|
||||||
if (!this.transporter) return;
|
if (!this.transporter) return;
|
||||||
|
|
||||||
// generate a verification link for the user
|
const templateNames: { [key in MailTypes]: string } = {
|
||||||
const link = await this.generateLink("verify", user.id, email);
|
verify: "verify_email.html",
|
||||||
|
reset: "password_reset_request.html",
|
||||||
|
pwchange: "password_changed.html",
|
||||||
|
};
|
||||||
|
|
||||||
// load the email template
|
const template = await fs.readFile(
|
||||||
const rawTemplate = fs.readFileSync(
|
|
||||||
path.join(
|
path.join(
|
||||||
ASSET_FOLDER_PATH,
|
ASSET_FOLDER_PATH,
|
||||||
"email_templates",
|
"email_templates",
|
||||||
"verify_email.html",
|
templateNames[type],
|
||||||
),
|
),
|
||||||
{ encoding: "utf-8" },
|
{ encoding: "utf-8" },
|
||||||
);
|
);
|
||||||
|
|
||||||
// replace email template placeholders
|
// replace email template placeholders
|
||||||
const html = this.doReplacements(rawTemplate, user, link);
|
const html = this.doReplacements(
|
||||||
|
template,
|
||||||
|
user,
|
||||||
|
// password change emails don't have links
|
||||||
|
type != MailTypes.pwchange
|
||||||
|
? await this.generateLink(type, user.id, email)
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
|
||||||
// extract the title from the email template to use as the email subject
|
// extract the title from the email template to use as the email subject
|
||||||
const subject = html.match(/<title>(.*)<\/title>/)?.[1] || "";
|
const subject = html.match(/<title>(.*)<\/title>/)?.[1] || "";
|
||||||
|
|
||||||
// construct the email
|
|
||||||
const message = {
|
const message = {
|
||||||
from:
|
from:
|
||||||
Config.get().general.correspondenceEmail || "noreply@localhost",
|
Config.get().general.correspondenceEmail || "noreply@localhost",
|
||||||
@ -199,78 +193,25 @@ export const Email: {
|
|||||||
html,
|
html,
|
||||||
};
|
};
|
||||||
|
|
||||||
// send the email
|
|
||||||
return this.transporter.sendMail(message);
|
return this.transporter.sendMail(message);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an email to the user with a link to verify their email address
|
||||||
|
*/
|
||||||
|
sendVerifyEmail: async function (user, email) {
|
||||||
|
return this.sendMail(MailTypes.verify, user, email);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Sends an email to the user with a link to reset their password
|
* Sends an email to the user with a link to reset their password
|
||||||
*/
|
*/
|
||||||
sendResetPassword: async function (user, email) {
|
sendResetPassword: async function (user, email) {
|
||||||
if (!this.transporter) return;
|
return this.sendMail(MailTypes.reset, user, email);
|
||||||
|
|
||||||
// generate a password reset link for the user
|
|
||||||
const link = await this.generateLink("reset", user.id, email);
|
|
||||||
|
|
||||||
// load the email template
|
|
||||||
const rawTemplate = await fs.promises.readFile(
|
|
||||||
path.join(
|
|
||||||
ASSET_FOLDER_PATH,
|
|
||||||
"email_templates",
|
|
||||||
"password_reset_request.html",
|
|
||||||
),
|
|
||||||
{ encoding: "utf-8" },
|
|
||||||
);
|
|
||||||
|
|
||||||
// replace email template placeholders
|
|
||||||
const html = this.doReplacements(rawTemplate, user, undefined, link);
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
html,
|
|
||||||
};
|
|
||||||
|
|
||||||
// send the email
|
|
||||||
return this.transporter.sendMail(message);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sends an email to the user notifying them that their password has been changed
|
* Sends an email to the user notifying them that their password has been changed
|
||||||
*/
|
*/
|
||||||
sendPasswordChanged: async function (user, email) {
|
sendPasswordChanged: async function (user, email) {
|
||||||
if (!this.transporter) return;
|
return this.sendMail(MailTypes.pwchange, user, email);
|
||||||
|
|
||||||
// load the email template
|
|
||||||
const rawTemplate = await fs.promises.readFile(
|
|
||||||
path.join(
|
|
||||||
ASSET_FOLDER_PATH,
|
|
||||||
"email_templates",
|
|
||||||
"password_changed.html",
|
|
||||||
),
|
|
||||||
{ encoding: "utf-8" },
|
|
||||||
);
|
|
||||||
|
|
||||||
// replace email template placeholders
|
|
||||||
const html = this.doReplacements(rawTemplate, user);
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
html,
|
|
||||||
};
|
|
||||||
|
|
||||||
// send the email
|
|
||||||
return this.transporter.sendMail(message);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user