mirror of
https://github.com/detleph/server.git
synced 2026-09-04 08:36:06 +02:00
@@ -35,3 +35,9 @@ services:
|
||||
- DATABASE_URL=postgresql://server:${DATABASE_PASSWORD}@postgres:5432/management?schema=public
|
||||
- DATABASE_USER=server
|
||||
- DATABASE_PASSWORD=${DATABASE_PASSWORD}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- DOMAIN=${DOMAIN}
|
||||
- MAILUSER=${MAILUSER}
|
||||
- MAILPASSWORD=${MAILPASSWORD}
|
||||
- FRONTEND_MAIL_ENDPOINT=https://josport.at/authteamleader/
|
||||
- ALLOW_ORIGIN=https://josport.at
|
||||
|
||||
@@ -37,7 +37,7 @@ model Discipline {
|
||||
id Int @id @default(autoincrement())
|
||||
pid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String
|
||||
briefDescription String
|
||||
briefDescription String @default("description")
|
||||
fullDescription String?
|
||||
minTeamSize Int
|
||||
maxTeamSize Int
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import prisma from "../lib/prisma";
|
||||
import { string, z } from "zod";
|
||||
import { Request, Response } from "express";
|
||||
import { DataType, generateError, generateInvalidBodyError } from "./common";
|
||||
import { DataType, generateError, generateInvalidBodyError, createInsufficientPermissionsError } from "./common";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||
import NotFoundError from "../Middleware/error/NotFoundError";
|
||||
import { requireLeaderOfTeam, requireResponsibleForParticipant } from "../Middleware/auth/teamleaderAuth";
|
||||
import { requireResponsibleForGroups } from "../Middleware/auth/auth";
|
||||
import { requireConfiguredAuthentication } from "../Middleware/auth/auth";
|
||||
import { isTeamleaderJWTPayload, TeamleaderJWTPayload } from "../Middleware/auth/teamleaderAuth";
|
||||
import { AuthJWTPayload } from "./admin_auth.controller";
|
||||
import AuthError from "../Middleware/error/AuthError";
|
||||
import { getGroupsByTeamPid } from "./team.controller";
|
||||
|
||||
require("express-async-errors");
|
||||
|
||||
@@ -16,17 +21,11 @@ const InitialParticipant = z.object({
|
||||
groupPid: z.string().min(1).uuid(),
|
||||
});
|
||||
|
||||
const returnedParticipant = {
|
||||
const basicParticipant = {
|
||||
pid: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
relevance: true,
|
||||
team: {
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
select: {
|
||||
pid: true,
|
||||
@@ -35,7 +34,76 @@ const returnedParticipant = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
// at: POST api/participants/
|
||||
const returnedParticipant = {
|
||||
...basicParticipant,
|
||||
team: {
|
||||
select: {
|
||||
pid: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const _getAllParticipants = async (res: Response, req: Request, teamPid?: string) => {
|
||||
if (req.teamleader?.isAuthenticated) {
|
||||
await requireLeaderOfTeam(req.teamleader, teamPid);
|
||||
} else if (req.auth?.permission_level == "STANDARD") {
|
||||
if (!teamPid) {
|
||||
throw new AuthError("A STANDARD Admin is not allowed to fetch all Participants!");
|
||||
} else {
|
||||
requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(teamPid));
|
||||
}
|
||||
}
|
||||
|
||||
const participants = await prisma.participant.findMany({
|
||||
where: { team: { pid: teamPid } },
|
||||
select: basicParticipant,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
type: "success",
|
||||
payload: {
|
||||
participants,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getAllParticipants = async (req: Request<{}, {}, {}, { teamPid?: string }>, res: Response) => {
|
||||
return _getAllParticipants(res, req, req.query.teamPid);
|
||||
};
|
||||
|
||||
export const getAllParticipantsParams = async (req: Request<{ teamPid: string }>, res: Response) => {
|
||||
return _getAllParticipants(res, req, req.params.teamPid);
|
||||
};
|
||||
|
||||
export const getParticipantForRole = async (req: Request<{ rolePid: string }>, res: Response) => {
|
||||
const participant = await prisma.participant.findFirst({
|
||||
where: { roles: { some: { pid: req.params.rolePid } } },
|
||||
select: returnedParticipant,
|
||||
});
|
||||
|
||||
if (!participant) {
|
||||
return res.status(404).json({
|
||||
type: "error",
|
||||
payload: {
|
||||
message: `Could not find a participant for the role with the ID '${req.params.rolePid}'`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (req.teamleader?.isAuthenticated) {
|
||||
await requireLeaderOfTeam(req.teamleader, participant?.team.pid);
|
||||
} else if (req.auth?.permission_level == "STANDARD") {
|
||||
requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(participant?.team.pid));
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
type: "success",
|
||||
payload: { participant },
|
||||
});
|
||||
};
|
||||
|
||||
// at: POST api/teams/:teamPid/participant/
|
||||
export const createParticipant = async (req: Request<{ teamPid: string }>, res: Response) => {
|
||||
const { teamPid } = req.params;
|
||||
|
||||
|
||||
@@ -11,6 +11,13 @@ import { getGroupsByTeamPid } from "./team.controller";
|
||||
|
||||
require("express-async-errors");
|
||||
|
||||
const basicRole = {
|
||||
pid: true,
|
||||
score: true,
|
||||
schema: { select: { pid: true } },
|
||||
participant: { select: { pid: true, firstName: true, lastName: true } },
|
||||
};
|
||||
|
||||
const detailedRole = {
|
||||
pid: true,
|
||||
score: true,
|
||||
@@ -61,18 +68,13 @@ export async function getRolesForTeam(req: Request<{ pid: string }>, res: Respon
|
||||
|
||||
if (req.teamleader?.isAuthenticated) {
|
||||
await requireLeaderOfTeam(req.teamleader, pid);
|
||||
} else {
|
||||
} else if (req.auth?.permission_level == "STANDARD") {
|
||||
requireResponsibleForGroups(req.auth, await getGroupsByTeamPid(pid));
|
||||
}
|
||||
|
||||
const roles = await prisma.role.findMany({
|
||||
where: { team: { pid } },
|
||||
select: {
|
||||
pid: true,
|
||||
score: true,
|
||||
schema: { select: { pid: true } },
|
||||
participant: { select: { pid: true, firstName: true, lastName: true } },
|
||||
},
|
||||
select: basicRole,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
@@ -83,6 +85,29 @@ export async function getRolesForTeam(req: Request<{ pid: string }>, res: Respon
|
||||
});
|
||||
}
|
||||
|
||||
export async function getRole(req: Request<{ rolePid: string }>, res: Response) {
|
||||
const rolePid = req.params.rolePid;
|
||||
|
||||
const role = await prisma.role.findUnique({ where: { pid: rolePid }, select: detailedRole });
|
||||
|
||||
if (!role) {
|
||||
throw new NotFoundError("role", rolePid);
|
||||
}
|
||||
|
||||
if (req.teamleader?.isAuthenticated) {
|
||||
await requireLeaderOfTeam(req.teamleader, role.team.pid);
|
||||
} else if (req.auth?.permission_level == "STANDARD" && role.participant?.pid !== undefined) {
|
||||
requireResponsibleForGroups(req.auth, await getGroupByParticipantPid(role.participant?.pid));
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
type: "success",
|
||||
payload: {
|
||||
role,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const AssignParticipantToRoleBody = z.object({
|
||||
participantPid: z.string().uuid(),
|
||||
});
|
||||
|
||||
@@ -136,7 +136,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") {
|
||||
return res.status(403).json(createInsufficientPermissionsError());
|
||||
}
|
||||
@@ -177,7 +177,27 @@ export const UpdateRoleSchema = async (req: Request<{ pid: string }>, res: Respo
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new NotFoundError("discipline", pid);
|
||||
throw new NotFoundError("role-schema", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRoleSchema = async (req: Request<{ pid: string }>, res: Response) => {
|
||||
if (req.auth?.permission_level !== "ELEVATED") {
|
||||
return res.status(403).json(createInsufficientPermissionsError());
|
||||
}
|
||||
|
||||
const { pid } = req.params;
|
||||
|
||||
try {
|
||||
await prisma.roleSchema.delete({ where: { pid } });
|
||||
|
||||
return res.status(204).end();
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === "P2025") {
|
||||
throw new NotFoundError("role-schema", pid);
|
||||
}
|
||||
|
||||
throw e;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response } from "express";
|
||||
import prisma from "../lib/prisma";
|
||||
import { DataType, generateInvalidBodyError } from "./common";
|
||||
import { createInsufficientPermissionsError, DataType, generateInvalidBodyError } from "./common";
|
||||
import { requireLeaderOfTeam } from "../Middleware/auth/teamleaderAuth";
|
||||
import { TeamBody } from "./user_auth.controller";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
@@ -97,7 +97,7 @@ export const register = async (req: Request<{}, {}, CreateTeamBody>, res: Respon
|
||||
};
|
||||
|
||||
export const requestToken = async (req: Request, res: Response) => {
|
||||
const data = z.object({ teamId: z.string().min(1) }).safeParse(req);
|
||||
const data = z.object({ teamId: z.string().min(1) }).safeParse(req.body);
|
||||
|
||||
if (data.success == false) {
|
||||
return res.status(400).json(generateInvalidBodyError({ teamId: DataType.STRING }, data.error));
|
||||
@@ -130,7 +130,7 @@ export const requestToken = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
export const requestTokenEmail = async (req: Request, res: Response) => {
|
||||
const data = z.object({ email: z.string().min(1) }).safeParse(req);
|
||||
const data = z.object({ email: z.string().email() }).safeParse(req.body);
|
||||
|
||||
if (data.success == false) {
|
||||
return res.status(400).json(generateInvalidBodyError({ email: DataType.STRING }, data.error));
|
||||
@@ -149,23 +149,24 @@ export const requestTokenEmail = async (req: Request, res: Response) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!teams) {
|
||||
return res.status(404).json(generateError("Team does not exist!"));
|
||||
if (teams.length <= 0) {
|
||||
// REVIEW: Potential for time-based attacks
|
||||
return res
|
||||
.status(200)
|
||||
.json({ type: "sucess", payload: { message: "If a team with the provided email exist, the token was sent!" } });
|
||||
}
|
||||
|
||||
const team = teams[0]; //REVIEW: maybe a email should be only able to be responsible for one team
|
||||
|
||||
if (!team) {
|
||||
return res.status(404).json(generateError("Team does not exist!"));
|
||||
}
|
||||
|
||||
const usid = nanoid();
|
||||
|
||||
(await mailClient).set(usid, team.pid);
|
||||
|
||||
verificationMail(team.leaderEmail, team.discipline.name, usid);
|
||||
|
||||
res.status(200).json({ type: "sucess", payload: { message: "Email sent!" } });
|
||||
res
|
||||
.status(200)
|
||||
.json({ type: "sucess", payload: { message: "If a team with the provided email exist, the token was sent!" } });
|
||||
};
|
||||
|
||||
export const verifyEmail = async (req: Request, res: Response) => {
|
||||
|
||||
@@ -10,6 +10,10 @@ export interface TeamleaderJWTPayload {
|
||||
team: string;
|
||||
}
|
||||
|
||||
export function isTeamleaderJWTPayload(payload: any): payload is TeamleaderJWTPayload {
|
||||
return typeof payload.team === "string";
|
||||
}
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
|
||||
export function generateTeamleaderJWT(teamleader: Team) {
|
||||
@@ -86,8 +90,13 @@ export const _requireTeamleaderAuthentication =
|
||||
|
||||
export const requireTeamleaderAuthentication = _requireTeamleaderAuthentication({ optional: false, controlled: false });
|
||||
|
||||
export async function requireLeaderOfTeam(auth: TeamleaderJWTPayload | undefined, teamPid: string) {
|
||||
export async function requireLeaderOfTeam(auth: TeamleaderJWTPayload | undefined, teamPid?: string) {
|
||||
if (!teamPid) {
|
||||
throw new AuthError("Could not match IDs");
|
||||
}
|
||||
|
||||
await checkTeamExistence(teamPid);
|
||||
|
||||
if (auth?.team !== teamPid) {
|
||||
throw new AuthError("The provided authorization is not valid for the requested team");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
import express from "express";
|
||||
import { createParticipant, deleteParticipant, updateParticipant } from "../Controllers/participant.controller";
|
||||
import { requireConfiguredAuthentication } from "../Middleware/auth/auth";
|
||||
import teamRouter from "./team.routes";
|
||||
import roleRouter from "./role.routes";
|
||||
import {
|
||||
createParticipant,
|
||||
deleteParticipant,
|
||||
getAllParticipants,
|
||||
getAllParticipantsParams,
|
||||
getParticipantForRole,
|
||||
updateParticipant,
|
||||
} from "../Controllers/participant.controller";
|
||||
import { requireAuthentication, requireConfiguredAuthentication } from "../Middleware/auth/auth";
|
||||
|
||||
require("express-async-errors");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
teamRouter.post<"/:teamPid/participants", { teamPid: string }>(
|
||||
"/:teamPid/participants",
|
||||
router.get(
|
||||
"/",
|
||||
requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }),
|
||||
getAllParticipants
|
||||
);
|
||||
|
||||
teamRouter.get(
|
||||
"/:teamPid/particpants",
|
||||
requireConfiguredAuthentication({ type: { admin: true, teamleader: true }, optional: false }),
|
||||
getAllParticipantsParams
|
||||
);
|
||||
|
||||
roleRouter.get(
|
||||
"/:rolePid/participant",
|
||||
requireConfiguredAuthentication({ type: { admin: true, teamleader: true }, optional: false }),
|
||||
getParticipantForRole
|
||||
);
|
||||
|
||||
teamRouter.post<"/:teamPid/participants/", { teamPid: string }>(
|
||||
"/:teamPid/participants/",
|
||||
requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }),
|
||||
createParticipant
|
||||
);
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import Express from "express";
|
||||
import { assignParticipantToRole, getRolesForTeam } from "../Controllers/role.controller";
|
||||
import { requireConfiguredAuthentication } from "../Middleware/auth/auth";
|
||||
import { assignParticipantToRole, getRole, getRolesForTeam } from "../Controllers/role.controller";
|
||||
import { requireAuthentication, requireConfiguredAuthentication } from "../Middleware/auth/auth";
|
||||
import { requireTeamleaderAuthentication } from "../Middleware/auth/teamleaderAuth";
|
||||
import teamRouter from "./team.routes";
|
||||
|
||||
const router = Express.Router();
|
||||
|
||||
router.get(
|
||||
"/:rolePid",
|
||||
requireConfiguredAuthentication({ type: { admin: true, teamleader: true }, optional: false }),
|
||||
getRole
|
||||
);
|
||||
|
||||
router.put<"/:pid/participant", { pid: string }>(
|
||||
"/:pid/participant",
|
||||
requireConfiguredAuthentication({ optional: false, type: { admin: true, teamleader: true } }),
|
||||
|
||||
@@ -2,11 +2,13 @@ import express from "express";
|
||||
import disciplineRouter from "./discipline.routes";
|
||||
import {
|
||||
createRoleSchema,
|
||||
deleteRoleSchema,
|
||||
getAllRoleSchemas,
|
||||
getAllRoleSchemasWithParam,
|
||||
getRoleSchema,
|
||||
updateRoleSchema,
|
||||
} from "../Controllers/role_schema.controller";
|
||||
import { requireAuthentication } from "../Middleware/auth/auth";
|
||||
import { requireAuthentication, requireConfiguredAuthentication } from "../Middleware/auth/auth";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -14,6 +16,10 @@ router.get("/", getAllRoleSchemas);
|
||||
|
||||
router.get("/:pid", getRoleSchema);
|
||||
|
||||
router.patch("/:pid", requireConfiguredAuthentication({ optional: false, type: "admin" }), updateRoleSchema);
|
||||
|
||||
router.delete("/:pid", requireConfiguredAuthentication({ optional: false, type: "admin" }), deleteRoleSchema);
|
||||
|
||||
disciplineRouter.get("/:disciplinePid/role-schemas", getAllRoleSchemasWithParam);
|
||||
|
||||
disciplineRouter.post("/:disciplinePid/role-schemas", requireAuthentication, createRoleSchema);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import express from "express";
|
||||
import { register, requestToken, verifyEmail } from "../Controllers/user_auth.controller";
|
||||
import { register, requestToken, requestTokenEmail, verifyEmail } from "../Controllers/user_auth.controller";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post("/", register);
|
||||
router.get("/verify/:code", verifyEmail);
|
||||
router.get("/token", requestToken);
|
||||
router.post("/token", requestToken);
|
||||
router.post("/email-token", requestTokenEmail);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -81,4 +81,4 @@ describe("events", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
*/
|
||||
|
||||
@@ -15,4 +15,4 @@ describe("mail", () => {
|
||||
info.accepted.length.should.eq(1);
|
||||
});
|
||||
});
|
||||
*/
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user