Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions backend/src/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import logging
import os
from pathlib import Path

import uvicorn
from fastapi import FastAPI, Request
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from module.api import v1
from module.conf import VERSION, settings, setup_logger
from module.security.api import get_current_user

setup_logger(reset=True)
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -38,8 +41,22 @@ def create_app() -> FastAPI:
app = create_app()


@app.get("/posters/{path:path}", tags=["posters"])
@app.get("/posters/{path:path}", tags=["posters"], dependencies=[Depends(get_current_user)])
def posters(path: str):
if ".." in path or path.startswith("/") or "\\" in path:
logger.warning(f"[Poster] Blocked path traversal attempt: {path}")
raise HTTPException(status_code=400, detail="Invalid path")

# 构建安全的文件路径
poster_dir = Path("data") / Path("posters")
post_path = poster_dir / Path(path)

# 确保解析后的路径仍在预期目录内
try:
post_path.resolve().relative_to(poster_dir.resolve())
except ValueError:
logger.warning(f"[Poster] Path outside allowed directory: {path}")
raise HTTPException(status_code=400, detail="Path outside allowed directory")
return FileResponse(f"data/posters/{path}")


Expand All @@ -51,10 +68,40 @@ def posters(path: str):

@app.get("/{path:path}")
def html(request: Request, path: str):
files = os.listdir("dist")
if path in files:
return FileResponse(f"dist/{path}")
"""
安全的SPA静态文件服务
- 防止路径遍历攻击
- 限制只能访问dist目录下的文件
- 对未匹配路由返回SPA入口页面
"""
# 空路径或根路径,返回SPA入口页面
if not path or path == "/":
context = {"request": request}
return templates.TemplateResponse("index.html", context)

# 验证路径安全性 - 阻止路径遍历
if ".." in path or path.startswith("/") or "\\" in path:
logger.warning(f"[Static] Blocked path traversal attempt: {path}")
context = {"request": request}
return templates.TemplateResponse("index.html", context)

# 构建安全的文件路径
dist_dir = Path("dist").resolve()
file_path = (dist_dir / path).resolve()

# 确保解析后的路径仍在预期目录内
try:
file_path.relative_to(dist_dir)
except ValueError:
logger.warning(f"[Static] Path outside allowed directory: {path}")
context = {"request": request}
return templates.TemplateResponse("index.html", context)

# 如果文件存在且是文件,则返回
if file_path.exists() and file_path.is_file():
return FileResponse(file_path)
else:
# 文件不存在,返回SPA入口页面(用于客户端路由)
context = {"request": request}
return templates.TemplateResponse("index.html", context)
else:
Expand Down