diff --git a/backend/api/controllers/user.js b/backend/api/controllers/user.js index 340ece2ce..05706e35b 100644 --- a/backend/api/controllers/user.js +++ b/backend/api/controllers/user.js @@ -14,296 +14,233 @@ const uaparser = require("ua-parser-js"); // import { isUsernameValid } from "../../handlers/validation"; class UserController { - static async createNewUser(req, res, next) { - try { - const { name } = req.body; - const { email, uid } = req.decodedToken; - await UsersDAO.addUser(name, email, uid); - Logger.log("user_created", `${name} ${email}`, uid); - return res.sendStatus(200); - } catch (e) { - return next(e); - } + static async createNewUser(req, res) { + const { name } = req.body; + const { email, uid } = req.decodedToken; + await UsersDAO.addUser(name, email, uid); + Logger.log("user_created", `${name} ${email}`, uid); + return res.sendStatus(200); } - static async deleteUser(req, res, next) { - try { - const { uid } = req.decodedToken; - const userInfo = await UsersDAO.getUser(uid); - await UsersDAO.deleteUser(uid); - Logger.log("user_deleted", `${userInfo.email} ${userInfo.name}`, uid); - return res.sendStatus(200); - } catch (e) { - return next(e); - } + static async deleteUser(req, res) { + const { uid } = req.decodedToken; + const userInfo = await UsersDAO.getUser(uid); + await UsersDAO.deleteUser(uid); + Logger.log("user_deleted", `${userInfo.email} ${userInfo.name}`, uid); + return res.sendStatus(200); } - static async updateName(req, res, next) { + static async updateName(req, res) { + const { uid } = req.decodedToken; + const { name } = req.body; + if (!isUsernameValid(name)) + return res.status(400).json({ + message: + "Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -", + }); + let olduser = await UsersDAO.getUser(uid); + await UsersDAO.updateName(uid, name); + Logger.log( + "user_name_updated", + `changed name from ${olduser.name} to ${name}`, + uid + ); + return res.sendStatus(200); + } + + static async clearPb(req, res) { + const { uid } = req.decodedToken; + await UsersDAO.clearPb(uid); + Logger.log("user_cleared_pbs", "", uid); + return res.sendStatus(200); + } + + static async checkName(req, res) { + const { name } = req.body; + if (!isUsernameValid(name)) + return next({ + status: 400, + message: + "Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -", + }); + const available = await UsersDAO.isNameAvailable(name); + if (!available) + return res.status(400).json({ message: "Username unavailable" }); + return res.sendStatus(200); + } + + static async updateEmail(req, res) { + const { uid } = req.decodedToken; + const { newEmail } = req.body; try { - const { uid } = req.decodedToken; - const { name } = req.body; - if (!isUsernameValid(name)) - return res.status(400).json({ - message: - "Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -", - }); - let olduser = await UsersDAO.getUser(uid); - await UsersDAO.updateName(uid, name); - Logger.log( - "user_name_updated", - `changed name from ${olduser.name} to ${name}`, - uid + await UsersDAO.updateEmail(uid, newEmail); + } catch (e) { + throw new MonkeyError(400, e.message, "update email", uid); + } + Logger.log("user_email_updated", `changed email to ${newEmail}`, uid); + return res.sendStatus(200); + } + + static async getUser(req, res) { + const { email, uid } = req.decodedToken; + let userInfo; + try { + userInfo = await UsersDAO.getUser(uid); + } catch (e) { + if (email && uid) { + userInfo = await UsersDAO.addUser(undefined, email, uid); + } else { + throw new MonkeyError( + 400, + "User not found. Could not recreate user document.", + "Tried to recreate user document but either email or uid is nullish", + uid + ); + } + } + let agent = uaparser(req.headers["user-agent"]); + let logobj = { + ip: + req.headers["cf-connecting-ip"] || + req.headers["x-forwarded-for"] || + req.ip || + "255.255.255.255", + agent: + agent.os.name + + " " + + agent.os.version + + " " + + agent.browser.name + + " " + + agent.browser.version, + }; + if (agent.device.vendor) { + logobj.device = + agent.device.vendor + + " " + + agent.device.model + + " " + + agent.device.type; + } + Logger.log("user_data_requested", logobj, uid); + return res.status(200).json(userInfo); + } + + static async linkDiscord(req, res) { + const { uid } = req.decodedToken; + + let requser; + try { + requser = await UsersDAO.getUser(uid); + } catch (e) { + requser = null; + } + if (requser?.banned === true) { + throw new MonkeyError(403, "Banned accounts cannot link with Discord"); + } + + let discordFetch = await fetch("https://discord.com/api/users/@me", { + headers: { + authorization: `${req.body.data.tokenType} ${req.body.data.accessToken}`, + }, + }); + discordFetch = await discordFetch.json(); + const did = discordFetch.id; + if (!did) { + throw new MonkeyError( + 500, + "Could not get Discord account info", + "did is undefined" ); - return res.sendStatus(200); - } catch (e) { - return next(e); } + let user; + try { + user = await UsersDAO.getUserByDiscordId(did); + } catch (e) { + user = null; + } + if (user !== null) { + throw new MonkeyError( + 400, + "This Discord account is already linked to a different account" + ); + } + await UsersDAO.linkDiscord(uid, did); + await BotDAO.linkDiscord(uid, did); + Logger.log("user_discord_link", `linked to ${did}`, uid); + return res.status(200).json({ + message: "Discord account linked", + did, + }); } - static async clearPb(req, res, next) { + static async unlinkDiscord(req, res) { + const { uid } = req.decodedToken; + let userInfo; try { - const { uid } = req.decodedToken; - await UsersDAO.clearPb(uid); - Logger.log("user_cleared_pbs", "", uid); - return res.sendStatus(200); + userInfo = await UsersDAO.getUser(uid); } catch (e) { - return next(e); + throw new MonkeyError(400, "User not found."); } + if (!userInfo.discordId) { + throw new MonkeyError(400, "User does not have a linked Discord account"); + } + await BotDAO.unlinkDiscord(uid, userInfo.discordId); + await UsersDAO.unlinkDiscord(uid); + Logger.log("user_discord_unlinked", userInfo.discordId, uid); + return res.status(200).send(); } - static async checkName(req, res, next) { - try { - const { name } = req.body; - if (!isUsernameValid(name)) - return next({ - status: 400, - message: - "Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -", - }); - const available = await UsersDAO.isNameAvailable(name); - if (!available) - return res.status(400).json({ message: "Username unavailable" }); - return res.sendStatus(200); - } catch (e) { - return next(e); - } - } - - static async updateEmail(req, res, next) { - try { - const { uid } = req.decodedToken; - const { newEmail } = req.body; - try { - await UsersDAO.updateEmail(uid, newEmail); - } catch (e) { - throw new MonkeyError(400, e.message, "update email", uid); - } - Logger.log("user_email_updated", `changed email to ${newEmail}`, uid); - return res.sendStatus(200); - } catch (e) { - return next(e); - } - } - - static async getUser(req, res, next) { - try { - const { email, uid } = req.decodedToken; - let userInfo; - try { - userInfo = await UsersDAO.getUser(uid); - } catch (e) { - if (email && uid) { - userInfo = await UsersDAO.addUser(undefined, email, uid); - } else { - throw new MonkeyError( - 400, - "User not found. Could not recreate user document.", - "Tried to recreate user document but either email or uid is nullish", - uid - ); - } - } - let agent = uaparser(req.headers["user-agent"]); - let logobj = { - ip: - req.headers["cf-connecting-ip"] || - req.headers["x-forwarded-for"] || - req.ip || - "255.255.255.255", - agent: - agent.os.name + - " " + - agent.os.version + - " " + - agent.browser.name + - " " + - agent.browser.version, - }; - if (agent.device.vendor) { - logobj.device = - agent.device.vendor + - " " + - agent.device.model + - " " + - agent.device.type; - } - Logger.log("user_data_requested", logobj, uid); - return res.status(200).json(userInfo); - } catch (e) { - return next(e); - } - } - - static async linkDiscord(req, res, next) { - try { - const { uid } = req.decodedToken; - - let requser; - try { - requser = await UsersDAO.getUser(uid); - } catch (e) { - requser = null; - } - if (requser?.banned === true) { - throw new MonkeyError(403, "Banned accounts cannot link with Discord"); - } - - let discordFetch = await fetch("https://discord.com/api/users/@me", { - headers: { - authorization: `${req.body.data.tokenType} ${req.body.data.accessToken}`, - }, + static async addTag(req, res) { + const { uid } = req.decodedToken; + const { tagName } = req.body; + if (!isTagPresetNameValid(tagName)) + return res.status(400).json({ + message: + "Tag name invalid. Name cannot contain special characters or more than 16 characters. Can include _ . and -", }); - discordFetch = await discordFetch.json(); - const did = discordFetch.id; - if (!did) { - throw new MonkeyError( - 500, - "Could not get Discord account info", - "did is undefined" - ); - } - let user; - try { - user = await UsersDAO.getUserByDiscordId(did); - } catch (e) { - user = null; - } - if (user !== null) { - throw new MonkeyError( - 400, - "This Discord account is already linked to a different account" - ); - } - await UsersDAO.linkDiscord(uid, did); - await BotDAO.linkDiscord(uid, did); - Logger.log("user_discord_link", `linked to ${did}`, uid); - return res.status(200).json({ - message: "Discord account linked", - did, + let tag = await UsersDAO.addTag(uid, tagName); + return res.status(200).json(tag); + } + + static async clearTagPb(req, res) { + const { uid } = req.decodedToken; + const { tagid } = req.body; + await UsersDAO.removeTagPb(uid, tagid); + return res.sendStatus(200); + } + + static async editTag(req, res) { + const { uid } = req.decodedToken; + const { tagid, newname } = req.body; + if (!isTagPresetNameValid(newname)) + return res.status(400).json({ + message: + "Tag name invalid. Name cannot contain special characters or more than 16 characters. Can include _ . and -", }); - } catch (e) { - return next(e); - } + await UsersDAO.editTag(uid, tagid, newname); + return res.sendStatus(200); } - static async unlinkDiscord(req, res, next) { - try { - const { uid } = req.decodedToken; - let userInfo; - try { - userInfo = await UsersDAO.getUser(uid); - } catch (e) { - throw new MonkeyError(400, "User not found."); - } - if (!userInfo.discordId) { - throw new MonkeyError( - 400, - "User does not have a linked Discord account" - ); - } - await BotDAO.unlinkDiscord(uid, userInfo.discordId); - await UsersDAO.unlinkDiscord(uid); - Logger.log("user_discord_unlinked", userInfo.discordId, uid); - return res.status(200).send(); - } catch (e) { - return next(e); - } + static async removeTag(req, res) { + const { uid } = req.decodedToken; + const { tagid } = req.body; + await UsersDAO.removeTag(uid, tagid); + return res.sendStatus(200); } - static async addTag(req, res, next) { - try { - const { uid } = req.decodedToken; - const { tagName } = req.body; - if (!isTagPresetNameValid(tagName)) - return res.status(400).json({ - message: - "Tag name invalid. Name cannot contain special characters or more than 16 characters. Can include _ . and -", - }); - let tag = await UsersDAO.addTag(uid, tagName); - return res.status(200).json(tag); - } catch (e) { - return next(e); - } + static async getTags(req, res) { + const { uid } = req.decodedToken; + let tags = await UsersDAO.getTags(uid); + if (tags == undefined) tags = []; + return res.status(200).json(tags); } - static async clearTagPb(req, res, next) { - try { - const { uid } = req.decodedToken; - const { tagid } = req.body; - await UsersDAO.removeTagPb(uid, tagid); - return res.sendStatus(200); - } catch (e) { - return next(e); - } - } - - static async editTag(req, res, next) { - try { - const { uid } = req.decodedToken; - const { tagid, newname } = req.body; - if (!isTagPresetNameValid(newname)) - return res.status(400).json({ - message: - "Tag name invalid. Name cannot contain special characters or more than 16 characters. Can include _ . and -", - }); - await UsersDAO.editTag(uid, tagid, newname); - return res.sendStatus(200); - } catch (e) { - return next(e); - } - } - - static async removeTag(req, res, next) { - try { - const { uid } = req.decodedToken; - const { tagid } = req.body; - await UsersDAO.removeTag(uid, tagid); - return res.sendStatus(200); - } catch (e) { - return next(e); - } - } - - static async getTags(req, res, next) { - try { - const { uid } = req.decodedToken; - let tags = await UsersDAO.getTags(uid); - if (tags == undefined) tags = []; - return res.status(200).json(tags); - } catch (e) { - return next(e); - } - } - - static async updateLbMemory(req, res, next) { - try { - const { uid } = req.decodedToken; - const { mode, mode2, language, rank } = req.body; - await UsersDAO.updateLbMemory(uid, mode, mode2, language, rank); - return res.sendStatus(200); - } catch (e) { - return next(e); - } + static async updateLbMemory(req, res) { + const { uid } = req.decodedToken; + const { mode, mode2, language, rank } = req.body; + await UsersDAO.updateLbMemory(uid, mode, mode2, language, rank); + return res.sendStatus(200); } } diff --git a/backend/api/routes/user.js b/backend/api/routes/user.js index 49a807c15..41c14866f 100644 --- a/backend/api/routes/user.js +++ b/backend/api/routes/user.js @@ -2,6 +2,10 @@ const { authenticateRequest } = require("../../middlewares/auth"); const { Router } = require("express"); const UserController = require("../controllers/user"); const RateLimit = require("../../middlewares/rate-limit"); +const { + asyncHandlerWrapper, + requestValidation, +} = require("../../middlewares/api-utils"); const router = Router(); @@ -9,100 +13,104 @@ router.get( "/", RateLimit.userGet, authenticateRequest, - UserController.getUser + asyncHandlerWrapper(UserController.getUser) ); router.post( "/signup", RateLimit.userSignup, authenticateRequest, - UserController.createNewUser + asyncHandlerWrapper(UserController.createNewUser) ); -router.post("/checkName", RateLimit.userCheckName, UserController.checkName); +router.post( + "/checkName", + RateLimit.userCheckName, + asyncHandlerWrapper(UserController.checkName) +); router.post( "/delete", RateLimit.userDelete, authenticateRequest, - UserController.deleteUser + asyncHandlerWrapper(UserController.deleteUser) ); router.post( "/updateName", RateLimit.userUpdateName, authenticateRequest, - UserController.updateName + asyncHandlerWrapper(UserController.updateName) ); router.post( "/updateLbMemory", RateLimit.userUpdateLBMemory, authenticateRequest, - UserController.updateLbMemory + asyncHandlerWrapper(UserController.updateLbMemory) ); router.post( "/updateEmail", RateLimit.userUpdateEmail, authenticateRequest, - UserController.updateEmail + asyncHandlerWrapper(UserController.updateEmail) ); router.post( "/clearPb", RateLimit.userClearPB, authenticateRequest, - UserController.clearPb + asyncHandlerWrapper(UserController.clearPb) ); router.post( "/tags/add", RateLimit.userTagsAdd, authenticateRequest, - UserController.addTag + asyncHandlerWrapper(UserController.addTag) ); router.get( "/tags", RateLimit.userTagsGet, authenticateRequest, - UserController.getTags + asyncHandlerWrapper(UserController.getTags) ); router.post( "/tags/clearPb", RateLimit.userTagsClearPB, authenticateRequest, - UserController.clearTagPb + asyncHandlerWrapper(UserController.clearTagPb) ); router.post( "/tags/remove", RateLimit.userTagsRemove, authenticateRequest, - UserController.removeTag + asyncHandlerWrapper(UserController.removeTag) ); router.post( "/tags/edit", RateLimit.userTagsEdit, authenticateRequest, - UserController.editTag + asyncHandlerWrapper(UserController.editTag) ); router.post( "/discord/link", RateLimit.userDiscordLink, authenticateRequest, - UserController.linkDiscord + asyncHandlerWrapper(UserController.linkDiscord) ); router.post( "/discord/unlink", RateLimit.userDiscordUnlink, authenticateRequest, - UserController.unlinkDiscord + asyncHandlerWrapper(UserController.unlinkDiscord) ); module.exports = router;