fix(security): isActive defense-in-depth, MIME magic bytes upload, tenantId=origins OAuth
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 54s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 54s
This commit is contained in:
@@ -64,6 +64,11 @@ export const requireAuth = async (
|
|||||||
return;
|
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;
|
(req as AuthenticatedRequest).user = data.data.user;
|
||||||
next();
|
next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
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 multer, { FileFilterCallback } from "multer";
|
||||||
import { AppDataSource } from "../config/data-source";
|
import { AppDataSource } from "../config/data-source";
|
||||||
import { Video } from "../entities/Video";
|
import { Video } from "../entities/Video";
|
||||||
@@ -71,6 +88,13 @@ router.post(
|
|||||||
res.status(400).json({ success: false, error: "NO_FILE" });
|
res.status(400).json({ success: false, error: "NO_FILE" });
|
||||||
return;
|
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({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: { storageKey: req.file.filename, storageType: "local" },
|
data: { storageKey: req.file.filename, storageType: "local" },
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function LoginPage() {
|
|||||||
function handleOAuth(providerId: string) {
|
function handleOAuth(providerId: string) {
|
||||||
if (oauthLoading) return;
|
if (oauthLoading) return;
|
||||||
setOauthLoading(providerId);
|
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) {
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user