mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-28 09:58:11 +03:00
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:
Vendored
+3
@@ -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}`;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user