Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Commit 27f57d5

Browse files
✨ Add JWT email verification
1 parent 65c9b3e commit 27f57d5

File tree

10 files changed

+67
-23
lines changed

10 files changed

+67
-23
lines changed

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const DB_PASSWORD = process.env.DB_PASSWORD || "";
1414
export const DB_DATABASE = process.env.DB_DATABASE || "database";
1515

1616
// Email
17+
export const FRONTEND_URL = process.env.FRONTEND_URL || "https://example.com";
1718
export const SES_EMAIL = process.env.SES_EMAIL || "";
1819
export const SES_REGION = process.env.SES_REGION || "eu-west-1";
1920
export const SES_ACCESS = process.env.SES_ACCESS || "";

src/crud/email.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { query, tableValues } from "../helpers/mysql";
1+
import { query, tableValues, setValues } from "../helpers/mysql";
22
import { Email } from "../interfaces/tables/emails";
3+
import { dateToDateTime } from "../helpers/utils";
4+
import { KeyValue } from "../interfaces/general";
35

46
export const createEmail = async (email: Email, sendVerification = true) => {
57
// Clean up values
@@ -13,3 +15,11 @@ export const createEmail = async (email: Email, sendVerification = true) => {
1315
Object.values(email)
1416
);
1517
};
18+
19+
export const updateEmail = async (id: number, email: KeyValue) => {
20+
email.updatedAt = dateToDateTime(new Date());
21+
return await query(`UPDATE emails SET ${setValues(email)} WHERE id = ?`, [
22+
...Object.values(email),
23+
id
24+
]);
25+
};

src/crud/user.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { query, tableValues, setValues } from "../helpers/mysql";
22
import { User } from "../interfaces/tables/user";
33
import { capitalizeFirstAndLastLetter, dateToDateTime } from "../helpers/utils";
44
import { hash } from "bcrypt";
5+
import { KeyValue } from "../interfaces/general";
56

67
export const listAllUsers = async () => {
78
return <User[]>await query("SELECT * from users");
@@ -27,12 +28,8 @@ export const createUser = async (user: User) => {
2728
);
2829
};
2930

30-
interface KV {
31-
[index: string]: any;
32-
}
33-
export const updateUser = async (id: number, user: KV) => {
31+
export const updateUser = async (id: number, user: KeyValue) => {
3432
user.updatedAt = dateToDateTime(new Date());
35-
// Create user
3633
return await query(`UPDATE users SET ${setValues(user)} WHERE id = ?`, [
3734
...Object.values(user),
3835
id

src/helpers/jwt.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { sign, Secret } from "jsonwebtoken";
1+
import { sign, verify } from "jsonwebtoken";
22
import { JWT_ISSUER, JWT_SECRET } from "../config";
33

44
export const generateToken = (
@@ -22,5 +22,13 @@ export const generateToken = (
2222
);
2323
});
2424

25-
export const emailVerificationToken = async (id: number) =>
25+
export const verifyToken = (token: string, subject: string) =>
26+
new Promise((resolve, reject) => {
27+
verify(token, JWT_SECRET, { subject }, (error, data) => {
28+
if (error) return reject(error);
29+
resolve(data);
30+
});
31+
});
32+
33+
export const emailVerificationToken = (id: number) =>
2634
generateToken({ id }, "7d", "email-verify");

src/helpers/mail.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import * as ses from "node-ses";
22
import { Mail } from "../interfaces/mail";
3-
import { SES_SECRET, SES_ACCESS, SES_EMAIL, SES_REGION } from "../config";
3+
import {
4+
SES_SECRET,
5+
SES_ACCESS,
6+
SES_EMAIL,
7+
SES_REGION,
8+
FRONTEND_URL
9+
} from "../config";
410
import { readFile } from "fs-extra";
511
import { join } from "path";
612
import { render } from "mustache";
@@ -30,7 +36,7 @@ export const mail = async (
3036
(await readFile(
3137
join(__dirname, "..", "..", "src", "templates", `${template}.md`)
3238
)).toString(),
33-
data
39+
{ ...data, frontendUrl: FRONTEND_URL }
3440
);
3541
const message = marked(altText);
3642
return await sendMail({

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express from "express";
22
import { PORT } from "./config";
3-
import { register } from "./rest/auth";
3+
import { register, verifyEmail } from "./rest/auth";
44

55
const app = express();
66

@@ -17,5 +17,13 @@ app.get("/create-account", async (req, res) => {
1717
res.json({ success: false });
1818
}
1919
});
20+
app.get("/verify-email/:token", async (req, res) => {
21+
try {
22+
res.json({ success: await verifyEmail(req.params.token) });
23+
} catch (error) {
24+
console.log("Error", error);
25+
res.json({ success: false });
26+
}
27+
});
2028

2129
app.listen(PORT, () => console.log("App running"));
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export interface KeyValue {
2+
[index: string]: any;
3+
}
4+
15
export interface HTTPError {
26
status: number;
37
code: string;

src/rest/auth.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { User } from "../interfaces/tables/user";
22
import { createUser, updateUser } from "../crud/user";
33
import { InsertResult } from "../interfaces/mysql";
4-
import { createEmail } from "../crud/email";
4+
import { createEmail, updateEmail } from "../crud/email";
55
import { mail } from "../helpers/mail";
6-
import { emailVerificationToken } from "../helpers/jwt";
6+
import { emailVerificationToken, verifyToken } from "../helpers/jwt";
7+
import { KeyValue } from "../interfaces/general";
78

89
export const register = async (
910
user: User,
@@ -31,7 +32,12 @@ export const sendEmailVerification = async (
3132
email: string,
3233
user: User
3334
) => {
34-
const token = emailVerificationToken(id);
35-
await mail(email, "verify-email", { name: user.name, email, token });
35+
const token = await emailVerificationToken(id);
36+
await mail(email, "email-verify", { name: user.name, email, token });
3637
return;
3738
};
39+
40+
export const verifyEmail = async (token: string) => {
41+
const emailId = (<KeyValue>await verifyToken(token, "email-verify")).id;
42+
return await updateEmail(emailId, { isVerified: true });
43+
};

src/templates/email-verify.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Verify your email
2+
3+
Dear {{name}},
4+
5+
Click on the following button to verify this email for your account.
6+
7+
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/verify-email?token={{token}}">Verify your email</a>
8+
9+
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/verify-email?token={{token}}.
10+
11+
If you didn't request this email, you can safely ignore it.
12+

src/templates/verify-email.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)