diff --git a/package.json b/package.json index c5b93b5..04bfa10 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,13 @@ "homepage": "https://github.com/detleph/server#readme", "dependencies": { "@prisma/client": "^3.3.0", + "@types/jsonwebtoken": "^8.5.5", "@types/node": "^16.10.3", "@types/nodemailer": "^6.4.4", + "argon2": "^0.28.2", "dotenv": "^10.0.0", "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "nodemailer": "^6.7.0" }, "devDependencies": { diff --git a/src/Controllers/admin_auth.controller.ts b/src/Controllers/admin_auth.controller.ts new file mode 100644 index 0000000..d65fc03 --- /dev/null +++ b/src/Controllers/admin_auth.controller.ts @@ -0,0 +1,74 @@ +import { Request, Response } from "express"; +import { Admin } from ".prisma/client"; +import prisma from "../lib/prisma"; +import argon2 from "argon2"; +import jwt from "jsonwebtoken"; + +const JWT_SECRET = process.env.JWT_SECRET || "secret"; + +function createAdminJWT(admin: Admin) { + let payload: { name: string; permission_level: number; revision: string; group?: string } = { + name: admin.name, + permission_level: admin.permission_level, + revision: "", // TODO: Revision in the schema is pending + }; + + if (admin.permission_level > 0) { + payload = { ...payload, group: "" }; // TODO: Group association for admins in the schema is pending + } + + return jwt.sign(payload, JWT_SECRET, { expiresIn: "4 hours" }); +} + +interface AuthenticateUserBody { + name?: string; + password?: string; +} + +export const authenticateUser = async (req: Request<{}, {}, AuthenticateUserBody>, res: Response) => { + // TODO: Provide more useful error messages (Maybe express-validator?) + + const { name, password } = req.body; + + if (name == null || password == null) { + return res.status(400).json({ + type: "error", + payload: { + message: "The body must contain 'name' and 'password' attributes of type string", + }, + }); + } + + const user = await prisma.admin.findFirst({ where: { name } }); + + // REVIEW: It is possible to initiate a timing attack here (To see which users exist) + // This should, however, not be of too much concern, as one can basically do nothing + // with only the username + + if (!user) { + return res.status(404).json({ + type: "error", + payload: { + message: `The provided credentials are not valid`, + }, + }); + } + + const param_password_hash = await argon2.hash(password, { type: argon2.argon2id }); + + if (param_password_hash === user.password) { + return { + type: "success", + payload: { + token: createAdminJWT(user), + }, + }; + } + + return { + type: "error", + payload: { + message: "The provided credentials are not valid", + }, + }; +};