From 379a9a115bd349634923f5ad5a87d43b61223da5 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Sun, 15 Mar 2026 17:34:19 +0100 Subject: [PATCH] fix(security): isActive defense-in-depth, MIME magic bytes upload, tenantId=origins OAuth --- backend/src/middleware/auth.middleware.ts | 5 +++++ backend/src/routes/admin.routes.ts | 24 +++++++++++++++++++++++ frontend/src/pages/LoginPage.tsx | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/src/middleware/auth.middleware.ts b/backend/src/middleware/auth.middleware.ts index fa9d573..5bf17dd 100644 --- a/backend/src/middleware/auth.middleware.ts +++ b/backend/src/middleware/auth.middleware.ts @@ -64,6 +64,11 @@ export const requireAuth = async ( return; } + if (!data.data.user.isActive) { + res.status(401).json({ success: false, error: "ACCOUNT_DISABLED", message: "Account is disabled" }); + return; + } + (req as AuthenticatedRequest).user = data.data.user; next(); } catch (err) { diff --git a/backend/src/routes/admin.routes.ts b/backend/src/routes/admin.routes.ts index 340f7fa..a33c303 100644 --- a/backend/src/routes/admin.routes.ts +++ b/backend/src/routes/admin.routes.ts @@ -1,6 +1,23 @@ import { Router, Request, Response } from "express"; import path from "path"; import fs from "fs"; + +/** + * Vérifie les magic bytes d'un fichier vidéo déjà écrit sur disque. + * MP4 : bytes 4-7 = 'ftyp' (0x66 0x74 0x79 0x70) + * WebM : bytes 0-3 = 0x1A 0x45 0xDF 0xA3 + */ +const isValidVideoMagicBytes = (filePath: string): boolean => { + const fd = fs.openSync(filePath, "r"); + const buf = Buffer.alloc(12); + fs.readSync(fd, buf, 0, 12, 0); + fs.closeSync(fd); + + const isWebM = buf[0] === 0x1a && buf[1] === 0x45 && buf[2] === 0xdf && buf[3] === 0xa3; + const isMP4 = buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70; + + return isMP4 || isWebM; +}; import multer, { FileFilterCallback } from "multer"; import { AppDataSource } from "../config/data-source"; import { Video } from "../entities/Video"; @@ -71,6 +88,13 @@ router.post( res.status(400).json({ success: false, error: "NO_FILE" }); return; } + + if (!isValidVideoMagicBytes(req.file.path)) { + fs.unlinkSync(req.file.path); + res.status(415).json({ success: false, error: "INVALID_FILE_CONTENT", message: "File content does not match a valid video format" }); + return; + } + res.status(201).json({ success: true, data: { storageKey: req.file.filename, storageType: "local" }, diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 69d2bdf..73cd7dc 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -27,7 +27,7 @@ export default function LoginPage() { function handleOAuth(providerId: string) { if (oauthLoading) return; setOauthLoading(providerId); - window.location.href = `${base}/api/v1/oauth/${providerId}?redirectUrl=${redirectUrl}`; + window.location.href = `${base}/api/v1/oauth/${providerId}?redirectUrl=${redirectUrl}&tenantId=origins`; } async function handleSubmit(e: React.FormEvent) {