From 1fab992f534e50fbcfee461e98740e9bae8edbab Mon Sep 17 00:00:00 2001 From: Nathaniel Tampus Date: Sun, 19 Mar 2023 20:52:58 +0800 Subject: [PATCH] add auth controllers & validation improvements --- server/src/controllers/auth.controller.ts | 215 +++++++++++++++++++++- 1 file changed, 214 insertions(+), 1 deletion(-) diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts index 2e2b067..4029a4b 100644 --- a/server/src/controllers/auth.controller.ts +++ b/server/src/controllers/auth.controller.ts @@ -1,6 +1,11 @@ import type { Request, Response } from "express"; import type { User } from "@chessu/types"; import xss from "xss"; +import { hash, verify } from "argon2"; + +import { activeGames } from "../db/models/game.model.js"; +import { io } from "../server.js"; +import * as UserModel from "../db/models/user.model.js"; export const getCurrentSession = async (req: Request, res: Response) => { try { @@ -17,9 +22,13 @@ export const getCurrentSession = async (req: Request, res: Response) => { export const guestSession = async (req: Request, res: Response) => { try { + if (req.session.user?.id && typeof req.session.user.id === "number") { + res.status(403).end(); + return; + } const name = xss(req.body.name); - const pattern = /^[A-Za-z0-9_]+$/; + const pattern = /^[A-Za-z0-9]+$/; if (!pattern.test(name)) { res.status(400).end(); @@ -36,6 +45,29 @@ export const guestSession = async (req: Request, res: Response) => { } else if (typeof req.session.user.id === "string" && req.session.user.name !== name) { // update guest name req.session.user.name = name; + + const game = activeGames.find( + (g) => + g.white?.id === req.session.user.id || + g.black?.id === req.session.user.id || + g.observers?.find((o) => o.id === req.session.user.id) + ); + if (game) { + if (game.host?.id === req.session.user.id) { + game.host.name = name; + } + if (game.white?.id === req.session.user.id) { + game.white.name = name; + } else if (game.black?.id === req.session.user.id) { + game.black.name = name; + } else { + const observer = game.observers?.find((o) => o.id === req.session.user.id); + if (observer) { + observer.name = name; + } + } + io.to(game.code as string).emit("receivedLatestGame", game); + } } req.session.save(() => { res.status(201).json(req.session.user); @@ -56,3 +88,184 @@ export const logoutSession = async (req: Request, res: Response) => { res.status(500).end(); } }; + +export const registerUser = async (req: Request, res: Response) => { + try { + if (req.session.user?.id && typeof req.session.user.id === "number") { + res.status(403).end(); + return; + } + + const name = xss(req.body.name); + const email = xss(req.body.email); + const password = await hash(req.body.password); + + const pattern = /^[A-Za-z0-9]+$/; + + if (!pattern.test(name)) { + res.status(400).end(); + return; + } + + const duplicateUsers = await UserModel.find({ name, email }); + if (duplicateUsers && duplicateUsers.length) { + const dupl = duplicateUsers[0].name === name ? "Username" : "Email"; + res.status(409).json({ message: `${dupl} is already in use.` }); + return; + } + + const newUser = await UserModel.create({ name, email }, password); + if (!newUser) { + throw new Error("Failed to create user"); + } + + if (req.session.user?.id && typeof req.session.user.id === "string") { + const game = activeGames.find( + (g) => + g.white?.id === req.session.user.id || + g.black?.id === req.session.user.id || + g.observers?.find((o) => o.id === req.session.user.id) + ); + if (game) { + if (game.host?.id === req.session.user.id) { + game.host = newUser; + } + if (game.white && game.white?.id === req.session.user.id) { + game.white = newUser; + } else if (game.black && game.black?.id === req.session.user.id) { + game.black = newUser; + } else { + const observer = game.observers?.find((o) => o.id === req.session.user.id); + if (observer) { + observer.id = newUser.id; + observer.name = newUser.name; + } + } + io.to(game.code as string).emit("receivedLatestGame", game); + } + } + + req.session.user = newUser; + req.session.save(() => { + res.status(201).json(req.session.user); + }); + } catch (err: unknown) { + console.log(err); + res.status(500).end(); + } +}; + +export const loginUser = async (req: Request, res: Response) => { + try { + if (req.session.user?.id && typeof req.session.user.id === "number") { + res.status(403).end(); + return; + } + + const nameOrEmail = xss(req.body.name); + const password = req.body.password; + + const users = await UserModel.find({ name: nameOrEmail, email: nameOrEmail }); + if (!users || !users.length) { + res.status(404).json({ message: "Invalid username/email." }); + return; + } + + const validPassword = await verify(users[0].password as string, password); + if (!validPassword) { + res.status(401).json({ message: "Invalid password." }); + return; + } + + const newUser = { + id: users[0].id, + name: users[0].name, + email: users[0].email + }; + + if (req.session.user?.id && typeof req.session.user.id === "string") { + const game = activeGames.find( + (g) => + g.white?.id === req.session.user.id || + g.black?.id === req.session.user.id || + g.observers?.find((o) => o.id === req.session.user.id) + ); + if (game) { + if (game.host?.id === req.session.user.id) { + game.host = newUser; + } + if (game.white && game.white?.id === req.session.user.id) { + game.white = newUser; + } else if (game.black && game.black?.id === req.session.user.id) { + game.black = newUser; + } else { + const observer = game.observers?.find((o) => o.id === req.session.user.id); + if (observer) { + observer.id = newUser.id; + observer.name = newUser.name; + } + } + io.to(game.code as string).emit("receivedLatestGame", game); + } + } + + req.session.user = { + id: users[0].id, + name: users[0].name + }; + req.session.save(() => { + res.status(200).json(req.session.user); + }); + } catch (err: unknown) { + console.log(err); + res.status(500).end(); + } +}; + +export const updateUser = async (req: Request, res: Response) => { + try { + if (!req.session.user?.id || typeof req.session.user.id === "string") { + res.status(403).end(); + return; + } + + const name = xss(req.body.name || req.session.user.name); + const pattern = /^[A-Za-z0-9]+$/; + if (!pattern.test(name)) { + res.status(400).end(); + return; + } + + const email = xss(req.body.email || req.session.user.email); + + const duplicateUsers = await UserModel.find({ name, email }); + if ( + duplicateUsers && + duplicateUsers.length && + duplicateUsers[0].id !== req.session.user.id + ) { + const dupl = duplicateUsers[0].name === name ? "Username" : "Email"; + res.status(409).json({ message: `${dupl} is already in use.` }); + return; + } + + let password: string | undefined = undefined; + if (req.body.password) { + password = await hash(req.body.password); + } + + const updatedUser = await UserModel.update(req.session.user.id, { name, email, password }); + + if (!updatedUser) { + throw new Error("Failed to update user"); + } + + req.session.user = updatedUser; + req.session.save(() => { + res.status(200).json(req.session.user); + }); + } catch (err: unknown) { + console.log(err); + res.status(500).end(); + } +};