From c0ac68a516c20e50a353fde2205554363f3bbb19 Mon Sep 17 00:00:00 2001 From: Bastien OLLIER Date: Tue, 11 Jun 2024 11:40:13 +0200 Subject: [PATCH 1/3] Keep exiting code when creating a room (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: bastien ollier Reviewed-on: https://codefirst.iut.uca.fr/git/sandkasten/labyrinth/pulls/5 Reviewed-by: Clément FRÉVILLE Co-authored-by: Bastien OLLIER Co-committed-by: Bastien OLLIER --- src/server.ts | 52 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/server.ts b/src/server.ts index 51d8de6..f859d62 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,18 +14,14 @@ await sender.bind(`tcp://127.0.0.1:5557`); const receiver = new Pull(); await receiver.bind(`tcp://127.0.0.1:5558`); - const clients: Record = {}; const generateId = () => nanoid(32); -//let updates: Update[] = []; -//let doc = Text.of(["foo"]); - type Room = { sockets: WebSocket[]; updates: Update[]; doc: Text; -} +}; const rooms: Record = {}; @@ -46,26 +42,25 @@ await fastify.register(cors, { }); fastify.register(websocket); - -fastify.register(async function (fastify: Fastify) { +fastify.register(async function(fastify: Fastify) { fastify.get( "/live/:roomId", { schema: { - params: Type.Object({ - roomId: Type.String(), - }) + params: Type.Object({ + roomId: Type.String(), + }), }, - websocket: true + websocket: true, }, (socket, request) => { const { roomId } = request.params; let room = rooms[roomId]; - if(!room){ + if (!room) { room = { sockets: [], updates: [], - doc: Text.of(['']) + doc: Text.of([""]), }; rooms[roomId] = room; } @@ -102,16 +97,33 @@ fastify.register(async function (fastify: Fastify) { send(socket, requestId, { version: room.updates.length, doc: room.doc.toString() }); } }); - }); -}) + }, + ); +}); /* Route pour créer une room */ -fastify.post( - "/live", - async (request, reply) => { - return generateId(); +fastify.post("/live", { + schema: { + body: Type.Object({ + code: Type.String(), + }), }, -); +}, (request, reply) => { + const { code } = request.body; + let room, roomId; + do { + roomId = generateId(); + room = rooms[roomId]; + } while (room); + + room = { + sockets: [], + updates: [], + doc: Text.of([code]), + }; + rooms[roomId] = room; + return roomId; +}); fastify.post("/run", { schema: { From 13788c553871d1214eae82c6d99507e21bd1cd84 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Wed, 19 Jun 2024 19:50:02 +0200 Subject: [PATCH 2/3] Dockerise --- .dockerignore | 4 ++++ Dockerfile | 14 ++++++++++++++ package.json | 3 ++- src/server.ts | 4 ++-- tsconfig.json | 9 ++++++--- 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4af4093 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +Dockerfile +.dockerignore +*.js diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3567603 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:22-bookworm AS runner +WORKDIR /app +ENV NODE_ENV="production" +COPY package.json package-lock.json ./ +RUN npm install --omit=dev + +FROM runner AS builder +COPY . . +RUN npm install && npm run build && npm prune --omit=dev + +FROM runner +COPY --from=builder /app/dist /app +EXPOSE 3000 +ENTRYPOINT ["node", "src/server.js"] diff --git a/package.json b/package.json index d2e192b..d895272 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "module": "src/server.ts", "type": "module", "scripts": { - "build": "tsc", + "build": "tsc && tsc-alias", "start": "tsx src/server.ts" }, "devDependencies": { "@types/bun": "^1.0.4", + "tsc-alias": "^1.8.10", "tsx": "^4.7.0", "typescript": "^5.3.3" }, diff --git a/src/server.ts b/src/server.ts index f859d62..b4798e0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,7 +5,7 @@ import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; import websocket, { WebSocket } from "@fastify/websocket"; import Fastify, { FastifyReply } from "fastify"; import { nanoid } from "nanoid"; -import { allocateBuffer, getRunner } from "runner"; +import { allocateBuffer, getRunner } from "./runner"; import { Pull, Push } from "zeromq"; import * as db from "./database"; @@ -559,4 +559,4 @@ async function forwardOutput() { } /* Lancer le serveur et la fonction forwardOutput sur le même thread en parallèle */ -await Promise.all([fastify.listen({ port: 3000 }), forwardOutput()]); +await Promise.all([fastify.listen({ port: 3000, host: '0.0.0.0' }), forwardOutput()]); diff --git a/tsconfig.json b/tsconfig.json index 2558e1d..4accc64 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { "compilerOptions": { "baseUrl": "./src", + "outDir": "./dist", "types": ["bun-types"], "lib": ["esnext"], "module": "esnext", "target": "esnext", - "moduleResolution": "bundler", - "noEmit": true, - "allowImportingTsExtensions": true, + "moduleResolution": "Node", + "noEmit": false, "moduleDetection": "force", "strict": true, @@ -18,5 +18,8 @@ "composite": true, "downlevelIteration": true, "allowSyntheticDefaultImports": true + }, + "tsc-alias": { + "resolveFullPaths": true } } From 3c79473164b1737f2d29927462830b7d24da19b1 Mon Sep 17 00:00:00 2001 From: Hugo PRADIER Date: Wed, 19 Jun 2024 22:29:42 +0200 Subject: [PATCH 3/3] Implement Login and Registration (#6) Co-authored-by: hugo.pradier2 Co-authored-by: bastien ollier Co-authored-by: clfreville2 Reviewed-on: https://codefirst.iut.uca.fr/git/sandkasten/labyrinth/pulls/6 Co-authored-by: Hugo PRADIER Co-committed-by: Hugo PRADIER --- package.json | 8 +++- src/database.ts | 46 ++++++++++++++++-- src/server.ts | 121 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 145 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index d895272..e007433 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,13 @@ "type": "module", "scripts": { "build": "tsc && tsc-alias", - "start": "tsx src/server.ts" + "start": "tsx src/server.ts", + "fmt": "dprint fmt" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/bun": "^1.0.4", + "dprint": "^0.46.2", "tsc-alias": "^1.8.10", "tsx": "^4.7.0", "typescript": "^5.3.3" @@ -15,11 +18,14 @@ "dependencies": { "@codemirror/collab": "^6.1.1", "@codemirror/state": "^6.4.1", + "@fastify/cookie": "^9.3.1", "@fastify/cors": "^9.0.0", + "@fastify/session": "^10.9.0", "@fastify/type-provider-typebox": "^4.0.0", "@fastify/websocket": "^10.0.1", "@sinclair/typebox": "^0.32.9", "dprint": "^0.46.1", + "bcrypt": "^5.1.1", "fastify": "^4.27.0", "nanoid": "^5.0.4", "sqlite3": "^5.1.7", diff --git a/src/database.ts b/src/database.ts index 3aa61be..79f02d4 100644 --- a/src/database.ts +++ b/src/database.ts @@ -4,6 +4,11 @@ import sqlite3 from "sqlite3"; const dbDirectory = "./src/db"; const dbFilePath = `${dbDirectory}/database.db`; +export type error = { + errno: number; + code: string; +}; + /* Fonction pour exécuter une requête sur la base de données */ /* Fonction pour exécuter une requête de modification de la base de données (INSERT, UPDATE, DELETE) */ @@ -44,9 +49,9 @@ export function getDB( db: sqlite3.Database, query: string, params: any[], -): Promise { +): Promise { return new Promise((resolve, reject) => { - db.get(query, params, (err, row: any) => { + db.get(query, params, (err, row: T) => { if (err) { reject(err); } else { @@ -112,17 +117,48 @@ export function createRegisteredUserTable(db: sqlite3.Database): Promise { } /* Insérer un utilisateur dans la table registered_user */ -export function insertUser( +export async function insertUser( db: sqlite3.Database, login: string, password: string, permissions: number, -) { +): Promise { const insertUserQuery = `INSERT INTO registered_user (login, password, permissions) VALUES (?, ?, ?)`; - return runDB(db, insertUserQuery, [login, password, permissions]); + try { + await runDB(db, insertUserQuery, [login, password, permissions]); + return true; + } catch (e) { + const error = e as error; + if (error.code === "SQLITE_CONSTRAINT") { + return false; + } else { + throw e; + } + } +} + +/* Vérifier si un utilisateur existe dans la table registered_user */ +export async function verifyUser( + db: sqlite3.Database, + login: string, +): Promise { + const verifyUserQuery = `SELECT login, password FROM registered_user WHERE login = ?`; + + const res = await getDB(db, verifyUserQuery, [login]); + + if (!res) { + return null; + } else { + return res; + } } +export type User = { + login: string; + password: string; +}; + /* Modifier le login d'un utilisateur dans la table registered_user */ export function updateUserLogin( db: sqlite3.Database, diff --git a/src/server.ts b/src/server.ts index b4798e0..27635ad 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,13 +1,16 @@ -import { rebaseUpdates, Update } from "@codemirror/collab"; -import { ChangeSet, Text } from "@codemirror/state"; import cors from "@fastify/cors"; -import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; import websocket, { WebSocket } from "@fastify/websocket"; +import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; import Fastify, { FastifyReply } from "fastify"; import { nanoid } from "nanoid"; import { allocateBuffer, getRunner } from "./runner"; import { Pull, Push } from "zeromq"; +import { ChangeSet, Text } from "@codemirror/state"; +import { Update, rebaseUpdates } from "@codemirror/collab"; import * as db from "./database"; +import bcrypt from "bcrypt"; +import { fastifySession } from "@fastify/session"; +import { fastifyCookie } from "@fastify/cookie"; const sender = new Push(); await sender.bind(`tcp://127.0.0.1:5557`); @@ -39,9 +42,28 @@ const fastify = Fastify({ type Fastify = typeof fastify; await fastify.register(cors, { origin: process.env.ALLOW_ORIGIN || "*", + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE"], }); -fastify.register(websocket); +fastify.register(fastifyCookie); +fastify.register(fastifySession, { + secret: "8jYuS75JZuxb6C72nDtH2cY6hnV4B7i35r5c39gQ3h9G9DApAweBsQ47dU9DGpk5", + cookie: { + secure: false, + sameSite: "none", + partitioned: true, + }, + saveUninitialized: false, + cookieName: "session-id", +}); + +declare module "fastify" { + interface Session { + userKey: string | null; + } +} +fastify.register(websocket); fastify.register(async function(fastify: Fastify) { fastify.get( "/live/:roomId", @@ -180,11 +202,56 @@ fastify.post( }, async (request, reply) => { const { login, password, permissions } = request.body; - db.insertUser(database, login, password, permissions); - reply.send({ success: true }); + + // Hashage du mot de passe + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(password, saltRounds); + + const success = await db.insertUser( + database, + login, + hashedPassword, + permissions + ); + reply.send({ success }); + } +); + +/* Route pour vérifier si un utilisateur existe */ +fastify.post( + "/users/login", + { + schema: { + body: Type.Object({ + login: Type.String(), + password: Type.String(), + }), + }, + }, + async (request, reply) => { + const { login, password } = request.body; + const user = await db.verifyUser(database, login); + + if (user === null || !(await bcrypt.compare(password, user.password))) { + reply.send({ success: false }); + } else { + request.session.userKey = generateId(); + reply.send({ success: true }); + } + + bcrypt.compare(password, user!.password) + .then(res => reply.send({ sucess: res })) + .catch(err => reply.send({ sucess: false })); }, ); +/* Route pour se déconnecter */ +fastify.get("/users/logout", async (request, reply) => { + console.log(request.session.userKey); + request.session.destroy(); + reply.send({ success: true }); +}); + /* Route pour mettre à jour le login d'un utilisateur */ fastify.put( "/users/:id/login", @@ -205,7 +272,7 @@ fastify.put( const { newLogin } = request.body; db.updateUserLogin(database, id, newLogin); reply.send({ success: true }); - }, + } ); /* Route pour mettre à jour le mot de passe d'un utilisateur */ @@ -226,9 +293,13 @@ fastify.put( async (request, reply) => { const { id } = request.params; const { newPassword } = request.body; - db.updateUserPassword(database, id, newPassword); + + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(newPassword, saltRounds); + + await db.updateUserPassword(database, id, hashedPassword); reply.send({ success: true }); - }, + } ); /* Route pour mettre à jour les permissions d'un utilisateur */ @@ -251,7 +322,7 @@ fastify.put( const { newPermissions } = request.body; await db.updateUserPermissions(database, id, newPermissions); reply.send({ success: true }); - }, + } ); /* Route pour supprimer un utilisateur par son ID */ @@ -270,7 +341,7 @@ fastify.delete( const { id } = request.params; await db.deleteUserById(database, id); reply.send({ success: true }); - }, + } ); /* Route pour supprimer un utilisateur par son login */ @@ -287,7 +358,7 @@ fastify.delete( const { login } = request.params; await db.deleteUserByLogin(database, login); reply.send({ success: true }); - }, + } ); /* Route pour supprimer tous les utilisateurs */ @@ -316,9 +387,11 @@ fastify.get( }, async (request, reply) => { const { id } = request.params; - const user = await db.selectUserById(database, id); - reply.send(user); - }, + if (request.session.userKey) { + const user = await db.selectUserById(database, id); + reply.send(user); + } + } ); /* Route pour récupérer un utilisateur par son login */ @@ -335,7 +408,7 @@ fastify.get( const { login } = request.params; const user = await db.selectUserByLogin(database, login); reply.send(user); - }, + } ); /* Route pour créer un language */ @@ -353,7 +426,7 @@ fastify.post( const { designation, version } = request.body; db.insertLanguage(database, designation, version); reply.send({ success: true }); - }, + } ); /* Route pour mettre à jour la désignation d'un language */ @@ -376,7 +449,7 @@ fastify.put( const { newDesignation } = request.body; db.updateLanguageDesignation(database, id, newDesignation); reply.send({ success: true }); - }, + } ); /* Route pour mettre à jour la version d'un language */ @@ -399,7 +472,7 @@ fastify.put( const { newVersion } = request.body; db.updateLanguageVersion(database, id, newVersion); reply.send({ success: true }); - }, + } ); /* Route pour supprimer un language */ @@ -418,7 +491,7 @@ fastify.delete( const { id } = request.params; db.deleteLanguage(database, id); reply.send({ success: true }); - }, + } ); /* Route pour supprimer tous les languages */ @@ -443,7 +516,7 @@ fastify.get( const { id } = request.params; const language = await db.selectLanguageById(database, id); reply.send(language); - }, + } ); /* Route pour récupérer tous les languages */ @@ -469,7 +542,7 @@ fastify.post( const { id_user, link, id_language, code } = request.body; db.insertWork(database, link, id_user, id_language, code); reply.send({ success: true }); - }, + } ); /* Route pour récupérer tous les works */ @@ -500,7 +573,7 @@ fastify.delete( const { id } = request.params; db.deleteWork(database, id); reply.send({ success: true }); - }, + } ); /* Route pour récupérer un work par son ID */ @@ -519,7 +592,7 @@ fastify.get( const { id } = request.params; const work = await db.selectWorkById(database, id); reply.send(work); - }, + } ); /* Forward output est une fonction asynchrone qui permet de récupérer les messages envoyés par le container et de les renvoyer au client */