patched review suggestions and discovered bugs

This commit is contained in:
Laurin
2022-06-07 03:47:37 +02:00
parent 94b2030c8f
commit 7655eebcd9
10 changed files with 259 additions and 221 deletions
+8 -6
View File
@@ -137,12 +137,14 @@ export const addEvent = async (req: Request, res: Response) => {
); );
} }
const body = result.data;
const event = await prisma.event.create({ const event = await prisma.event.create({
data: { data: {
name: req.body.name, name: body.name,
date: req.body.date, date: body.date,
briefDescription: req.body.briefDescription, briefDescription: body.briefDescription,
fullDescription: req.body.fullDescription, fullDescription: body.fullDescription,
}, },
select: basicEvent, select: basicEvent,
}); });
@@ -206,7 +208,7 @@ export const updateEvent = async (req: Request<{ pid: string }>, res: Response)
}); });
} catch (e) { } catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError("discipline", pid); throw new NotFoundError("event", pid);
} }
throw e; throw e;
@@ -231,7 +233,7 @@ export const deleteEvent = async (req: Request<DeleteEventQueryParams>, res: Res
return res.status(204).end(); return res.status(204).end();
} catch (e) { } catch (e) {
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError("discipline", pid); throw new NotFoundError("event", pid);
} }
throw e; throw e;
+18 -12
View File
@@ -10,6 +10,7 @@ import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import { generateInvalidBodyError, DataType } from "./common"; import { generateInvalidBodyError, DataType } from "./common";
import { unlink } from "fs/promises"; import { unlink } from "fs/promises";
import ForwardableError from "../Middleware/error/ForwardableError"; import ForwardableError from "../Middleware/error/ForwardableError";
import { table } from "console";
require("express-async-errors"); require("express-async-errors");
@@ -204,21 +205,26 @@ export const linkMedia = async (req: Request<{ pid: string }, {}, { mediaPid: st
); );
} }
const updatedRec = await getPrismaUpdateFKT(tableToUpdate[2])({ try {
where: { pid }, const updatedRec = await getPrismaUpdateFKT(tableToUpdate[2])({
data: { where: { pid },
visual: { connect: { pid: mediaPid } }, data: {
}, visual: { connect: { pid: mediaPid } },
}); },
});
if (!updatedRec) { return res.status(200).json({
throw new NotFoundError(tableToUpdate[2], pid); type: "success",
payload: { message: "Linking with the visual was successful" },
});
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError(tableToUpdate[2], pid);
}
throw e;
} }
return res.status(200).json({
type: "success",
payload: {},
});
}; };
export const unlinkMedia = async (req: Request<{ pid: string; mediaPid: string }>, res: Response) => { export const unlinkMedia = async (req: Request<{ pid: string; mediaPid: string }>, res: Response) => {
+2 -2
View File
@@ -28,7 +28,7 @@ const detailedOrganisation = {
pid: true, pid: true,
name: true, name: true,
date: true, date: true,
description: true, briefDescription: true,
}, },
}, },
} as const; } as const;
@@ -191,7 +191,7 @@ export const updateOrganisation = async (
}); });
} catch (e) { } catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError("discipline", pid); throw new NotFoundError("organisation", pid);
} }
throw e; throw e;
+12 -10
View File
@@ -16,8 +16,6 @@ const InitialParticipant = z.object({
groupPid: z.string().min(1).uuid(), groupPid: z.string().min(1).uuid(),
}); });
const ParticipantBody = InitialParticipant.extend({ teamPid: z.string().uuid() });
const returnedParticipant = { const returnedParticipant = {
pid: true, pid: true,
firstName: true, firstName: true,
@@ -41,7 +39,7 @@ const returnedParticipant = {
export const createParticipant = async (req: Request<{ teamPid: string }>, res: Response) => { export const createParticipant = async (req: Request<{ teamPid: string }>, res: Response) => {
const { teamPid } = req.params; const { teamPid } = req.params;
const result = ParticipantBody.safeParse(req.body); const result = InitialParticipant.safeParse(req.body);
if (result.success === false) { if (result.success === false) {
return res.status(400).json( return res.status(400).json(
@@ -59,20 +57,20 @@ export const createParticipant = async (req: Request<{ teamPid: string }>, res:
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
await requireLeaderOfTeam(req.teamleader, teamPid); await requireLeaderOfTeam(req.teamleader, teamPid);
} else { } else if (req.auth?.permission_level == "STANDARD") {
requireResponsibleForGroups(req.auth, body.groupPid); requireResponsibleForGroups(req.auth, body.groupPid);
} }
try { try {
const discipline = await prisma.team.findUnique({ const discipline = await prisma.team.findUnique({
where: { pid: body.teamPid }, where: { pid: teamPid },
select: { discipline: true } select: { discipline: true }
}); });
const maxteamsize = discipline?.discipline.maxTeamSize; const maxteamsize = discipline?.discipline.maxTeamSize;
const userCount = await prisma.participant.count({ const userCount = await prisma.participant.count({
where: { team: { pid: body.teamPid } } where: { team: { pid: teamPid } }
}); });
if (maxteamsize == userCount) { if (maxteamsize == userCount) {
@@ -85,7 +83,7 @@ export const createParticipant = async (req: Request<{ teamPid: string }>, res:
lastName: body.lastName, lastName: body.lastName,
relevance: "MEMBER", relevance: "MEMBER",
group: { connect: { pid: body.groupPid } }, group: { connect: { pid: body.groupPid } },
team: { connect: { pid: body.teamPid } }, team: { connect: { pid: teamPid } },
}, },
select: returnedParticipant, select: returnedParticipant,
}); });
@@ -95,7 +93,7 @@ export const createParticipant = async (req: Request<{ teamPid: string }>, res:
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") { if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
return res return res
.status(404) .status(404)
.json(generateError(`Could not link to team with ID '${body.teamPid}, or group with ID ${body.groupPid}'`)); .json(generateError(`Could not link to team with ID '${teamPid}, or group with ID ${body.groupPid}'`));
} }
throw e; throw e;
} }
@@ -107,6 +105,8 @@ export const updateParticipant = async (req: Request<{ pid: string }>, res: Resp
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
await requireResponsibleForParticipant(req.teamleader, pid); await requireResponsibleForParticipant(req.teamleader, pid);
} else if (req.auth?.permission_level == "STANDARD") {
requireResponsibleForGroups(req.auth, await getGroupByParticipantPid(pid));
} }
const result = InitialParticipant.partial().safeParse(req.body); const result = InitialParticipant.partial().safeParse(req.body);
@@ -143,7 +143,9 @@ export const updateParticipant = async (req: Request<{ pid: string }>, res: Resp
}); });
} catch (e) { } catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError("participant", pid); return res
.status(404)
.json(generateError(`Could not find participant '${pid}, or link to group with ID ${body.groupPid}.'`));
} }
throw e; throw e;
@@ -156,7 +158,7 @@ export const deleteParticipant = async (req: Request<{ pid: string }>, res: Resp
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
await requireResponsibleForParticipant(req.teamleader, pid); await requireResponsibleForParticipant(req.teamleader, pid);
} else { } else if (req.auth?.permission_level == "STANDARD") {
requireResponsibleForGroups(req.auth, await getGroupByParticipantPid(pid)); requireResponsibleForGroups(req.auth, await getGroupByParticipantPid(pid));
} }
+20 -22
View File
@@ -1,3 +1,4 @@
import { Prisma } from "@prisma/client";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { z } from "zod"; import { z } from "zod";
import prisma from "../lib/prisma"; import prisma from "../lib/prisma";
@@ -61,7 +62,7 @@ export async function getRolesForTeam(req: Request<{ pid: string }>, res: Respon
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
await requireLeaderOfTeam(req.teamleader, pid); await requireLeaderOfTeam(req.teamleader, pid);
} else { } else {
await requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(pid)); requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(pid));
} }
const roles = await prisma.role.findMany({ const roles = await prisma.role.findMany({
@@ -84,7 +85,6 @@ export async function getRolesForTeam(req: Request<{ pid: string }>, res: Respon
const AssignParticipantToRoleBody = z.object({ const AssignParticipantToRoleBody = z.object({
participantPid: z.string().uuid(), participantPid: z.string().uuid(),
teamPid: z.string().uuid(),
}); });
// requires: auth(leader of the team) // requires: auth(leader of the team)
@@ -102,31 +102,29 @@ export async function assignParticipantToRole(req: Request<{ pid: string }>, res
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
requireResponsibleForParticipant(req.teamleader, participantPid); requireResponsibleForParticipant(req.teamleader, participantPid);
} else { } else {
await requireResponsibleForGroups(req.auth, await getGroupByParticipantPid(participantPid)); requireResponsibleForGroups(req.auth, await getGroupByParticipantPid(participantPid));
} }
const schema = await prisma.role.findFirst({ try {
where: { pid, team: { participants: { some: { pid: participantPid } } } }, const schema = await prisma.role.update({ where: { pid }, data: { participant: { connect: { pid: participantPid } } }, select: detailedRole });
select: { participant: { select: { pid: true, firstName: true, lastName: true } } },
});
if (!schema) { return res.status(200).json({
return res.status(404).json({ type: "success",
type: "error",
payload: { payload: {
message: `No role with the ID '${pid}' could be found in the scope of the participant with the ID '${participantPid}'`, message: `The participant with ID '${participantPid}' was successfully assigned to the role with ID '${pid}'`,
...(schema.participant ? { unassigned: schema.participant } : {}),
}, },
}); });
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
return res.status(404).json({
type: "error",
payload: {
message: `No role with the ID '${pid}' could be found in the scope of the participant with the ID '${participantPid}'`,
},
});
}
throw e;
} }
// No error handling should be neccesary as the existence of the role and participant have already been checked above
await prisma.role.update({ where: { pid }, data: { participant: { connect: { pid: participantPid } } } });
return res.status(200).json({
type: "success",
payload: {
message: `The participant with ID '${participantPid}' was successfully assigned to the role with ID '${pid}'`,
...(schema.participant ? { unassigned: schema.participant } : {}),
},
});
} }
+4 -4
View File
@@ -3,7 +3,7 @@ import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { z } from "zod"; import { z } from "zod";
import prisma from "../lib/prisma"; import prisma from "../lib/prisma";
import { DurationSchemaT, parseSchema, PointSchemaT } from "../lib/result_schema"; import { DurationSchema, parseSchema, PointSchema } from "../lib/result_schema";
import NotFoundError from "../Middleware/error/NotFoundError"; import NotFoundError from "../Middleware/error/NotFoundError";
import SchemaError from "../Middleware/error/SchemaError"; import SchemaError from "../Middleware/error/SchemaError";
import { import {
@@ -19,7 +19,7 @@ require("express-async-errors");
const RoleSchemaBody = z.object({ const RoleSchemaBody = z.object({
name: z.string().min(1), name: z.string().min(1),
schema: z.string(), schema: z.string(PointSchema).or(z.string(DurationSchema)),
}); });
const UpdateBody = RoleSchemaBody.partial(); const UpdateBody = RoleSchemaBody.partial();
@@ -138,7 +138,7 @@ export const createRoleSchema = async (
export const UpdateRoleSchema = async (req: Request<{ pid: string }>, res: Response) => { export const UpdateRoleSchema = async (req: Request<{ pid: string }>, res: Response) => {
if (req.auth?.permission_level !== "ELEVATED") { if (req.auth?.permission_level !== "ELEVATED") {
res.status(403).json(createInsufficientPermissionsError()); return res.status(403).json(createInsufficientPermissionsError());
} }
const { pid } = req.params; const { pid } = req.params;
@@ -150,7 +150,7 @@ export const UpdateRoleSchema = async (req: Request<{ pid: string }>, res: Respo
generateInvalidBodyError( generateInvalidBodyError(
{ {
name: DataType.STRING, name: DataType.STRING,
schema: DataType.STRING, schema: DataType.RESULT_SCHEMA,
}, },
result.error result.error
) )
+19 -6
View File
@@ -7,6 +7,8 @@ import { Prisma } from "@prisma/client";
import NotFoundError from "../Middleware/error/NotFoundError"; import NotFoundError from "../Middleware/error/NotFoundError";
import { requireResponsibleForGroups } from "../Middleware/auth/auth"; import { requireResponsibleForGroups } from "../Middleware/auth/auth";
import AuthError from "../Middleware/error/AuthError"; import AuthError from "../Middleware/error/AuthError";
import { runInNewContext } from "vm";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
require("express-async-errors"); require("express-async-errors");
@@ -31,6 +33,9 @@ export const basicTeam = {
}; };
export const getTeams = async (req: Request, res: Response) => { export const getTeams = async (req: Request, res: Response) => {
if (req.auth?.permission_level == "STANDARD") {
throw new AuthError("A STANDARD Admin is not allowed to get all teams!");
}
const teams = await prisma.team.findMany({ select: basicTeam }); const teams = await prisma.team.findMany({ select: basicTeam });
res.status(200).json({ type: "success", payload: { teams } }); res.status(200).json({ type: "success", payload: { teams } });
@@ -41,8 +46,8 @@ export const getTeam = async (req: Request<{ pid: string }>, res: Response) => {
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
await requireLeaderOfTeam(req.teamleader, pid); await requireLeaderOfTeam(req.teamleader, pid);
} else { } else if (req.auth?.permission_level == "STANDARD") {
await requireResponsibleForGroups(req.auth, pid); requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(pid));
} }
const team = await prisma.team.findUnique({ const team = await prisma.team.findUnique({
@@ -62,8 +67,8 @@ export const updateTeam = async (req: Request, res: Response) => {
if (req.teamleader?.isAuthenticated) { if (req.teamleader?.isAuthenticated) {
await requireLeaderOfTeam(req.teamleader, pid); await requireLeaderOfTeam(req.teamleader, pid);
} else { } else if (req.auth?.permission_level == "STANDARD") {
await requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(pid)); requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(pid));
} }
const result = TeamBody.omit({ partGroupId: true, partFirstName: true, partLastName: true }).safeParse(req.body); const result = TeamBody.omit({ partGroupId: true, partFirstName: true, partLastName: true }).safeParse(req.body);
@@ -117,9 +122,17 @@ export const deleteTeam = async (req: Request, res: Response) => {
throw new AuthError("STANDARD Admins are not allowed to delete Teams!") throw new AuthError("STANDARD Admins are not allowed to delete Teams!")
} }
await prisma.team.delete({ where: { pid } }); try {
await prisma.team.delete({ where: { pid } });
res.status(204).json({ type: "success", payload: { message: "Sucesfully deleted team" } }); res.status(204).json({ type: "success", payload: { message: "Sucesfully deleted team" } });
} catch (e) {
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError("team", pid);
}
throw e;
}
}; };
export async function checkTeamExistence(teamPid: string) { export async function checkTeamExistence(teamPid: string) {
+69 -52
View File
@@ -7,6 +7,8 @@ import { DataType, generateError, generateInvalidBodyError } from "./common";
import { generateTeamleaderJWT } from "../Middleware/auth/teamleaderAuth"; import { generateTeamleaderJWT } from "../Middleware/auth/teamleaderAuth";
import { createRolesForTeam } from "./role.controller"; import { createRolesForTeam } from "./role.controller";
import { z } from "zod"; import { z } from "zod";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import NotFoundError from "../Middleware/error/NotFoundError";
require("express-async-errors"); require("express-async-errors");
@@ -49,50 +51,55 @@ export const register = async (req: Request<{}, {}, CreateTeamBody>, res: Respon
const body = result.data; const body = result.data;
const group = prisma.group.findUnique({ where: { pid: body.partGroupId } }); try {
const team = await prisma.team.create({
if (typeof group == null) { data: {
return res.status(404).json(generateError("Specified group was not found!")); leaderEmail: body.leaderEmail,
} name: body.teamName,
roles: undefined,
const discipline = prisma.discipline.findUnique({ where: { pid: body.disciplineId } }); discipline: { connect: { pid: body.disciplineId } },
participants: {
if (typeof discipline == null) { create: {
return res.status(404).json(generateError("Specified discipline was not found!")); firstName: body.partFirstName,
} lastName: body.partLastName,
relevance: "TEAMLEADER",
const team = await prisma.team.create({ group: { connect: { pid: body.partGroupId } },
data: { },
leaderEmail: body.leaderEmail,
name: body.teamName,
roles: undefined,
discipline: { connect: { pid: body.disciplineId } },
participants: {
create: {
firstName: body.partFirstName,
lastName: body.partLastName,
relevance: "TEAMLEADER",
group: { connect: { pid: body.partGroupId } },
}, },
}, },
}, select: {
select: { pid: true,
pid: true, name: true,
name: true, discipline: { select: { pid: true } },
discipline: { select: { pid: true } }, },
}, });
});
await createRolesForTeam(team.pid); await createRolesForTeam(team.pid);
const usid = nanoid(); const usid = nanoid();
(await mailClient).set(usid, team.pid);
const eventname = await prisma.discipline.findUnique({ where: { pid: body.disciplineId }, select: { event: { select: { name: true, } } } }).event.name;
verificationMail(req.body.leaderEmail, eventname, usid);
return res.status(201).json({ type: "success", payload: { team } });
} catch (e) {
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
return res.status(404).json({
type: "error",
payload: {
message: `Could not connect to discipline with the ID '${body.disciplineId}' or could not connect participant to group with the ID '${body.partGroupId}'`,
},
});
}
throw e;
}
(await mailClient).set(usid, team.pid);
// TODO: fix "eventname"
verificationMail(req.body.leaderEmail, "eventname", usid);
res.status(201).json({ type: "success", payload: { team } });
}; };
export const requestToken = async (req: Request, res: Response) => { export const requestToken = async (req: Request, res: Response) => {
@@ -117,6 +124,7 @@ export const requestToken = async (req: Request, res: Response) => {
(await mailClient).set(usid, team.pid); (await mailClient).set(usid, team.pid);
// TODO: fix "eventname" // TODO: fix "eventname"
// let teamName = await prisma.team.findUnique({ where: { pid: teamId }, select: { discipline: { select: { event: { select: { name: true } } } } } });
verificationMail(team.leaderEmail, "eventname", usid); verificationMail(team.leaderEmail, "eventname", usid);
res.status(200).json({ type: "sucess", payload: { message: "Email sent!" } }); res.status(200).json({ type: "sucess", payload: { message: "Email sent!" } });
@@ -145,23 +153,32 @@ export const verifyEmail = async (req: Request, res: Response) => {
}); });
} }
const team = await prisma.team.update({ try {
where: { const team = await prisma.team.update({
pid: acc, where: {
}, pid: acc,
data: { },
verified: true, data: {
}, verified: true,
}); },
});
mailClient.set(code, ""); mailClient.set(code, "");
const token = generateTeamleaderJWT(team); const token = generateTeamleaderJWT(team);
res.cookie("teamLeaderToken", token, { res.cookie("teamLeaderToken", token, {
path: "/", path: "/",
maxAge: 1000 * 60 * 60 * 24 * 4, maxAge: 1000 * 60 * 60 * 24 * 4,
}); });
res.status(200).json({ type: "succes", payload: { token } });
} catch (e) {
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
throw new NotFoundError("discipline", acc);
}
throw e;
}
res.status(200).json({ type: "succes", payload: { token } });
}; };
+105 -105
View File
@@ -18,42 +18,82 @@ export const getBearerToken = (authorization: string) => authorization.slice(7);
const _requireAdminAuthentication = const _requireAdminAuthentication =
(config: { optional?: Boolean; controlled?: Boolean } = { optional: false, controlled: false }) => (config: { optional?: Boolean; controlled?: Boolean } = { optional: false, controlled: false }) =>
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
if (!JWT_SECRET) { if (!JWT_SECRET) {
throw new Error("JWT_SECRET not set"); throw new Error("JWT_SECRET not set");
}
const { authorization } = req.headers;
if (!authorization) {
if (config.optional) {
return false;
} }
return res.status(403).send({ const { authorization } = req.headers;
type: "error",
payload: {
message: "The requeset did not include the Authorization header",
},
});
}
if (!verifyAuthorizationFormat(authorization)) { if (!authorization) {
return res.status(400).send({ if (config.optional) {
type: "error", return false;
payload: { }
message: "Malformed Authorization header",
format: "Bearer <token>",
},
});
}
let token_payload_: string | JwtPayload; return res.status(403).send({
type: "error",
payload: {
message: "The requeset did not include the Authorization header",
},
});
}
try { if (!verifyAuthorizationFormat(authorization)) {
token_payload_ = jwt.verify(getBearerToken(authorization), JWT_SECRET); return res.status(400).send({
} catch (e) { type: "error",
if (e instanceof JsonWebTokenError) { payload: {
message: "Malformed Authorization header",
format: "Bearer <token>",
},
});
}
let token_payload_: string | JwtPayload;
try {
token_payload_ = jwt.verify(getBearerToken(authorization), JWT_SECRET);
} catch (e) {
if (e instanceof JsonWebTokenError) {
return res.status(403).json({
type: "error",
payload: {
message: "Token could not be verified; It might be expired",
},
});
}
throw e;
}
const token_payload = token_payload_ as AuthJWTPayload;
if (!token_payload.permission_level || !token_payload.pid || !token_payload.revision) {
if (typeof (token_payload as unknown as TeamleaderJWTPayload).team === "string") {
if (config.controlled) {
return false;
}
throw new AuthError("Teamleader authentication is not supported for this operation!");
}
throw new AuthError("The token did not include the required information!");
}
const { pid, revision } = token_payload;
let db_revision = await authClient.get(pid);
if (db_revision === null) {
// Load the revision ID from the main DB and cache it in redis
const user = await prisma.admin.findUnique({ where: { pid }, select: { revision: true } });
if (user) {
db_revision = user.revision.toISOString();
await authClient.set(pid, db_revision);
}
}
if (revision !== db_revision || !revision || !db_revision) {
return res.status(403).json({ return res.status(403).json({
type: "error", type: "error",
payload: { payload: {
@@ -62,62 +102,22 @@ const _requireAdminAuthentication =
}); });
} }
throw e; req.auth = {
} isAuthenticated: true,
pid: token_payload.pid,
name: token_payload.name,
permission_level: token_payload.permission_level,
groups: token_payload.groups,
revision: token_payload.revision,
};
const token_payload = token_payload_ as AuthJWTPayload; if (!config.controlled) {
next();
if (!token_payload.permission_level || !token_payload.pid || !token_payload.revision) {
if (typeof (token_payload as unknown as TeamleaderJWTPayload).team === "string") {
if (config.controlled) {
return false;
}
throw new AuthError("Teamleader authentication is not supported for this operation!");
} }
throw new AuthError("The token did not include the required information!"); return true;
}
const { pid, revision } = token_payload;
let db_revision = await authClient.get(pid);
if (db_revision === null) {
// Load the revision ID from the main DB and cache it in redis
const user = await prisma.admin.findUnique({ where: { pid }, select: { revision: true } });
if (user) {
db_revision = user.revision.toISOString();
await authClient.set(pid, db_revision);
}
}
if (revision !== db_revision || !revision || !db_revision) {
return res.status(403).json({
type: "error",
payload: {
message: "Token could not be verified; It might be expired",
},
});
}
req.auth = {
isAuthenticated: true,
pid: token_payload.pid,
name: token_payload.name,
permission_level: token_payload.permission_level,
groups: token_payload.groups,
revision: token_payload.revision,
}; };
if (!config.controlled) {
next();
}
return true;
};
export const requireAuthentication = _requireAdminAuthentication({ optional: false, controlled: false }); export const requireAuthentication = _requireAdminAuthentication({ optional: false, controlled: false });
type AuthType = "admin" | "teamleader"; type AuthType = "admin" | "teamleader";
@@ -144,37 +144,37 @@ function getAuthTypes(type: AuthType | AuthTypeConfig): AuthType[] {
export const requireConfiguredAuthentication = export const requireConfiguredAuthentication =
(config: AuthConfiguration = { optional: false, type: "admin" }) => (config: AuthConfiguration = { optional: false, type: "admin" }) =>
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const types = getAuthTypes(config.type); const types = getAuthTypes(config.type);
const optional = config.optional; const optional = config.optional;
let adminFinished = false; let adminFinished = false;
let teamleaderFinished = false; let teamleaderFinished = false;
if (types.includes("admin")) { if (types.includes("admin")) {
adminFinished = Boolean(await _requireAdminAuthentication({ optional: true, controlled: true })(req, res, next)); adminFinished = Boolean(await _requireAdminAuthentication({ optional: true, controlled: true })(req, res, next));
if (adminFinished) { if (adminFinished) {
return next(); return next();
}
} }
}
if (types.includes("teamleader")) { if (types.includes("teamleader")) {
teamleaderFinished = Boolean( teamleaderFinished = Boolean(
_requireTeamleaderAuthentication({ optional: true, controlled: true })(req, res, next) _requireTeamleaderAuthentication({ optional: true, controlled: true })(req, res, next)
); );
if (teamleaderFinished) { if (teamleaderFinished) {
return next(); return next();
}
} }
}
if (!config.optional) { if (!config.optional) {
throw new AuthError("No sufficient authorization was provided for this operation"); throw new AuthError("No sufficient authorization was provided for this operation");
} }
next(); next();
}; };
export function requireResponsibleForGroups(auth: AuthJWTPayload | undefined, groupPids: string[] | string) { export function requireResponsibleForGroups(auth: AuthJWTPayload | undefined, groupPids: string[] | string) {
if (auth?.permission_level === "ELEVATED") { if (auth?.permission_level === "ELEVATED") {
@@ -190,7 +190,7 @@ export function requireResponsibleForGroups(auth: AuthJWTPayload | undefined, gr
throw new AuthError("The provided authorization is not valid for the requested operation!"); throw new AuthError("The provided authorization is not valid for the requested operation!");
}); });
} else { } else {
if (auth?.groups.includes(groupPids)) { if (!auth?.groups.includes(groupPids)) {
throw new AuthError("The provided authorization is not valid for the requested operation!"); throw new AuthError("The provided authorization is not valid for the requested operation!");
} }
} }
+2 -2
View File
@@ -7,7 +7,7 @@ const SchemaVersion = z.enum(["1.0"]);
const TimeUnit = z.enum(["days", "hours", "minutes", "seconds", "milliseconds"]); const TimeUnit = z.enum(["days", "hours", "minutes", "seconds", "milliseconds"]);
const DurationSchema = z export const DurationSchema = z
.object({ .object({
type: z.literal("duration"), type: z.literal("duration"),
min: z.number().int({ message: "min must be an integer (relative to smallestUnit)" }), min: z.number().int({ message: "min must be an integer (relative to smallestUnit)" }),
@@ -17,7 +17,7 @@ const DurationSchema = z
}) })
.refine(({ min, max }) => min < max, { message: "min must be smaller than max" }); .refine(({ min, max }) => min < max, { message: "min must be smaller than max" });
const PointSchema = z export const PointSchema = z
.object({ .object({
type: z.literal("points"), type: z.literal("points"),
min: z.number(), min: z.number(),