fix: security file upload (#3923)
* fix: add check user authentication * fix: add request body multipart boundary validation
This commit is contained in:
parent
059fd9403a
commit
f873309004
4 changed files with 44 additions and 2 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -273,3 +273,5 @@ src/frontend/temp
|
||||||
*-shm
|
*-shm
|
||||||
*-wal
|
*-wal
|
||||||
.history
|
.history
|
||||||
|
|
||||||
|
.dspy_cache/
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ def get_flow_id(
|
||||||
async def upload_file(
|
async def upload_file(
|
||||||
file: UploadFile,
|
file: UploadFile,
|
||||||
flow_id: UUID = Depends(get_flow_id),
|
flow_id: UUID = Depends(get_flow_id),
|
||||||
|
current_user=Depends(get_current_active_user),
|
||||||
|
session=Depends(get_session),
|
||||||
storage_service: StorageService = Depends(get_storage_service),
|
storage_service: StorageService = Depends(get_storage_service),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
|
@ -50,6 +52,10 @@ async def upload_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
flow_id_str = str(flow_id)
|
flow_id_str = str(flow_id)
|
||||||
|
flow = session.get(Flow, flow_id_str)
|
||||||
|
if flow.user_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="You don't have access to this flow")
|
||||||
|
|
||||||
file_content = await file.read()
|
file_content = await file.read()
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
file_name = file.filename or hashlib.sha256(file_content).hexdigest()
|
file_name = file.filename or hashlib.sha256(file_content).hexdigest()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
@ -8,7 +9,7 @@ from pathlib import Path
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import nest_asyncio # type: ignore
|
import nest_asyncio # type: ignore
|
||||||
from fastapi import FastAPI, HTTPException, Request, Response
|
from fastapi import FastAPI, HTTPException, Request, Response, status
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse, JSONResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
@ -127,6 +128,39 @@ def create_app():
|
||||||
)
|
)
|
||||||
app.add_middleware(JavaScriptMIMETypeMiddleware)
|
app.add_middleware(JavaScriptMIMETypeMiddleware)
|
||||||
|
|
||||||
|
@app.middleware("http")
|
||||||
|
async def check_boundary(request: Request, call_next):
|
||||||
|
if "/api/v1/files/upload" in request.url.path:
|
||||||
|
content_type = request.headers.get("Content-Type")
|
||||||
|
|
||||||
|
if not content_type or "multipart/form-data" not in content_type or "boundary=" not in content_type:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||||
|
content={"detail": "Content-Type header must be 'multipart/form-data' with a boundary parameter."},
|
||||||
|
)
|
||||||
|
|
||||||
|
boundary = content_type.split("boundary=")[-1].strip()
|
||||||
|
|
||||||
|
if not re.match(r"^[\w\-]{1,70}$", boundary):
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||||
|
content={"detail": "Invalid boundary format"},
|
||||||
|
)
|
||||||
|
|
||||||
|
body = await request.body()
|
||||||
|
|
||||||
|
boundary_start = f"--{boundary}".encode()
|
||||||
|
boundary_end = f"--{boundary}--\r\n".encode()
|
||||||
|
|
||||||
|
if not body.startswith(boundary_start) or not body.endswith(boundary_end):
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||||
|
content={"detail": "Invalid multipart formatting"},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await call_next(request)
|
||||||
|
return response
|
||||||
|
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
async def flatten_query_string_lists(request: Request, call_next):
|
async def flatten_query_string_lists(request: Request, call_next):
|
||||||
flattened: list[tuple[str, str]] = []
|
flattened: list[tuple[str, str]] = []
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class CustomComponent(Component):
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const text of allVisibleTexts) {
|
for (const text of allVisibleTexts) {
|
||||||
await expect(page.getByText(text)).toBeVisible();
|
await expect(page.getByText(text).last()).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.locator(".ag-cell-value").first().click();
|
await page.locator(".ag-cell-value").first().click();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue