From 6263ca3556e09784823ccdb1a9c29156df1356d0 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Thu, 22 Jun 2023 14:01:00 +0200 Subject: [PATCH] fix(joinGameHandler): Allow teachers and admins to join any game console --- .../play/GameConsole/GameConsoleView.tsx | 1 + .../src/modules/play/context/playContext.tsx | 4 ++ .../eventHandlers/joinGameHandler.ts | 38 +++++++++++++++++-- .../src/modules/websocket/services/index.ts | 1 + .../websocket/services/roleServices.ts | 16 ++++++++ .../server/src/modules/websocket/types.ts | 1 + 6 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 packages/server/src/modules/websocket/services/roleServices.ts diff --git a/packages/client/src/modules/play/GameConsole/GameConsoleView.tsx b/packages/client/src/modules/play/GameConsole/GameConsoleView.tsx index 117d89eb..7a7dee98 100644 --- a/packages/client/src/modules/play/GameConsole/GameConsoleView.tsx +++ b/packages/client/src/modules/play/GameConsole/GameConsoleView.tsx @@ -15,6 +15,7 @@ import { useTranslation } from "../../translations/useTranslation"; export { GameConsoleView }; +// TODO: protect page using user role. function GameConsoleView() { const [selectedScreen, setSelectedScreen] = useState("Teams"); return ( diff --git a/packages/client/src/modules/play/context/playContext.tsx b/packages/client/src/modules/play/context/playContext.tsx index 014b1be8..303ee290 100644 --- a/packages/client/src/modules/play/context/playContext.tsx +++ b/packages/client/src/modules/play/context/playContext.tsx @@ -418,6 +418,10 @@ function useGameSocket({ setIsInitialised(true); }); + newSocket.on("game:leave", () => { + navigate("/play/my-games"); + }); + newSocket.on("game:update", ({ update }: { update: Partial }) => { setGame((previous) => { if (previous === null) { diff --git a/packages/server/src/modules/websocket/eventHandlers/joinGameHandler.ts b/packages/server/src/modules/websocket/eventHandlers/joinGameHandler.ts index 74de20b2..c940f301 100644 --- a/packages/server/src/modules/websocket/eventHandlers/joinGameHandler.ts +++ b/packages/server/src/modules/websocket/eventHandlers/joinGameHandler.ts @@ -10,8 +10,12 @@ import { gameServices, playerServices, productionActionServices, + roleServices, teamServices, } from "../services"; +import { BusinessError } from "../../utils/businessError"; +import { Game } from "../../games/types"; +import { User } from "../../users/types"; export { handleJoinGame }; @@ -29,10 +33,8 @@ function handleJoinGame(io: Server, socket: Socket) { socket.data.gameId = gameId; socket.data.user = user; - const isPlayer = game?.teacherId !== user.id; - let gameInitData: GameInitEmitted; - if (isPlayer) { + if (await hasPlayerAccess(user, game)) { const player = await playerServices.findOne(gameId, user.id, { include: { user: true, @@ -82,7 +84,7 @@ function handleJoinGame(io: Server, socket: Socket) { players: [player], teams: [team], }; - } else { + } else if (await hasTeacherAccess(user, game)) { const [players, teams, consumptionActions, productionActions] = await Promise.all([ playerServices.findMany(gameId, { @@ -116,9 +118,37 @@ function handleJoinGame(io: Server, socket: Socket) { players, teams, }; + } else { + socket.emit("game:leave"); + throw new BusinessError( + `User ${user.id} is not authorized to join game ${game.id}` + ); } socket.emit("game:init", gameInitData); }) ); } + +async function hasTeacherAccess(user: User, game: Game) { + if (game.teacherId === user.id) { + return true; + } + + // TODO: protect game access with a list of allowed teachers? + const userRole = await roleServices.findOne(user.roleId); + if (["admin", "teacher"].includes(userRole?.name || "")) { + return true; + } + + return false; +} + +async function hasPlayerAccess(user: User, game: Game) { + const player = await playerServices.findOne(game.id, user.id); + if (player) { + return true; + } + + return false; +} diff --git a/packages/server/src/modules/websocket/services/index.ts b/packages/server/src/modules/websocket/services/index.ts index 49c60aa6..601dd8ff 100644 --- a/packages/server/src/modules/websocket/services/index.ts +++ b/packages/server/src/modules/websocket/services/index.ts @@ -5,5 +5,6 @@ export * from "./playerActionServices"; export * from "./playerServices"; export * from "./productionActionServices"; export * from "./profileServices"; +export * from "./roleServices"; export * from "./teamActionServices"; export * from "./teamServices"; diff --git a/packages/server/src/modules/websocket/services/roleServices.ts b/packages/server/src/modules/websocket/services/roleServices.ts new file mode 100644 index 00000000..9ed0c104 --- /dev/null +++ b/packages/server/src/modules/websocket/services/roleServices.ts @@ -0,0 +1,16 @@ +import { Role } from "@prisma/client"; +import { database } from "../../../database"; + +const model = database.role; + +export const roleServices = { + findOne, +}; + +async function findOne(id: number): Promise { + return model.findFirst({ + where: { + id, + }, + }); +} diff --git a/packages/server/src/modules/websocket/types.ts b/packages/server/src/modules/websocket/types.ts index 69bac0b1..d760212e 100644 --- a/packages/server/src/modules/websocket/types.ts +++ b/packages/server/src/modules/websocket/types.ts @@ -27,6 +27,7 @@ type ListenEvents = any; type EmitEvents = { "game:init": (args: GameInitEmitted) => void; + "game:leave": () => void; "game:update": (args: { update: Partial }) => void; "player:update": (args: { updates: Partial[] }) => void; "player-actions:update": (args: {