文件
kdbx-viewer/config.js
T
wangchuanli 45f6f08195 fix(server): 修复 IP 封锁绕过漏洞并补充审计日志
- 支持 TRUST_PROXY 配置,由 express 正确解析客户端 IP,防止伪造 X-Forwarded-For
- 修复 IP 封锁使用对象作为 Map key 导致失效的问题,改用字符串 IP
- 主密码解锁失败时记录 IP 失败次数,达到阈值后封锁
- 补充条目更新操作的审计日志
2026-08-26 11:09:47 +08:00

95 行
4.1 KiB
JavaScript
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
'use strict';
const process = require('process');
const path = require('path');
// ===== 环境变量优先的取数助手 =====
// 配置写在 config.js,敏感值(口令固定串、SESSION_SECRET)可用同名环境变量覆盖,
// 便于 Docker 注入密钥,避免把密钥明文写进仓库。
const env = process.env;
const pick = (key, fallback) =>
(env[key] !== undefined && env[key] !== '') ? env[key] : fallback;
// 解析带单位的体积(如 5M / 1024K / 5242880),返回字节数;非法时回退到 fallbackBytes
function parseSize(val, fallbackBytes = 5 * 1024 * 1024) {
if (typeof val === 'number') return val > 0 ? val : fallbackBytes;
const s = String(val).trim().toLowerCase();
const m = s.match(/^(\d+(?:\.\d+)?)\s*([kmg]?b?)$/);
if (!m) return fallbackBytes;
const n = parseFloat(m[1]);
const unit = m[2];
const mult = unit.startsWith('k') ? 1024
: unit.startsWith('m') ? 1024 * 1024
: unit.startsWith('g') ? 1024 * 1024 * 1024
: 1;
return Math.floor(n * mult);
}
// 日期格式化(默认 yyyymmdd)
function formatDate(d, fmt) {
const y = String(d.getFullYear());
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return fmt.replace(/yyyy/g, y).replace(/mm/g, m).replace(/dd/g, day);
}
const config = {
// 1) APP 门户口令:dynamic = 当天日期 + 固定串;static = 固定口令
// 敏感值必须来自环境变量(.env / docker environment),不要在文件里写死真实值
appPassword: {
mode: pick('APP_PW_MODE', 'dynamic'), // 'dynamic' | 'static'
dateFormat: pick('APP_PW_DATE_FMT', 'yyyymmdd'),
datePosition: pick('APP_PW_DATE_POS', 'prefix'), // 'prefix' | 'suffix'
dynamicSecret: pick('APP_PW_DYNAMIC', ''),
staticPassword: pick('APP_PW_STATIC', ''),
},
// 2) 密码库 / 密钥文件路径(容器内路径,由 docker-compose 挂载)
kdbxPath: pick('KDBX_PATH', '/app/vault/vault.kdbx'),
keyfilePath: pick('KEYFILE_PATH', '/app/vault/vault.key'),
// 3) 权限:默认只读;true 时开放网页端编辑并写回 kdbx
// 注意:WRITABLE=true 时必须把 docker-compose 里 vault 挂载从 :ro 改为 :rw
writable: pick('WRITABLE', 'false') === 'true',
// 4) 会话与安全
sessionSecret: pick('SESSION_SECRET', ''),
sessionMaxAge: parseInt(pick('SESSION_MAX_AGE', String(30 * 60 * 1000)), 10),
// 反向代理层数/地址(如 "1" 或 "127.0.0.1")。设置后由 express 正确解析客户端 IP,
// 防止攻击者伪造 X-Forwarded-For 绕过 IP 封锁。直连部署留空。
trustProxy: pick('TRUST_PROXY', ''),
// 审计日志:本地文件持久化目录(可按大小自动切分),默认 ./logs
logDir: pick('LOG_DIR', path.join(__dirname, 'logs')),
// 审计日志单文件切分阈值(字节),默认 5MB;支持 K/M 单位
auditFileMax: parseSize(pick('AUDIT_FILE_MAX', '5M')),
// 审计日志切分文件最多保留份数(audit.1.log ~ audit.N.log),默认 10
auditFileKeep: parseInt(pick('AUDIT_FILE_KEEP', '10'), 10),
port: parseInt(pick('PORT', '3000'), 10),
// 缺失敏感值的友好提示(仅提醒,不阻断启动)
_warnMissing: [],
};
if (!config.appPassword.dynamicSecret && !config.appPassword.staticPassword)
config._warnMissing.push('APP_PW_DYNAMIC / APP_PW_STATIC 均未设置,登录将失败');
// 今日期望的 APP 口令(服务端动态计算,前端/用户都看不到拼接逻辑)
config.getExpectedAppPassword = function () {
const ap = this.appPassword;
if (ap.mode === 'static') return ap.staticPassword;
const dateStr = formatDate(new Date(), ap.dateFormat);
return ap.datePosition === 'suffix'
? ap.dynamicSecret + dateStr
: dateStr + ap.dynamicSecret; // 默认:20260825 + 固定串
};
// 启动前检查必要敏感值是否已从环境变量注入
if (!config.sessionSecret) config._warnMissing.push('SESSION_SECRET 未设置');
if (config._warnMissing.length) {
console.warn('[config] 警告:以下敏感值未从环境变量取得,请检查 .env:\n - ' +
config._warnMissing.join('\n - '));
}
module.exports = config;