fix: actual mime type checking in submit route

This commit is contained in:
trafficlunar 2025-04-20 17:48:56 +01:00
parent 48c99e442f
commit b17478a718
3 changed files with 80 additions and 1 deletions

View file

@ -19,6 +19,7 @@
"dayjs": "^1.11.13",
"downshift": "^9.0.9",
"embla-carousel-react": "^8.6.0",
"file-type": "^20.4.1",
"jsqr": "^1.4.0",
"next": "15.2.4",
"next-auth": "5.0.0-beta.25",

View file

@ -35,6 +35,9 @@ importers:
embla-carousel-react:
specifier: ^8.6.0
version: 8.6.0(react@19.1.0)
file-type:
specifier: ^20.4.1
version: 20.4.1
jsqr:
specifier: ^1.4.0
version: 1.4.0
@ -792,6 +795,13 @@ packages:
'@tailwindcss/postcss@4.1.3':
resolution: {integrity: sha512-6s5nJODm98F++QT49qn8xJKHQRamhYHfMi3X7/ltxiSQ9dyRsaFSfFkfaMsanWzf+TMYQtbk8mt5f6cCVXJwfg==}
'@tokenizer/inflate@0.2.7':
resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==}
engines: {node: '>=18'}
'@tokenizer/token@0.3.0':
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
'@trafficlunar/asmcrypto.js@1.0.2':
resolution: {integrity: sha512-Iv0EnYsr8PdZo4iipddzZAV790VbQoDAEr9ZdNvwlrtaAZDoSl5ivweMOBEHV2smMzFqkRlSQIyo+HKUPyFkjQ==}
@ -1382,6 +1392,9 @@ packages:
picomatch:
optional: true
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@ -1390,6 +1403,10 @@ packages:
resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
engines: {node: '>= 12'}
file-type@20.4.1:
resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==}
engines: {node: '>=18'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@ -1492,6 +1509,9 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@ -1879,6 +1899,10 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
peek-readable@7.0.0:
resolution: {integrity: sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==}
engines: {node: '>=18'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -2132,6 +2156,10 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
strtok3@10.2.2:
resolution: {integrity: sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==}
engines: {node: '>=18'}
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@ -2176,6 +2204,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
token-types@6.0.0:
resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==}
engines: {node: '>=14.16'}
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@ -2213,6 +2245,10 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
uint8array-extras@1.4.0:
resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==}
engines: {node: '>=18'}
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@ -2788,6 +2824,16 @@ snapshots:
postcss: 8.5.3
tailwindcss: 4.1.3
'@tokenizer/inflate@0.2.7':
dependencies:
debug: 4.4.0
fflate: 0.8.2
token-types: 6.0.0
transitivePeerDependencies:
- supports-color
'@tokenizer/token@0.3.0': {}
'@trafficlunar/asmcrypto.js@1.0.2': {}
'@tybys/wasm-util@0.9.0':
@ -3572,6 +3618,8 @@ snapshots:
optionalDependencies:
picomatch: 4.0.2
fflate@0.8.2: {}
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@ -3580,6 +3628,15 @@ snapshots:
dependencies:
tslib: 2.8.1
file-type@20.4.1:
dependencies:
'@tokenizer/inflate': 0.2.7
strtok3: 10.2.2
token-types: 6.0.0
uint8array-extras: 1.4.0
transitivePeerDependencies:
- supports-color
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@ -3687,6 +3744,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
ieee754@1.2.1: {}
ignore@5.3.2: {}
import-fresh@3.3.1:
@ -4058,6 +4117,8 @@ snapshots:
path-parse@1.0.7: {}
peek-readable@7.0.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@ -4390,6 +4451,11 @@ snapshots:
strip-json-comments@3.1.1: {}
strtok3@10.2.2:
dependencies:
'@tokenizer/token': 0.3.0
peek-readable: 7.0.0
styled-jsx@5.1.6(react@19.1.0):
dependencies:
client-only: 0.0.1
@ -4422,6 +4488,11 @@ snapshots:
dependencies:
is-number: 7.0.0
token-types@6.0.0:
dependencies:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
ts-api-utils@2.1.0(typescript@5.8.3):
dependencies:
typescript: 5.8.3
@ -4474,6 +4545,8 @@ snapshots:
typescript@5.8.3: {}
uint8array-extras@1.4.0: {}
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4

View file

@ -1,6 +1,7 @@
// import * as tf from "@tensorflow/tfjs-node";
// import * as nsfwjs from "nsfwjs";
import sharp from "sharp";
import { fileTypeFromBuffer } from "file-type";
const MIN_IMAGE_DIMENSIONS = 128;
const MAX_IMAGE_DIMENSIONS = 1024;
@ -23,12 +24,16 @@ const MAX_IMAGE_SIZE = 1024 * 1024; // 1 MB
export async function validateImage(file: File): Promise<{ valid: boolean; error?: string; status?: number }> {
if (!file || file.size == 0) return { valid: false, error: "Empty image file" };
if (!file.type.startsWith("image/")) return { valid: false, error: "Invalid file type. Only images are allowed" };
if (file.size > MAX_IMAGE_SIZE)
return { valid: false, error: `One or more of your images are too large. Maximum size is ${MAX_IMAGE_SIZE / (1024 * 1024)}MB` };
try {
const buffer = Buffer.from(await file.arrayBuffer());
// Check mime type
const fileType = await fileTypeFromBuffer(buffer);
if (!fileType || !fileType.mime.startsWith("image/")) return { valid: false, error: "Invalid image file type. Only actual images are allowed" };
const metadata = await sharp(buffer).metadata();
// Check image dimensions