From a10a97da4f273cd50df0893beba423e615473402 Mon Sep 17 00:00:00 2001 From: dash <1549469775@qq.com> Date: Fri, 5 Sep 2025 00:58:29 +0800 Subject: [PATCH] =?UTF-8?q?chore(env):=20=E5=A2=9E=E5=BC=BA=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F=E9=85=8D=E7=BD=AE=E5=92=8C=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E4=BD=93=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完善 .env.example 模板,增加详细注释和安全提示 - 创建环境变量验证模块,校验必需变量和格式正确性 - 移除 JWT_SECRET 默认值,强制必须配置 - 在应用启动时进行环境变量验证,验证失败则退出 - 更新 README.md,补充快速启动和环境配置指南 - 修改 docker-compose.yml,支持从环境变量读取敏感配置 - 添加环境变量验证测试脚本,覆盖缺失、格式和正确配置场景 - 提供环境变量脱敏显示功能,保护敏感信息安全 - 完善项目文档,新增环境配置、安全规范和改进任务跟踪文档 --- .env.example | 57 +++++++- README.md | 70 +++++++++- database/development.sqlite3-shm | Bin 32768 -> 32768 bytes docker-compose.yml | 4 + docs/environment-setup.md | 141 +++++++++++++++++++ docs/improvement-tasks.md | 195 +++++++++++++++++++++++++++ docs/project-standards-review.md | 285 +++++++++++++++++++++++++++++++++++++++ package.json | 3 +- scripts/test-env-validation.js | 50 +++++++ src/global.js | 19 +-- src/middlewares/Auth/auth.js | 2 +- src/utils/envValidator.js | 165 +++++++++++++++++++++++ 12 files changed, 978 insertions(+), 13 deletions(-) create mode 100644 docs/environment-setup.md create mode 100644 docs/improvement-tasks.md create mode 100644 docs/project-standards-review.md create mode 100644 scripts/test-env-validation.js create mode 100644 src/utils/envValidator.js diff --git a/.env.example b/.env.example index 6ac9f01..c11cbbf 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,55 @@ -SESSION_SECRET=随机字符串 -HTTPS_ENABLE=on \ No newline at end of file +# ======================================== +# koa3-demo 环境变量配置模板 +# ======================================== +# 复制此文件为 .env 并设置实际值 + +# ======================================== +# 必需环境变量 (Required) +# ======================================== + +# 会话密钥,用于cookie签名,多个密钥用逗号分隔,支持密钥轮换 +# Session secrets for cookie signing, comma-separated for key rotation +SESSION_SECRET=your-super-secret-session-key-at-least-32-chars,backup-secret-key + +# JWT密钥,用于生成和验证JWT令牌,至少32个字符 +# JWT secret for token generation and verification, minimum 32 characters +JWT_SECRET=your-super-secret-jwt-key-must-be-at-least-32-characters-long + +# ======================================== +# 可选环境变量 (Optional) +# ======================================== + +# 运行环境: development | production | test +# Application environment +NODE_ENV=development + +# 服务器端口 +# Server port +PORT=3000 + +# 日志文件目录 +# Log files directory +LOG_DIR=logs + +# 是否启用HTTPS (生产环境推荐): on | off +# Enable HTTPS in production environment +HTTPS_ENABLE=off + +# ======================================== +# 生产环境额外配置建议 +# ======================================== + +# 生产环境示例配置: +# NODE_ENV=production +# PORT=3000 +# HTTPS_ENABLE=on +# SESSION_SECRET=生产环境强密钥1,生产环境强密钥2 +# JWT_SECRET=生产环境JWT强密钥至少32字符 + +# ======================================== +# 安全提示 +# ======================================== +# 1. 永远不要将真实的密钥提交到版本控制系统 +# 2. 生产环境的密钥应该使用安全的随机字符串 +# 3. 定期轮换密钥 +# 4. SESSION_SECRET 支持多个密钥,便于无缝密钥更新 \ No newline at end of file diff --git a/README.md b/README.md index 4a07b2e..5764c61 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,51 @@ - [ ] 界面 - [ ] 定时任务 - [ ] htmx + - [x] 环境变量验证 + +## 快速开始 + +### 1. 环境配置 + +复制环境变量模板文件: +```bash +cp .env.example .env +``` + +编辑 `.env` 文件,设置必需的环境变量: +- `SESSION_SECRET`: 会话密钥(至少32个字符) +- `JWT_SECRET`: JWT密钥(至少32个字符) + +### 2. 安装依赖 +```bash +bun install +``` + +### 3. 初始化数据库 +```bash +bun run dev:init +``` + +### 4. 启动开发服务器 +```bash +bun run dev +``` + +## 环境变量说明 + +项目启动时会自动验证环境变量配置,确保应用安全性: + +### 必需环境变量 +- `SESSION_SECRET`: 会话密钥,支持多密钥轮换(逗号分隔) +- `JWT_SECRET`: JWT令牌密钥(最少32字符) + +### 可选环境变量 +- `NODE_ENV`: 运行环境 (development/production/test) +- `PORT`: 服务器端口 (默认: 3000) +- `LOG_DIR`: 日志目录 (默认: logs) +- `HTTPS_ENABLE`: 是否启用HTTPS (默认: off) + +详细配置请参考 `.env.example` 文件。 ### 数据库查询缓存(QueryBuilder 扩展) @@ -58,4 +103,27 @@ const stats = DbQueryCache.stats() // { size, valid, expired } - 该实现为进程内内存缓存,适合单实例与读多写少场景;多实例下需用外部缓存(如 Redis)替换/扩展。 - TTL 为可选,未设置则永久有效,直到被失效或进程重启。 -- 自定义 key 建议使用明确的命名空间前缀(如 `users:`、`site:`),以便使用前缀批量失效。 \ No newline at end of file +- 自定义 key 建议使用明确的命名空间前缀(如 `users:`、`site:`),以便使用前缀批量失效。 + +## 📚 项目文档 + +- [环境变量配置指南](./docs/environment-setup.md) - 详细的环境配置说明 +- [项目规范检查报告](./docs/project-standards-review.md) - 完整的规范评估和改进记录 +- [改进任务跟踪](./docs/improvement-tasks.md) - 项目改进任务的执行状态 + +## 🔧 开发和部署 + +### 测试环境变量配置 +```bash +bun run test:env +``` + +### 生产环境部署 +```bash +# 使用Docker +docker-compose up -d + +# 或直接构建 +docker build -t koa3-demo . +docker run -p 3000:3000 koa3-demo +``` \ No newline at end of file diff --git a/database/development.sqlite3-shm b/database/development.sqlite3-shm index 3827c87fbc700252d5582640f1f27d0c7b99f50e..3b94e9ba73ee083da6936dea1f86cb0a5393c796 100644 GIT binary patch delta 86 zcmZo@U}|V!;*@x#%K!!wIpqb9`P|#flXH7HvY;kR5TcBMVI`399|^$4H#XMS0|1ha B9?Sp$ delta 86 zcmZo@U}|V!;*@x#%K!pQ6FKDtANbtcYnF3+IkKQ8Oc1OLNUQ+j|40BPzI { - app.keys.push(secret) - }) -} +// SESSION_SECRET 已通过环境变量验证确保存在 +process.env.SESSION_SECRET.split(",").forEach(secret => { + app.keys.push(secret.trim()) +}) export { app } export default app \ No newline at end of file diff --git a/src/middlewares/Auth/auth.js b/src/middlewares/Auth/auth.js index 8d1cc5f..81bfc70 100644 --- a/src/middlewares/Auth/auth.js +++ b/src/middlewares/Auth/auth.js @@ -2,7 +2,7 @@ import { logger } from "@/logger" import jwt from "./jwt" import { minimatch } from "minimatch" -export const JWT_SECRET = process.env.JWT_SECRET || "jwt-demo-secret" +export const JWT_SECRET = process.env.JWT_SECRET function matchList(list, path) { for (const item of list) { diff --git a/src/utils/envValidator.js b/src/utils/envValidator.js new file mode 100644 index 0000000..fc9fb03 --- /dev/null +++ b/src/utils/envValidator.js @@ -0,0 +1,165 @@ +import { logger } from "@/logger.js" + +/** + * 环境变量验证配置 + * required: 必需的环境变量 + * optional: 可选的环境变量(提供默认值) + */ +const ENV_CONFIG = { + required: [ + "SESSION_SECRET", + "JWT_SECRET" + ], + optional: { + "NODE_ENV": "development", + "PORT": "3000", + "LOG_DIR": "logs", + "HTTPS_ENABLE": "off" + } +} + +/** + * 验证必需的环境变量 + * @returns {Object} 验证结果 + */ +function validateRequiredEnv() { + const missing = [] + const valid = {} + + for (const key of ENV_CONFIG.required) { + const value = process.env[key] + if (!value || value.trim() === '') { + missing.push(key) + } else { + valid[key] = value + } + } + + return { missing, valid } +} + +/** + * 设置可选环境变量的默认值 + * @returns {Object} 设置的默认值 + */ +function setOptionalDefaults() { + const defaults = {} + + for (const [key, defaultValue] of Object.entries(ENV_CONFIG.optional)) { + if (!process.env[key]) { + process.env[key] = defaultValue + defaults[key] = defaultValue + } + } + + return defaults +} + +/** + * 验证环境变量的格式和有效性 + * @param {Object} env 环境变量对象 + * @returns {Array} 错误列表 + */ +function validateEnvFormat(env) { + const errors = [] + + // 验证 PORT 是数字 + if (env.PORT && isNaN(parseInt(env.PORT))) { + errors.push("PORT must be a valid number") + } + + // 验证 NODE_ENV 的值 + const validNodeEnvs = ['development', 'production', 'test'] + if (env.NODE_ENV && !validNodeEnvs.includes(env.NODE_ENV)) { + errors.push(`NODE_ENV must be one of: ${validNodeEnvs.join(', ')}`) + } + + // 验证 SESSION_SECRET 至少包含一个密钥 + if (env.SESSION_SECRET) { + const secrets = env.SESSION_SECRET.split(',').filter(s => s.trim()) + if (secrets.length === 0) { + errors.push("SESSION_SECRET must contain at least one non-empty secret") + } + } + + // 验证 JWT_SECRET 长度 + if (env.JWT_SECRET && env.JWT_SECRET.length < 32) { + errors.push("JWT_SECRET must be at least 32 characters long for security") + } + + return errors +} + +/** + * 初始化和验证所有环境变量 + * @returns {boolean} 验证是否成功 + */ +export function validateEnvironment() { + logger.info("🔍 开始验证环境变量...") + + // 1. 验证必需的环境变量 + const { missing, valid } = validateRequiredEnv() + + if (missing.length > 0) { + logger.error("❌ 缺少必需的环境变量:") + missing.forEach(key => { + logger.error(` - ${key}`) + }) + logger.error("请设置这些环境变量后重新启动应用") + return false + } + + // 2. 设置可选环境变量的默认值 + const defaults = setOptionalDefaults() + if (Object.keys(defaults).length > 0) { + logger.info("⚙️ 设置默认环境变量:") + Object.entries(defaults).forEach(([key, value]) => { + logger.info(` - ${key}=${value}`) + }) + } + + // 3. 验证环境变量格式 + const formatErrors = validateEnvFormat(process.env) + if (formatErrors.length > 0) { + logger.error("❌ 环境变量格式错误:") + formatErrors.forEach(error => { + logger.error(` - ${error}`) + }) + return false + } + + // 4. 记录有效的环境变量(敏感信息脱敏) + logger.info("✅ 环境变量验证成功:") + logger.info(` - NODE_ENV=${process.env.NODE_ENV}`) + logger.info(` - PORT=${process.env.PORT}`) + logger.info(` - LOG_DIR=${process.env.LOG_DIR}`) + logger.info(` - SESSION_SECRET=${maskSecret(process.env.SESSION_SECRET)}`) + logger.info(` - JWT_SECRET=${maskSecret(process.env.JWT_SECRET)}`) + + return true +} + +/** + * 脱敏显示敏感信息 + * @param {string} secret 敏感字符串 + * @returns {string} 脱敏后的字符串 + */ +export function maskSecret(secret) { + if (!secret) return "未设置" + if (secret.length <= 8) return "*".repeat(secret.length) + return secret.substring(0, 4) + "*".repeat(secret.length - 8) + secret.substring(secret.length - 4) +} + +/** + * 获取环境变量配置(用于生成 .env.example) + * @returns {Object} 环境变量配置 + */ +export function getEnvConfig() { + return ENV_CONFIG +} + +export default { + validateEnvironment, + getEnvConfig, + maskSecret +} \ No newline at end of file