2026-10-12 10:26:03 +08:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* 后端配置共用:**配置密钥解析(唯一入口)**
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* ============================================================
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* 当前覆盖两个同族密钥:
|
|
|
|
|
|
* · `JWT_SECRET` —— JWT 签名密钥(`loadJwtSecret`)
|
|
|
|
|
|
* · `APP_KEYS` —— Cookie 签名密钥(`loadAppKeys`,Egg `config.keys`)
|
2026-10-12 10:26:03 +08:00
|
|
|
|
*
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* 为什么单独成文件(R105-D1 + R106-D6,2026-10-12)
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* ------------------------------------------------------------
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* 原实现(config.default.js 旧 L300/L304 与 L157/L158)各自带**可预测的兜底值**:
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* const DEFAULT_JWT_SECRET = '<32 位明文串>';
|
|
|
|
|
|
* secret: process.env.JWT_SECRET || DEFAULT_JWT_SECRET
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* const DEFAULT_APP_KEYS = appInfo.name + '_competition_website_secret_key_2026';
|
|
|
|
|
|
* config.keys = process.env.APP_KEYS || DEFAULT_APP_KEYS
|
|
|
|
|
|
* 两者都是「**已入库、任何人可读或可推算**」的值 ⇒ 等价于「密钥公开」:
|
|
|
|
|
|
* · JWT_SECRET 公开 ⇒ 任意人可伪造 token 冒充任意身份;
|
|
|
|
|
|
* · APP_KEYS 公开 ⇒ 任意人可伪造已签名 Cookie(session / 防篡改校验)。
|
|
|
|
|
|
* 且它们与仓库既有纪律直接冲突:
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* · `scripts/_creds.py` / `backend/scripts/_db.cjs` 早已统一为「缺凭据明确失败」;
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* · config.default.js 对 `DB_PASSWORD` 也已改为「缺失即阻断」。
|
|
|
|
|
|
* 同族问题若各自实现必然漂移(R104 的结论)⇒ 收敛到**唯一解析源**,不留兜底。
|
2026-10-12 10:26:03 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 读取优先级(与 `_db.cjs` / `_creds.py` 同一纪律)
|
|
|
|
|
|
* ------------------------------------------------------------
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* 1. 对应的**环境变量**(生产由 systemd `EnvironmentFile=` / 启动器注入;本地由
|
|
|
|
|
|
* config.default.js 顶部的 `loadDeployEnv()` 注入 —— 注意 **R106-D9 起
|
|
|
|
|
|
* `JWT_SECRET` / `APP_KEYS` 属"非生产跳过键"**,本地开发不再继承生产值)
|
|
|
|
|
|
* 2. 仓库根 `scripts/.creds.local.json` 的对应键(本机开发用;`.gitignore` 已排除;
|
|
|
|
|
|
* `jwt_fallback_secret` 同时是 `scripts/_fixtures.py` 的末级来源,两侧同源 ⇒
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* 自签 token 与后端验签必然一致)
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* 3. **明确失败** —— ⛔ **刻意不提供明文兜底**:抛错并给出可行动的修复指引。
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* 理由:密钥缺失属"注入链路断了"的真实故障,明确阻断远优于静默用公开值签名。
|
|
|
|
|
|
*
|
2026-10-12 11:06:01 +08:00
|
|
|
|
* ⚠️ 与 Python 侧 `scripts/_creds.py`(`JWT_FALLBACK_SECRET` / `APP_KEYS`)口径必须一致;
|
2026-10-12 10:26:03 +08:00
|
|
|
|
* 如需增删键,两边同时改。
|
|
|
|
|
|
*/
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
|
|
|
|
const LOCAL_FILE = path.join(__dirname, '..', '..', 'scripts', '.creds.local.json');
|
|
|
|
|
|
|
|
|
|
|
|
function _loadLocal() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(fs.readFileSync(LOCAL_FILE, 'utf8'));
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (e.code === 'ENOENT') return {}; // 文件不存在合法(走环境变量)
|
|
|
|
|
|
// 损坏必须报错,不静默降级
|
|
|
|
|
|
throw new Error(`凭据文件解析失败 ${LOCAL_FILE}: ${e.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-12 11:06:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 通用解析:env → `.creds.local.json` → 明确失败。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {object} o
|
|
|
|
|
|
* @param {string} o.envKey 环境变量名(如 'JWT_SECRET')
|
|
|
|
|
|
* @param {string} o.localKey `.creds.local.json` 中的键名(如 'jwt_fallback_secret')
|
|
|
|
|
|
* @param {string} o.purpose 失败时的用途说明(如 '签名/校验登录态')
|
|
|
|
|
|
* @param {string} o.builtin 被移除的兜底物名称(如 '内置密钥')
|
|
|
|
|
|
* @param {string} o.whyWrong 该兜底值为何不可接受的后果(如 'JWT 可被任意伪造')
|
|
|
|
|
|
* @returns {string} 生效中的密钥
|
|
|
|
|
|
*/
|
|
|
|
|
|
function _loadSecret(o) {
|
|
|
|
|
|
const { envKey, localKey, purpose, builtin, whyWrong } = o;
|
|
|
|
|
|
|
|
|
|
|
|
const fromEnv = process.env[envKey];
|
2026-10-12 10:26:03 +08:00
|
|
|
|
if (fromEnv) return fromEnv;
|
|
|
|
|
|
|
|
|
|
|
|
const local = _loadLocal();
|
2026-10-12 11:06:01 +08:00
|
|
|
|
const fromFile = local[localKey];
|
2026-10-12 10:26:03 +08:00
|
|
|
|
if (fromFile) return fromFile;
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error(
|
2026-10-12 11:06:01 +08:00
|
|
|
|
`[配置缺失] 未取到 ${envKey} —— 无法安全地${purpose}。请任选其一:\n` +
|
|
|
|
|
|
` ① 设置环境变量 ${envKey}(生产/CI 推荐,deploy/.env.production 已含该键);\n` +
|
|
|
|
|
|
` ② 在 ${LOCAL_FILE} 写入键 "${localKey}"(本机开发用,该文件已被 .gitignore 排除);\n` +
|
|
|
|
|
|
`⛔ 本模块刻意不提供明文兜底:原 config.default.js 的${builtin}已移除,\n` +
|
|
|
|
|
|
` 因为「可预测的公开密钥」等价于「${whyWrong}」。`
|
2026-10-12 10:26:03 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-12 11:06:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* JWT 签名密钥。
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function loadJwtSecret() {
|
|
|
|
|
|
return _loadSecret({
|
|
|
|
|
|
envKey: 'JWT_SECRET',
|
|
|
|
|
|
localKey: 'jwt_fallback_secret',
|
|
|
|
|
|
purpose: '签名/校验登录态',
|
|
|
|
|
|
builtin: '内置密钥',
|
|
|
|
|
|
whyWrong: 'JWT 可被任意伪造',
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Cookie 签名密钥(Egg `config.keys`)。
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function loadAppKeys() {
|
|
|
|
|
|
return _loadSecret({
|
|
|
|
|
|
envKey: 'APP_KEYS',
|
|
|
|
|
|
localKey: 'app_keys',
|
|
|
|
|
|
purpose: '签发/校验 Cookie 签名',
|
|
|
|
|
|
builtin: '内置兜底值',
|
|
|
|
|
|
whyWrong: 'Cookie 可被任意伪造',
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { loadJwtSecret, loadAppKeys };
|