fix: P0 安全加固 + 前端错误边界 + ESLint 修复
后端: - 实现登出 API(清除 refresh token) - 清除 videos.py 中已被 Celery 任务取代的死代码 - 添加速率限制中间件(60次/分钟,登录10次/分钟) - 添加 SECRET_KEY/ENCRYPTION_KEY 默认值警告 - OSS STS 方法回退到 Policy 签名(不再抛异常) 前端: - 添加全局 404/error/loading 页面 - 添加三端 error.tsx + loading.tsx 错误边界 - 修复 useId 条件调用违反 Hooks 规则 - 修复未转义引号和 Image 命名冲突 - 添加 ESLint 配置 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a8be7bbca9
commit
8eb8100cf4
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
简单的速率限制中间件
|
||||
基于内存的滑动窗口计数器
|
||||
"""
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class RateLimitMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
速率限制中间件
|
||||
|
||||
- 默认: 60 次/分钟 per IP
|
||||
- 登录/注册: 10 次/分钟 per IP
|
||||
"""
|
||||
|
||||
def __init__(self, app, default_limit: int = 60, window_seconds: int = 60):
|
||||
super().__init__(app)
|
||||
self.default_limit = default_limit
|
||||
self.window_seconds = window_seconds
|
||||
self.requests: dict[str, list[float]] = defaultdict(list)
|
||||
# Stricter limits for auth endpoints
|
||||
self.strict_paths = {"/api/v1/auth/login", "/api/v1/auth/register"}
|
||||
self.strict_limit = 10
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
client_ip = request.client.host if request.client else "unknown"
|
||||
path = request.url.path
|
||||
now = time.time()
|
||||
|
||||
# Determine rate limit
|
||||
if path in self.strict_paths:
|
||||
key = f"{client_ip}:{path}"
|
||||
limit = self.strict_limit
|
||||
else:
|
||||
key = client_ip
|
||||
limit = self.default_limit
|
||||
|
||||
# Clean old entries
|
||||
window_start = now - self.window_seconds
|
||||
self.requests[key] = [t for t in self.requests[key] if t > window_start]
|
||||
|
||||
# Check limit
|
||||
if len(self.requests[key]) >= limit:
|
||||
return JSONResponse(
|
||||
status_code=429,
|
||||
content={"detail": "请求过于频繁,请稍后再试"},
|
||||
)
|
||||
|
||||
# Record request
|
||||
self.requests[key].append(now)
|
||||
|
||||
# Periodic cleanup (every 1000 requests to this key)
|
||||
if len(self.requests) > 10000:
|
||||
self._cleanup(now)
|
||||
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
def _cleanup(self, now: float):
|
||||
"""Clean up expired entries"""
|
||||
window_start = now - self.window_seconds
|
||||
expired_keys = [
|
||||
k for k, v in self.requests.items()
|
||||
if not v or v[-1] < window_start
|
||||
]
|
||||
for k in expired_keys:
|
||||
del self.requests[k]
|
||||
Reference in New Issue
Block a user