From 8286294ff8f856edac8102f991b1d8613f67097d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:37:50 +0000 Subject: [PATCH 1/2] Initial plan From a06148595b2572557a6a2aa6daf43c8753b85363 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:44:31 +0000 Subject: [PATCH 2/2] Implement secure environment variable validation system Co-authored-by: black-zero358 <53086059+black-zero358@users.noreply.github.com> --- backend/.env.example | 28 +++++++++++++++++--------- backend/app/core/config.py | 41 ++++++++++++++++++++++++++++++++++---- backend/app/db/init_db.py | 14 +++++++++---- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index b17b64a..107fb87 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,20 +1,30 @@ -# 数据库配置 +# ==================== 安全配置(必须设置) ==================== +# JWT密钥:用于令牌签名,生产环境必须使用强密钥 +# 建议使用: openssl rand -hex 32 生成 +SECRET_KEY=your_generated_secret_key_here +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 + +# ==================== 数据库配置(必须设置) ==================== POSTGRES_SERVER=localhost POSTGRES_USER=postgres -POSTGRES_PASSWORD=your_password +# 数据库密码:必须设置强密码,不能使用默认值 +POSTGRES_PASSWORD=your_secure_database_password POSTGRES_DB=GradNote POSTGRES_PORT=5432 -# Redis配置(可选) +# ==================== 初始管理员配置(必须设置) ==================== +# 初始管理员账户配置,首次启动时创建 +FIRST_SUPERUSER=admin +# 管理员密码:必须设置强密码,不能使用默认值 +FIRST_SUPERUSER_PASSWORD=your_secure_admin_password +FIRST_SUPERUSER_EMAIL=admin@example.com + +# ==================== Redis配置(可选) ==================== REDIS_HOST=localhost REDIS_PORT=6379 -# 安全配置 -SECRET_KEY=your_generated_secret_key_here -ALGORITHM=HS256 -ACCESS_TOKEN_EXPIRE_MINUTES=30 - -# LANGFUSE配置 +# ==================== LLM服务配置(可选) ==================== LANGFUSE_PUBLIC_KEY='your_langfuse_public_key' LANGFUSE_SECRET_KEY='your_langfuse_secret_key' diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 7b1fa73..ee60b0c 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,20 +1,51 @@ import os +import sys from typing import Optional, Dict, Any, List from pydantic import PostgresDsn, field_validator, ValidationInfo from pydantic_settings import BaseSettings +def validate_required_env_vars() -> None: + """ + 验证必需的环境变量是否存在 + 如果缺少关键的环境变量,应用将无法启动 + """ + required_vars = { + "SECRET_KEY": "JWT密钥,用于令牌签名", + "POSTGRES_PASSWORD": "PostgreSQL数据库密码", + "FIRST_SUPERUSER": "初始管理员用户名", + "FIRST_SUPERUSER_PASSWORD": "初始管理员密码", + "FIRST_SUPERUSER_EMAIL": "初始管理员邮箱" + } + + missing_vars = [] + for var_name, description in required_vars.items(): + if not os.getenv(var_name): + missing_vars.append(f" - {var_name}: {description}") + + if missing_vars: + error_msg = ( + "❌ 安全错误:缺少必需的环境变量\n\n" + "以下环境变量是必需的,但未设置:\n" + + "\n".join(missing_vars) + "\n\n" + "请设置这些环境变量后重新启动应用。\n" + "参考 .env.example 文件了解如何配置。\n" + ) + print(error_msg, file=sys.stderr) + sys.exit(1) + + class Settings(BaseSettings): API_V1_STR: str = "/api/v1" # JWT相关配置 - SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-for-development") + SECRET_KEY: str = os.getenv("SECRET_KEY", "") ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 # 24小时 # 数据库配置 POSTGRES_SERVER: str = os.getenv("POSTGRES_SERVER", "localhost") POSTGRES_USER: str = os.getenv("POSTGRES_USER", "postgres") - POSTGRES_PASSWORD: str = os.getenv("POSTGRES_PASSWORD", "123456") + POSTGRES_PASSWORD: str = os.getenv("POSTGRES_PASSWORD", "") POSTGRES_DB: str = os.getenv("POSTGRES_DB", "GradNote") POSTGRES_PORT: str = os.getenv("POSTGRES_PORT", "5432") DATABASE_URI: Optional[str] = None @@ -31,8 +62,7 @@ def assemble_db_connection(cls, v: Optional[str], info: ValidationInfo) -> Any: required_keys = ["POSTGRES_USER", "POSTGRES_PASSWORD", "POSTGRES_SERVER", "POSTGRES_PORT", "POSTGRES_DB"] for key in required_keys: if key not in data or not data.get(key): - # 如果配置不完整,返回一个默认的本地开发URI - return "postgresql://postgres:123456@localhost:5432/GradNote" + raise ValueError(f"数据库配置不完整:缺少 {key} 环境变量") # 使用字符串拼接而不是PostgresDsn.build,避免编码问题 return f"postgresql://{data.get('POSTGRES_USER')}:{data.get('POSTGRES_PASSWORD')}@{data.get('POSTGRES_SERVER')}:{data.get('POSTGRES_PORT')}/{data.get('POSTGRES_DB')}" @@ -80,4 +110,7 @@ class Config: env_file = ".env" extra = "ignore" # 允许额外的字段,忽略不在模型中定义的字段 +# 在创建settings实例前验证必需的环境变量 +validate_required_env_vars() + settings = Settings() \ No newline at end of file diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py index 2aa1145..7aeae8a 100644 --- a/backend/app/db/init_db.py +++ b/backend/app/db/init_db.py @@ -1,4 +1,5 @@ from sqlalchemy.orm import Session +import os from app.core.config import settings from app.services.user import create_user, get_user_by_username from app.api.schemas.user import UserCreate @@ -9,10 +10,10 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -# 初始测试用户 -FIRST_SUPERUSER = "admin" -FIRST_SUPERUSER_PASSWORD = "admin" -FIRST_SUPERUSER_EMAIL = "admin@example.com" +# 从环境变量获取初始用户配置 +FIRST_SUPERUSER = os.getenv("FIRST_SUPERUSER", "") +FIRST_SUPERUSER_PASSWORD = os.getenv("FIRST_SUPERUSER_PASSWORD", "") +FIRST_SUPERUSER_EMAIL = os.getenv("FIRST_SUPERUSER_EMAIL", "") # 初始知识点数据 - 简化并使用ASCII字符 INITIAL_KNOWLEDGE_POINTS = [ @@ -57,6 +58,11 @@ def init_db(db: Session) -> None: """ 初始化数据库 """ + # 检查初始用户配置 + if not all([FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD, FIRST_SUPERUSER_EMAIL]): + logger.error("初始管理员用户配置不完整,请设置 FIRST_SUPERUSER、FIRST_SUPERUSER_PASSWORD 和 FIRST_SUPERUSER_EMAIL 环境变量") + return + # 创建初始管理员用户 user = get_user_by_username(db, FIRST_SUPERUSER) if not user: