From c57af012b1352fc5543e9127d5994fa76a625a06 Mon Sep 17 00:00:00 2001 From: admin <362324317@qq.com> Date: Mon, 18 May 2026 16:48:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90JWT?= =?UTF-8?q?=5FSECRET=E5=92=8CADMIN=5FPASSWORD=E5=B9=B6=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E5=88=B0secrets.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source_clean/src/config/index.ts | 52 +++++++++++++++++++- source_clean/src/config/startup-validator.ts | 9 +--- source_clean/src/utils/crypto.ts | 29 +++++++++-- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/source_clean/src/config/index.ts b/source_clean/src/config/index.ts index d976ad8..3ecbfb1 100755 --- a/source_clean/src/config/index.ts +++ b/source_clean/src/config/index.ts @@ -1,3 +1,7 @@ +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; + export interface Config { port: number; nodeEnv: string; @@ -23,6 +27,50 @@ export interface Config { dbPath: string; } +const DEFAULT_JWT_SECRETS = ['', 'cloudsearch-jwt-secret-dev', 'cloudsearch-jwt-secret-2024', 'your-super-secret-jwt-key-change-me']; +const DEFAULT_PASSWORDS = ['', 'admin123', 'admin', 'password', '123456', '0nL5kLhMIJ1121PYmQb25A']; + +function loadOrGenerateSecret(key: string, envKey: string, defaultVal: string, isDefault: (v: string) => boolean, byteLen: number): string { + const envVal = process.env[envKey]; + + // If env var is set and NOT a known default → use it directly + if (envVal && !isDefault(envVal)) { + return envVal; + } + + // Try to load from persisted secrets file + const secretsPath = path.join(process.env.DATA_DIR || '/app/data', 'secrets.json'); + try { + if (fs.existsSync(secretsPath)) { + const secrets = JSON.parse(fs.readFileSync(secretsPath, 'utf8')); + if (secrets[key]) return secrets[key]; + } + } catch (_) {} + + // If env var is set but it's a default → still use it (admin set it explicitly, maybe for dev) + if (envVal) return envVal; + + // No env var, no persisted file → auto-generate + const generated = byteLen === 16 + ? crypto.randomBytes(8).toString('hex') // 16-char hex for passwords + : crypto.randomBytes(byteLen).toString('hex'); // 64-char hex for JWT + + try { + const dir = path.dirname(secretsPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + let existing: Record = {}; + if (fs.existsSync(secretsPath)) { + try { existing = JSON.parse(fs.readFileSync(secretsPath, 'utf8')); } catch (_) {} + } + existing[key] = generated; + fs.writeFileSync(secretsPath, JSON.stringify(existing, null, 2)); + console.log(`[Config] Auto-generated ${envKey} → saved to secrets.json`); + } catch (e: any) { + console.log(`[Config] Could not persist ${envKey}:`, e.message); + } + return generated; +} + const config: Config = { port: parseInt(process.env.PORT || '9527', 10), nodeEnv: process.env.NODE_ENV || 'development', @@ -30,9 +78,9 @@ const config: Config = { pansouUrl: process.env.PANSOU_URL || 'http://localhost:8888', pansouAuthToken: process.env.PANSOU_AUTH_TOKEN || '', videoParserUrl: process.env.VIDEO_PARSER_URL || 'http://localhost:3001', - jwtSecret: process.env.JWT_SECRET || 'cloudsearch-jwt-secret-dev', + jwtSecret: loadOrGenerateSecret('jwt_secret', 'JWT_SECRET', 'cloudsearch-jwt-secret-dev', (v) => DEFAULT_JWT_SECRETS.includes(v), 32), adminUsername: process.env.ADMIN_USERNAME || 'admin', - adminPassword: process.env.ADMIN_PASSWORD || 'admin123', + adminPassword: loadOrGenerateSecret('admin_password', 'ADMIN_PASSWORD', 'admin123', (v) => DEFAULT_PASSWORDS.includes(v), 16), validation: { concurrency: parseInt(process.env.VALIDATION_CONCURRENCY || '10', 10), timeout: parseInt(process.env.VALIDATION_TIMEOUT || '5000', 10), diff --git a/source_clean/src/config/startup-validator.ts b/source_clean/src/config/startup-validator.ts index 606f336..98899ae 100644 --- a/source_clean/src/config/startup-validator.ts +++ b/source_clean/src/config/startup-validator.ts @@ -42,14 +42,7 @@ export function validateConfig(): ValidationError[] { } // ─── Cookie Encryption ─── - if (!process.env.COOKIE_ENCRYPTION_KEY) { - errors.push({ - key: 'COOKIE_ENCRYPTION_KEY', - message: '未设置网盘 Cookie 加密密钥!Cookie 将以明文存储。生产环境强烈建议设置。\n' + - '生成: openssl rand -hex 32', - severity: 'warn', - }); - } + // Key is auto-generated and persisted to encryption.key if COOKIE_ENCRYPTION_KEY is not set // ─── Port conflict check (best-effort) ─── if (config.port < 1024 && (process as any).getuid?.() !== 0) { diff --git a/source_clean/src/utils/crypto.ts b/source_clean/src/utils/crypto.ts index 0710af9..f24c654 100644 --- a/source_clean/src/utils/crypto.ts +++ b/source_clean/src/utils/crypto.ts @@ -6,6 +6,8 @@ * Production MUST set COOKIE_ENCRYPTION_KEY! */ import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; const ALGORITHM = 'aes-256-gcm'; const IV_LENGTH = 12; // 96-bit nonce for GCM @@ -25,9 +27,30 @@ function getKey(): Buffer { ENCRYPTION_KEY = crypto.createHash('sha256').update(envKey).digest(); console.log('[Crypto] Cookie encryption enabled (key from COOKIE_ENCRYPTION_KEY, SHA-256 derived)'); } else { - // Default stable key (not ephemeral) — data survives container restart - ENCRYPTION_KEY = crypto.createHash('sha256').update('cloudsearch-cookie-key-v1').digest(); - console.log('[Crypto] Cookie encryption enabled (built-in default key — set COOKIE_ENCRYPTION_KEY in .env for extra security)'); + // Auto-generate a random key and persist to file + const keyFile = path.join(process.env.DATA_DIR || '/app/data', 'encryption.key'); + try { + if (fs.existsSync(keyFile)) { + const savedKey = fs.readFileSync(keyFile, 'utf8').trim(); + if (savedKey.length >= 32) { + ENCRYPTION_KEY = crypto.createHash('sha256').update(savedKey).digest(); + console.log('[Crypto] Cookie encryption enabled (loaded from encryption.key)'); + return ENCRYPTION_KEY; + } + } + } catch (_) { /* file read failed, will generate new key */ } + + const autoKey = crypto.randomBytes(32).toString('hex'); + try { + const dir = path.dirname(keyFile); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(keyFile, autoKey); + console.log('[Crypto] Auto-generated encryption key saved to encryption.key'); + } catch (e: any) { + console.log('[Crypto] Could not persist key, using ephemeral:', e.message); + } + ENCRYPTION_KEY = crypto.createHash('sha256').update(autoKey).digest(); + console.log('[Crypto] Cookie encryption enabled (auto-generated key)'); } return ENCRYPTION_KEY; }