Merge pull request #369 from TNG-dev:zkldi/issue-337-Require-email-verification-for-account-creation

Add email verification endpoints and general email verification.
This commit is contained in:
zkldi
2021-10-14 04:15:30 +01:00
committed by GitHub
4 changed files with 121 additions and 1 deletions
+3
View File
@@ -184,6 +184,8 @@ const db = {
"class-achievements": monkDB.get<ClassAchievementDocument>("class-achievements"),
"score-blacklist":
monkDB.get<{ scoreID: string; userID: integer; score: ScoreDocument }>("score-blacklist"),
"verify-email-codes":
monkDB.get<{ userID: integer; code: string; email: string }>("verify-email-codes"),
};
export type StaticDatabases =
@@ -218,6 +220,7 @@ export type StaticDatabases =
| "user-settings"
| "counters"
| "score-blacklist"
| "verify-email-codes"
| "class-achievements";
export type Databases = StaticDatabases | `songs-${Game}` | `charts-${Game}`;
+6
View File
@@ -9,6 +9,12 @@ export function EmailFormatResetPassword(username: string, resetCode: string, ip
);
}
export function EmailFormatVerifyEmail(username: string, code: string) {
return MainHTMLWrapper(
`Hey ${username}, You need to verify your email before you can use the site.<br/><a href="${ServerConfig.OUR_URL}/verify-email?code=${code}">Click here</a> to verify your email.`
);
}
export function MainHTMLWrapper(innerHTML: string) {
return `<!DOCTYPE html>
<html lang="en">
+84 -1
View File
@@ -23,7 +23,7 @@ import CreateLogCtx from "lib/logger/logger";
import prValidate from "server/middleware/prudence-validate";
import { DecrementCounterValue, GetNextCounterValue } from "utils/db";
import { SendEmail } from "lib/email/client";
import { EmailFormatResetPassword } from "lib/email/formats";
import { EmailFormatResetPassword, EmailFormatVerifyEmail } from "lib/email/formats";
import { Random20Hex } from "utils/misc";
import { ServerConfig } from "lib/setup/config";
@@ -260,6 +260,16 @@ router.post(
MountAuthCookie(req, user!, newSettings);
const resetEmailCode = Random20Hex();
await db["verify-email-codes"].insert({
code: resetEmailCode,
userID: userID,
email: req.body.email,
});
await SendEmail(req.body.email, EmailFormatVerifyEmail(user!.username, resetEmailCode));
return res.status(200).json({
success: true,
description: `Successfully created account ${req.body.username}!`,
@@ -282,6 +292,79 @@ router.post(
}
);
/**
* Verifies the provided email according to the code provided.
*
* @param code - The emailCode set in the /register function.
*
* @name POST /api/v1/auth/verify-email
*/
router.post(
"/verify-email",
prValidate({
code: "string",
}),
async (req, res) => {
const code = await db["verify-email-codes"].findOne({
code: req.body.code,
});
if (!code) {
return res.status(400).json({
success: false,
description: `This email code is invalid.`,
});
}
await db["verify-email-codes"].remove({
code: req.body.code,
});
return res.status(200).json({
success: true,
description: `Verified email!`,
body: {},
});
}
);
/**
* Resend a verification email, for when they fall through the
* cracks.
*
* @param email - The email to send a verification email to.
*
* @name POST /api/v1/auth/resend-verify-email
*/
router.post("/resend-verify-email", prValidate({ email: "string" }), async (req, res) => {
// Immediately send a response so the existence of emails
// cannot be timing attacked out.
res.status(200).json({
success: true,
description: `Sent an email if the email address has not been verified.`,
body: {},
});
const verifyInfo = await db["verify-email-codes"].findOne({ email: req.body.email });
if (!verifyInfo) {
logger.warn(
`Attempted to send reset email to ${req.body.email}, but no verifyInfo was set for them.`
);
return;
}
const user = await GetUserWithID(verifyInfo.userID);
if (!user) {
logger.severe(`Email verifyInfo belongs to user that no longer exists?`, verifyInfo);
return;
}
// Send the email again.
await SendEmail(req.body.email, EmailFormatVerifyEmail(user!.username, verifyInfo.code));
});
/**
* Logs out the requesting user.
* @name POST /api/v1/auth/logout
@@ -203,6 +203,34 @@ router.get("/game-stats", async (req, res) => {
});
});
/**
* Returns whether the user has verified their email or not.
* Requires self-key level permissions.
*
* @name GET /api/v1/users/:userID/is-email-verified
*/
router.get("/is-email-verified", RequireSelfRequestFromUser, async (req, res) => {
const user = req[SYMBOL_TachiData]!.requestedUser!;
const verifyInfo = await db["verify-email-codes"].findOne({
userID: user.id,
});
if (verifyInfo) {
return res.status(200).json({
success: true,
description: `User has not verified email.`,
body: false,
});
}
return res.status(200).json({
success: true,
description: `User has verified email.`,
body: true,
});
});
router.use("/games/:game/:playtype", gamePTRouter);
router.use("/pfp", pfpRouter);
router.use("/banner", bannerRouter);