mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 12:00:14 +00:00
安全修复和管理员账号密码自定义
This commit is contained in:
@@ -10,6 +10,9 @@ DATABASE_URL=sqlite+aiosqlite:///./qquiz.db
|
||||
# JWT Secret (must be at least 32 characters; generate randomly for production)
|
||||
SECRET_KEY=
|
||||
|
||||
# Default admin username (must be at least 3 characters; default: admin)
|
||||
ADMIN_USERNAME=admin
|
||||
|
||||
# Default admin password (must be at least 12 characters; generate randomly for production)
|
||||
ADMIN_PASSWORD=
|
||||
|
||||
|
||||
@@ -86,6 +86,11 @@ async def init_default_config(db: AsyncSession):
|
||||
"ai_provider": os.getenv("AI_PROVIDER", "openai"),
|
||||
}
|
||||
|
||||
# Validate admin credentials
|
||||
admin_username = os.getenv("ADMIN_USERNAME", "admin")
|
||||
if not admin_username or len(admin_username) < 3:
|
||||
raise ValueError("ADMIN_USERNAME must be at least 3 characters long")
|
||||
|
||||
admin_password = os.getenv("ADMIN_PASSWORD")
|
||||
if not admin_password or len(admin_password) < 12:
|
||||
raise ValueError("ADMIN_PASSWORD must be set and at least 12 characters long")
|
||||
@@ -99,15 +104,15 @@ async def init_default_config(db: AsyncSession):
|
||||
db.add(config)
|
||||
print(f"✅ Created default config: {key} = {value}")
|
||||
|
||||
# Create default admin user if not exists
|
||||
result = await db.execute(select(User).where(User.username == "admin"))
|
||||
# Create or update default admin user
|
||||
result = await db.execute(select(User).where(User.username == admin_username))
|
||||
admin = result.scalar_one_or_none()
|
||||
|
||||
default_admin_id = admin.id if admin else None
|
||||
|
||||
if not admin:
|
||||
admin_user = User(
|
||||
username="admin",
|
||||
username=admin_username,
|
||||
hashed_password=pwd_context.hash(admin_password),
|
||||
is_admin=True
|
||||
)
|
||||
@@ -115,8 +120,12 @@ async def init_default_config(db: AsyncSession):
|
||||
await db.commit()
|
||||
await db.refresh(admin_user)
|
||||
default_admin_id = admin_user.id
|
||||
print("✅ Created default admin user (username: admin)")
|
||||
print(f"✅ Created default admin user (username: {admin_username})")
|
||||
else:
|
||||
# Update password if it has changed (verify current password doesn't match)
|
||||
if not pwd_context.verify(admin_password, admin.hashed_password):
|
||||
admin.hashed_password = pwd_context.hash(admin_password)
|
||||
print(f"🔄 Updated default admin password (username: {admin_username})")
|
||||
await db.commit()
|
||||
|
||||
if default_admin_id is not None:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Authentication Router
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from datetime import timedelta
|
||||
@@ -66,6 +66,7 @@ async def register(
|
||||
@router.post("/login", response_model=Token)
|
||||
@limiter.limit("5/minute")
|
||||
async def login(
|
||||
request: Request,
|
||||
user_data: UserLogin,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Exam Router - Handles exam creation, file upload, and deduplication
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, BackgroundTasks
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, BackgroundTasks, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_
|
||||
@@ -537,6 +537,7 @@ async def async_parse_and_save(
|
||||
@router.post("/create", response_model=ExamUploadResponse, status_code=status.HTTP_201_CREATED)
|
||||
@limiter.limit("10/minute")
|
||||
async def create_exam_with_upload(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
title: str = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
@@ -587,6 +588,7 @@ async def create_exam_with_upload(
|
||||
@router.post("/{exam_id}/append", response_model=ExamUploadResponse)
|
||||
@limiter.limit("10/minute")
|
||||
async def append_document_to_exam(
|
||||
request: Request,
|
||||
exam_id: int,
|
||||
background_tasks: BackgroundTasks,
|
||||
file: UploadFile = File(...),
|
||||
|
||||
@@ -11,41 +11,12 @@ services:
|
||||
container_name: qquiz
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
# 数据库配置(SQLite 默认)
|
||||
# 数据库配置(SQLite 默认,使用持久化卷)
|
||||
- DATABASE_URL=sqlite+aiosqlite:////app/data/qquiz.db
|
||||
|
||||
# JWT 密钥(生产环境必须设置为随机字符串)
|
||||
- SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY to a random string of at least 32 characters}
|
||||
|
||||
# 管理员密码(生产环境必须设置为随机强密码,至少 12 位)
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:?Set ADMIN_PASSWORD to a strong password of at least 12 characters}
|
||||
|
||||
# AI 提供商配置
|
||||
- AI_PROVIDER=gemini
|
||||
- GEMINI_API_KEY=${GEMINI_API_KEY}
|
||||
- GEMINI_BASE_URL=${GEMINI_BASE_URL:-https://generativelanguage.googleapis.com}
|
||||
- GEMINI_MODEL=${GEMINI_MODEL:-gemini-2.0-flash-exp}
|
||||
|
||||
# OpenAI 配置(可选)
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
- OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
||||
- OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o-mini}
|
||||
|
||||
# Anthropic 配置(可选)
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL:-claude-3-haiku-20240307}
|
||||
|
||||
# Qwen 配置(可选)
|
||||
- QWEN_API_KEY=${QWEN_API_KEY:-}
|
||||
- QWEN_BASE_URL=${QWEN_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
|
||||
- QWEN_MODEL=${QWEN_MODEL:-qwen-plus}
|
||||
|
||||
# 系统配置
|
||||
- ALLOW_REGISTRATION=true
|
||||
- MAX_UPLOAD_SIZE_MB=10
|
||||
- MAX_DAILY_UPLOADS=20
|
||||
|
||||
volumes:
|
||||
# 持久化数据卷
|
||||
- qquiz_data:/app/data # 数据库文件
|
||||
|
||||
Reference in New Issue
Block a user