first commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
const db = require('../db');
|
||||
const config = require('../config');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
*/
|
||||
function createLog({ approvalCode, instanceId, workflowType, applicantName, applicantPinyin, action, detail }) {
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO operation_logs
|
||||
(approval_code, approval_instance_id, workflow_type, applicant_name, applicant_pinyin, action, detail, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'running')
|
||||
`);
|
||||
const result = stmt.run(approvalCode, instanceId, workflowType, applicantName, applicantPinyin, action, detail);
|
||||
return result.lastInsertRowid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新日志状态
|
||||
*/
|
||||
function updateLog(logId, status, errorMessage = null) {
|
||||
const stmt = db.prepare(`
|
||||
UPDATE operation_logs
|
||||
SET status = ?, error_message = ?, finished_at = datetime('now', 'localtime')
|
||||
WHERE id = ?
|
||||
`);
|
||||
stmt.run(status, errorMessage, logId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询日志(带分页和过滤)
|
||||
*/
|
||||
function queryLogs({ page = 1, pageSize = 20, workflowType, status, startDate, endDate, keyword } = {}) {
|
||||
let where = 'WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (workflowType) {
|
||||
where += ' AND workflow_type = ?';
|
||||
params.push(workflowType);
|
||||
}
|
||||
if (status) {
|
||||
where += ' AND status = ?';
|
||||
params.push(status);
|
||||
}
|
||||
if (startDate) {
|
||||
where += ' AND created_at >= ?';
|
||||
params.push(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
where += ' AND created_at <= ?';
|
||||
params.push(endDate + ' 23:59:59');
|
||||
}
|
||||
if (keyword) {
|
||||
where += ' AND (applicant_name LIKE ? OR detail LIKE ? OR approval_instance_id LIKE ?)';
|
||||
params.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`);
|
||||
}
|
||||
|
||||
const countStmt = db.prepare(`SELECT COUNT(*) as total FROM operation_logs ${where}`);
|
||||
const { total } = countStmt.get(...params);
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const dataStmt = db.prepare(`
|
||||
SELECT * FROM operation_logs ${where}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`);
|
||||
const rows = dataStmt.all(...params, pageSize, offset);
|
||||
|
||||
return { total, page, pageSize, rows };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期日志(保留 N 天)
|
||||
*/
|
||||
function cleanupOldLogs() {
|
||||
const retentionDays = config.security.logRetentionDays;
|
||||
const stmt = db.prepare(`
|
||||
DELETE FROM operation_logs
|
||||
WHERE created_at < datetime('now', 'localtime', '-' || ? || ' days')
|
||||
`);
|
||||
const result = stmt.run(retentionDays);
|
||||
if (result.changes > 0) {
|
||||
logger.info(`已清理 ${result.changes} 条过期日志(保留${retentionDays}天)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查审批是否已处理(防重复)
|
||||
*/
|
||||
function isApprovalProcessed(instanceId) {
|
||||
const stmt = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM operation_logs
|
||||
WHERE approval_instance_id = ? AND status = 'success'
|
||||
`);
|
||||
const { count } = stmt.get(instanceId);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createLog,
|
||||
updateLog,
|
||||
queryLogs,
|
||||
cleanupOldLogs,
|
||||
isApprovalProcessed,
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
const config = require('../config');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* 路径安全验证 - 白名单机制
|
||||
* 只允许访问预设目录
|
||||
*/
|
||||
function validatePath(path) {
|
||||
if (!path || typeof path !== 'string') {
|
||||
throw new Error('路径不能为空');
|
||||
}
|
||||
|
||||
// 规范化路径(防止 ../ 穿越)
|
||||
const normalized = normalizePath(path);
|
||||
|
||||
// 检查是否在白名单内
|
||||
const allowed = config.security.whitelistDirs.some(dir => {
|
||||
const normalizedDir = normalizePath(dir);
|
||||
return normalized === normalizedDir || normalized.startsWith(normalizedDir + '/');
|
||||
});
|
||||
|
||||
if (!allowed) {
|
||||
logger.warn(`[安全] 路径被拒绝: ${path} (规范化: ${normalized})`);
|
||||
throw new Error(`路径不在白名单内: ${path}。允许的目录: ${config.security.whitelistDirs.join(', ')}`);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径规范化(防止路径穿越攻击)
|
||||
*/
|
||||
function normalizePath(path) {
|
||||
// 移除多余的斜杠
|
||||
let normalized = path.replace(/\/+/g, '/');
|
||||
// 解析 .. 和 .
|
||||
const parts = normalized.split('/');
|
||||
const result = [];
|
||||
for (const part of parts) {
|
||||
if (part === '' || part === '.') continue;
|
||||
if (part === '..') {
|
||||
result.pop(); // 回退一级
|
||||
} else {
|
||||
result.push(part);
|
||||
}
|
||||
}
|
||||
return '/' + result.join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令安全验证
|
||||
* 防止命令注入
|
||||
*/
|
||||
function validateCommand(command) {
|
||||
if (!command || typeof command !== 'string') {
|
||||
throw new Error('命令不能为空');
|
||||
}
|
||||
|
||||
// 禁止的危险字符和模式
|
||||
const dangerousPatterns = [
|
||||
/;/, // 命令分隔
|
||||
/&&/, // 命令链接(允许在预设命令中)
|
||||
/\|\|/, // 或链接
|
||||
/\$\(/, // 命令替换
|
||||
/`/, // 反引号命令替换
|
||||
/>\s*\//, // 重定向到绝对路径
|
||||
/rm\s+-rf/, // 危险删除
|
||||
/rm\s+-r/, // 递归删除
|
||||
/dd\s+if=/, // dd 命令
|
||||
/mkfs/, // 格式化
|
||||
/shutdown/, // 关机
|
||||
/reboot/, // 重启
|
||||
/curl.*\|/, // 管道执行
|
||||
/wget.*\|/, // 管道执行
|
||||
];
|
||||
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(command)) {
|
||||
logger.warn(`[安全] 危险命令被拒绝: ${command}`);
|
||||
throw new Error(`命令包含危险模式,已被拒绝: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件名安全性
|
||||
*/
|
||||
function validateFileName(fileName) {
|
||||
if (!fileName || typeof fileName !== 'string') {
|
||||
throw new Error('文件名不能为空');
|
||||
}
|
||||
// 禁止路径分隔符和特殊字符
|
||||
if (/[/\\:*?"<>|]/.test(fileName)) {
|
||||
throw new Error(`文件名包含非法字符: ${fileName}`);
|
||||
}
|
||||
if (fileName.includes('..')) {
|
||||
throw new Error(`文件名包含路径穿越: ${fileName}`);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
module.exports = { validatePath, validateCommand, validateFileName, normalizePath };
|
||||
Reference in New Issue
Block a user