From 4d23eccba64f3d64ffbcbe1f3a36ca7ac6ca8146 Mon Sep 17 00:00:00 2001 From: Stephan <57194608+stephan418@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:49:01 +0100 Subject: [PATCH] Restructure the process of changing a password + Requests are now dispatched to the according user ressource, not the collection + The current user can be referenced by setting the pid to 'current' + Refactored the error logic --- src/Controllers/admin.controller.ts | 119 +++++++++++++++------------- src/Controllers/common.ts | 20 +++++ src/Routes/admin.routes.ts | 6 +- 3 files changed, 88 insertions(+), 57 deletions(-) create mode 100644 src/Controllers/common.ts diff --git a/src/Controllers/admin.controller.ts b/src/Controllers/admin.controller.ts index 9cf735c..1cd66a1 100644 --- a/src/Controllers/admin.controller.ts +++ b/src/Controllers/admin.controller.ts @@ -2,6 +2,7 @@ import prisma from "../lib/prisma"; import { Request, Response } from "express"; import { AdminLevel } from "@prisma/client"; import argon2 from "argon2"; +import { DataType, generateInvalidBodyError } from "./common"; const AUTH_ERROR = { type: "failure", @@ -122,12 +123,6 @@ export const createAdmin = async (req: Request<{}, {}, CreateAdminBody>, res: Re }); }; -interface UpdatePasswordBody { - pid?: string; - password?: string; - new_password: string; -} - // Expects a valid username (Should be tested beforehand) const updatePasswordField = async (pid: string, new_password: string) => { const new_password_hash = await argon2.hash(new_password, { type: argon2.argon2id }); @@ -135,28 +130,72 @@ const updatePasswordField = async (pid: string, new_password: string) => { await prisma.admin.update({ where: { pid }, data: { password: new_password_hash } }); }; -// requires: auth -export const updatePassword = async (req: Request<{}, {}, UpdatePasswordBody>, res: Response) => { +interface UpdateForeignPasswordBody { + new_password?: string; +} + +interface UpdateForeignPasswordQueryParams { + pid: string; +} + +export const updateForeignPassword = async ( + req: Request, + res: Response +) => { if (!req.auth?.isAuthenticated) { return res.status(500).json(AUTH_ERROR); } - const pid = req.body.pid || req.auth.pid; - if (typeof req.body.new_password !== "string") { - return res.status(400).json({ + return res.status(400).json(generateInvalidBodyError({ new_password: DataType.STRING })); + } + + const user_to_upate = await prisma.admin.findUnique({ where: { pid: req.params.pid } }); + + if (!user_to_upate) { + // REVIEW: This allows potential attackers (which are authorized with some account) + // to test account names + return res.status(404).json({ type: "error", payload: { - message: "The body of your request did not conform to the requirements", - schema: { - body: { - new_password: "string", - }, - }, + message: "The requested user was not found", }, }); } + if (req.auth.permission_level == "ELEVATED" && user_to_upate.permission_level == "STANDARD") { + updatePasswordField(req.params.pid, req.body.new_password); + + res.status(200).json({ + type: "success", + }); + } else { + res.status(403).json({ + type: "error", + payload: { + message: "Operation not permitted; Try logging in as another user", + }, + }); + } +}; + +interface UpdatePasswordBody { + password?: string; + new_password?: string; +} + +// requires: auth +export const updateOwnPassword = async (req: Request<{}, {}, UpdatePasswordBody>, res: Response) => { + if (!req.auth?.isAuthenticated) { + return res.status(500).json(AUTH_ERROR); + } + + const pid = req.auth.pid; + + if (typeof req.body.password !== "string" || typeof req.body.new_password !== "string") { + return res.status(400).json(generateInvalidBodyError({ password: DataType.STRING, new_password: DataType.STRING })); + } + const user_to_upate = await prisma.admin.findUnique({ where: { pid } }); if (!user_to_upate) { @@ -170,46 +209,18 @@ export const updatePassword = async (req: Request<{}, {}, UpdatePasswordBody>, r }); } - if (req.auth.permission_level == "ELEVATED" && user_to_upate.permission_level == "STANDARD") { + if (await argon2.verify(user_to_upate.password, req.body.password, { type: argon2.argon2id })) { updatePasswordField(pid, req.body.new_password); - res.status(200).json({ + return res.status(200).json({ type: "success", }); - } else if (req.auth.pid === pid) { - if (typeof req.body.password !== "string") { - return res.status(400).json({ - type: "error", - payload: "The body of your request did not conform to the requirements", - schema: { - body: { - password: "string", - new_password: "string", - }, - }, - }); - } - - if (await argon2.verify(user_to_upate.password, req.body.password, { type: argon2.argon2id })) { - updatePasswordField(pid, req.body.new_password); - - return res.status(200).json({ - type: "success", - }); - } - - return res.status(401).json({ - type: "error", - payload: { - message: "The provided password is not valid", - }, - }); - } else { - res.status(403).json({ - type: "error", - payload: { - message: "Operation not permitted; Try logging in as another user", - }, - }); } + + return res.status(401).json({ + type: "error", + payload: { + message: "The provided password is not valid", + }, + }); }; diff --git a/src/Controllers/common.ts b/src/Controllers/common.ts new file mode 100644 index 0000000..2adc291 --- /dev/null +++ b/src/Controllers/common.ts @@ -0,0 +1,20 @@ +export enum DataType { + STRING = "string", + NUMBER = "number", + INTEGER = "integer", + PERMISSION_LEVEL = "'ELEVATED' | 'STANDARD'", +} + +interface Body { + [k: string]: DataType; +} + +export function generateInvalidBodyError(body: Body) { + return { + type: "error", + payload: { + message: "The body of your request did not conform to the requirements", + schema: { body }, + }, + }; +} diff --git a/src/Routes/admin.routes.ts b/src/Routes/admin.routes.ts index 20d5df7..b4be6ce 100644 --- a/src/Routes/admin.routes.ts +++ b/src/Routes/admin.routes.ts @@ -1,5 +1,5 @@ import express from "express"; -import { getAllAdmins, createAdmin, updatePassword } from "../Controllers/admin.controller"; +import { getAllAdmins, createAdmin, updateOwnPassword, updateForeignPassword } from "../Controllers/admin.controller"; import { requireAuthentication } from "../Middleware/auth/auth"; const router = express.Router(); @@ -8,7 +8,7 @@ router.get("/", requireAuthentication, getAllAdmins); router.post("/", requireAuthentication, createAdmin); -// TODO: Use URL parameters to specify the user to update -router.put("/password", requireAuthentication, updatePassword); +router.put("/current/password", requireAuthentication, updateOwnPassword); +router.put<"/:pid/password", { pid: string }>("/:pid/password", requireAuthentication, updateForeignPassword); export default router;