refactor: update Docker base images, enhance image upload security, and implement shared form client logic and validation

This commit is contained in:
2026-08-01 15:57:55 +07:00 Unverified
parent e243a75d33
commit b557879928
14 changed files with 505 additions and 318 deletions
+37 -1
View File
@@ -9,7 +9,10 @@ const ALLOWED_IMAGE_MIME_TYPES = new Set([
const ALLOWED_IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "gif"]);
export function validateImageUploadFile(file: Pick<File, "name" | "type" | "size">) {
export function validateImageUploadFile(
file: Pick<File, "name" | "type" | "size">,
bytes?: Uint8Array
) {
if (!ALLOWED_IMAGE_MIME_TYPES.has(file.type)) {
throw new Error("Only PNG, JPG, WebP, and GIF images are allowed");
}
@@ -18,10 +21,43 @@ export function validateImageUploadFile(file: Pick<File, "name" | "type" | "size
throw new Error("Image must be 50MB or smaller");
}
if (file.size === 0) {
throw new Error("Image cannot be empty");
}
const extension = (file.name.split(".").pop() || "").toLowerCase();
if (!ALLOWED_IMAGE_EXTENSIONS.has(extension)) {
throw new Error("Invalid image extension");
}
if (bytes && !hasMatchingImageSignature(file.type, bytes)) {
throw new Error("Image contents do not match the declared image type");
}
}
function hasMatchingImageSignature(type: string, bytes: Uint8Array) {
if (type === "image/png") {
return bytes.length >= 8 &&
bytes.slice(0, 8).every((value, index) => value === [137, 80, 78, 71, 13, 10, 26, 10][index]);
}
if (type === "image/jpeg") {
return bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;
}
if (type === "image/gif") {
return bytes.length >= 6 &&
(String.fromCharCode(...bytes.slice(0, 6)) === "GIF87a" ||
String.fromCharCode(...bytes.slice(0, 6)) === "GIF89a");
}
if (type === "image/webp") {
return bytes.length >= 12 &&
String.fromCharCode(...bytes.slice(0, 4)) === "RIFF" &&
String.fromCharCode(...bytes.slice(8, 12)) === "WEBP";
}
return false;
}
export function cdnUrl(id: string | null | undefined) {