chanjs 2.0.5 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/helper/safe.js DELETED
@@ -1,263 +0,0 @@
1
- /**
2
- * 安全中间件:用于防御常见Web攻击,包括XSS、SQL注入、点击劫持等
3
- * 功能:1. 检测URL中的敏感关键词 2. 设置X-Frame-Options头防止点击劫持
4
- */
5
- /**
6
- * 扩展后的敏感关键词列表
7
- * 包含:敏感文件扩展名、路径遍历、管理路径、SQL注入、XSS、命令注入等常见攻击特征
8
- */
9
- const defaultKeywords = [
10
- // 敏感文件扩展名(路径遍历/敏感文件访问)
11
- "\\.(php|asp|aspx|jsp|jspx|go|cgi|pl|sh|bat|exe|sql|rar|gz|tar|tgz|7z|json|xml|vscode|bak|well-known|log|conf|ini|env|yml|yaml|pem|key|crt|db|sqlite)",
12
-
13
- // 路径遍历特征(目录跳转)
14
- "\\.\\./", // ../ 上级目录
15
- "\\.\\.\\\\", // ..\ Windows系统上级目录
16
- "/etc/", // 系统配置目录
17
- "/proc/", // 进程信息目录
18
- "/dev/", // 设备文件目录
19
- "/root/", // 根用户目录
20
- "/home/", // 用户主目录
21
- "C:\\\\Windows\\\\", // Windows系统目录
22
- "C:\\\\Users\\\\", // Windows用户目录
23
-
24
- // 敏感管理路径
25
- "/manager/",
26
- "/backend/",
27
- "/system/",
28
- "/control/",
29
- "/dashboard/",
30
- "/wp-admin/", // WordPress管理后台
31
- "/phpmyadmin/", // MySQL管理工具
32
- "/adminer/",
33
- "/pma/",
34
-
35
- // SQL注入相关关键词
36
- "union\\s+select",
37
- "drop\\s+table",
38
- "delete\\s+from",
39
- "insert\\s+into",
40
- "update\\s+set",
41
- "exec\\s+\\(",
42
- "xp_cmdshell", // SQL Server命令执行
43
- "into\\s+outfile", // MySQL写文件
44
- "load_file\\(", // MySQL读文件
45
- "where\\s+1=1", // 条件注入
46
- "or\\s+1=1",
47
-
48
- // XSS相关关键词
49
- "script",
50
- "alert",
51
- "iframe",
52
- "onerror",
53
- "onclick",
54
- "onload",
55
- "onmouseover",
56
- "confirm",
57
- "prompt",
58
- "eval",
59
- // "javascript:", // JS伪协议
60
- // "vbscript:", // VBScript伪协议
61
- // "<img", // 图片标签注入
62
- // "<svg", // SVG注入
63
- // "<video",
64
- // "<audio",
65
-
66
- // 命令注入相关
67
- "cmd\\.exe",
68
- "bash",
69
- "sh",
70
- "powershell",
71
- "cmd",
72
- "system\\(",
73
- "shell_exec\\(",
74
- "exec\\(",
75
- "passthru\\(",
76
- "`.*`", // 反引号命令执行
77
- "\\|", // 管道符
78
- "&", // 后台执行符
79
- ";", // 命令分隔符
80
-
81
- // 敏感操作/信息
82
- "document\\.cookie",
83
- "window\\.location",
84
- "fetch",
85
- "xhr",
86
- "ajax",
87
- "data:", // data协议
88
- "root", // 管理员用户
89
- "backup",
90
- "callback",
91
- "ftp",
92
- "ssh",
93
- "telnet",
94
-
95
- "token",
96
- "secret",
97
- "password",
98
- "passwd",
99
-
100
- ];
101
-
102
- export default defaultKeywords;
103
-
104
-
105
-
106
- /**
107
- * 安全中间件:简化配置的Web安全防御工具
108
- * 功能:防点击劫持、敏感请求拦截
109
- */
110
-
111
- // 基础敏感关键词(高风险攻击特征)
112
- const baseKeywords = [
113
- // SQL注入核心特征
114
- 'union select', 'drop table', 'delete from',
115
- 'insert into', 'update .+ set', 'exec\\(',
116
- // XSS核心特征
117
- '<script', 'onerror=', 'onclick=',
118
- 'eval\\(', 'document\\.cookie'
119
- ];
120
-
121
- /**
122
- * 安全中间件主函数
123
- * @param {Object} options - 配置选项
124
- * @param {string[]} [options.keywords=[]] - 自定义敏感关键词(会与基础关键词合并)
125
- * @param {number} [options.threshold=1] - 敏感词匹配阈值(默认匹配1个即拦截)
126
- * @param {string} [options.framePolicy='SAMEORIGIN'] - X-Frame-Options策略:DENY/SAMEORIGIN/ALLOW-FROM
127
- * @param {string} [options.allowFrom] - 当framePolicy为ALLOW-FROM时的允许地址
128
- * @param {string} [options.blockMessage='请求被安全策略拦截'] - 拦截提示信息
129
- * @param {Function} [options.onBlock] - 拦截时的回调函数
130
- * @returns {Function} Express中间件函数
131
- */
132
- export const safe = (options = {}) => {
133
- // 合并默认配置
134
- const {
135
- keywords = [],
136
- threshold = 1,
137
- framePolicy = 'SAMEORIGIN',
138
- allowFrom = '',
139
- blockMessage = '请求被安全策略拦截',
140
- onBlock = () => {}
141
- } = options;
142
-
143
- // 处理关键词:转义特殊字符,合并基础关键词与自定义关键词
144
- const escapedKeywords = [
145
- ...baseKeywords,
146
- ...keywords.map(kw => kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
147
- ];
148
-
149
- // 生成不区分大小写的全局匹配正则
150
- const keywordReg = new RegExp(`(${escapedKeywords.join('|')})`, 'ig');
151
-
152
- return (req, res, next) => {
153
- // 1. 设置防点击劫持头
154
- let frameHeader = 'SAMEORIGIN';
155
- if (framePolicy === 'DENY') {
156
- frameHeader = 'DENY';
157
- } else if (framePolicy === 'ALLOW-FROM' && allowFrom) {
158
- frameHeader = `ALLOW-FROM ${allowFrom}`;
159
- }
160
- res.setHeader('X-Frame-Options', frameHeader);
161
-
162
- // 2. 添加XSS防御头
163
- res.setHeader('X-XSS-Protection', '1; mode=block');
164
-
165
- // 3. 敏感请求检查
166
- // 拼接URL和查询参数作为检查文本
167
- const checkText = `${req.url} ${JSON.stringify(req.query)}`;
168
- // 匹配所有出现的敏感词
169
- const matches = checkText.match(keywordReg) || [];
170
-
171
- // 去重后判断是否达到阈值
172
- if ([...new Set(matches)].length >= threshold) {
173
- const logInfo = {
174
- method: req.method,
175
- url: req.url,
176
- ip: req.ip,
177
- matches: [...new Set(matches)]
178
- };
179
- console.error('[安全拦截] 疑似恶意请求:', logInfo);
180
- onBlock(logInfo); // 触发拦截回调
181
- return res.status(403).send(blockMessage);
182
- }
183
-
184
- // 检查通过,继续处理
185
- next();
186
- };
187
- };
188
-
189
-
190
-
191
- // utils/safe.js
192
- const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
193
-
194
- const BASE_KEYWORDS = [
195
- 'union select', 'drop table', 'delete from',
196
- 'insert into', 'exec(', '<script', 'onerror=',
197
- 'onclick=', 'eval(', 'document.cookie'
198
- ];
199
-
200
- export const safe = (options = {}) => {
201
- const {
202
- keywords = [],
203
- threshold = 1,
204
- blockMessage = '请求被安全策略拦截',
205
- onBlock = () => {},
206
- enableBodyCheck = true,
207
- ignorePaths = [/\/static\//, /\/assets\//, /\.(js|css|png|jpg|jpeg|gif|ico)$/i]
208
- } = options;
209
-
210
- // 白名单路径检查
211
- const isIgnoredPath = (url) => {
212
- return ignorePaths.some(pattern => pattern.test(url));
213
- };
214
-
215
- // 所有关键词转义
216
- const keywordList = [...BASE_KEYWORDS, ...keywords].map(escapeRegExp);
217
-
218
- return (req, res, next) => {
219
- // 1. 白名单路径跳过检查
220
- if (isIgnoredPath(req.url)) {
221
- return next();
222
- }
223
-
224
- // 2. 设置安全头
225
- res.setHeader('X-Frame-Options', 'DENY');
226
- res.setHeader('X-Content-Type-Options', 'nosniff');
227
- res.setHeader('X-XSS-Protection', '1; mode=block');
228
- res.setHeader('Referrer-Policy', 'no-referrer-when-downgrade');
229
- res.setHeader('Content-Security-Policy', "frame-ancestors 'self'; default-src 'self'");
230
-
231
- // 3. 构造检查文本
232
- let checkText = `${req.url} ${JSON.stringify(req.query)}`;
233
- if (enableBodyCheck && req.body) {
234
- checkText += ` ${JSON.stringify(req.body)}`;
235
- }
236
- checkText = checkText.toLowerCase();
237
-
238
- // 4. 逐个检查关键词(性能好,可提前退出)
239
- const matches = [];
240
- for (const kw of keywordList) {
241
- if (checkText.includes(kw.toLowerCase())) {
242
- matches.push(kw);
243
- if (matches.length >= threshold) break;
244
- }
245
- }
246
-
247
- if (matches.length >= threshold) {
248
- const logInfo = {
249
- method: req.method,
250
- url: req.url,
251
- ip: req.ip,
252
- userAgent: req.get('User-Agent'),
253
- matches: [...new Set(matches)],
254
- timestamp: new Date().toISOString()
255
- };
256
- console.warn('[安全拦截] 疑似恶意请求:', logInfo);
257
- onBlock(logInfo);
258
- return res.status(403).send(blockMessage);
259
- }
260
-
261
- next();
262
- };
263
- };
package/helper/tree.js DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * @param {Array} arr - 原始数据数组
3
- * @param {number|string} [pid=0] - 根节点父ID
4
- * @param {string} [idKey='id'] - ID字段名
5
- * @param {string} [pidKey='pid'] - 父ID字段名
6
- * @param {string} [childrenKey='children'] - 子节点字段名
7
- * @returns {Array} 树形结构数组
8
- */
9
- export function buildTree(arr, pid = 0, idKey = 'id', pidKey = 'pid', childrenKey = 'children') {
10
- // 基础参数校验
11
- if (!Array.isArray(arr)) return [];
12
-
13
- const tree = [];
14
-
15
- for (let i = 0; i < arr.length; i++) {
16
- const item = arr[i];
17
- // 找到当前层级的节点
18
- if (item[pidKey] === pid) {
19
- // 递归查找子节点(通过slice创建子数组,避免重复遍历已处理项)
20
- const children = buildTree(arr.slice(i + 1), item[idKey], idKey, pidKey, childrenKey);
21
-
22
- // 有子节点则添加,避免空数组
23
- if (children.length) {
24
- item[childrenKey] = children;
25
- }
26
-
27
- tree.push(item);
28
- }
29
- }
30
-
31
- return tree;
32
- }
@@ -1,144 +0,0 @@
1
- /**
2
- * 安全中间件:用于防御常见Web攻击,包括XSS、SQL注入、点击劫持等
3
- * 功能:1. 检测URL中的敏感关键词 2. 设置X-Frame-Options头防止点击劫持
4
- */
5
-
6
- // 默认敏感关键词列表
7
- // 包含:敏感文件扩展名、管理路径、SQL注入语句、XSS相关函数、敏感操作等
8
- const defaultKeywords = [
9
- // 敏感文件扩展名(可能用于路径遍历或敏感文件访问)
10
- "\\.(php|asp|aspx|jsp|jspx|cgi|pl|sh|bat|exe|sql|rar|gz|tar|tgz|7z|json|xml|vscode|bak|well-known)",
11
- "admin", // 管理后台路径
12
- // SQL注入相关关键词
13
- "union\\s+select",
14
- "drop\\s+table",
15
- "delete\\s+from",
16
- "insert\\s+into",
17
- "update\\s+set",
18
- "exec\\s+\\(",
19
- // XSS相关关键词
20
- "script",
21
- "alert",
22
- "iframe",
23
- "onerror",
24
- "confirm",
25
- "prompt",
26
- "eval",
27
- // 敏感操作/信息
28
- "document\\.cookie",
29
- "window\\.location",
30
- "fetch",
31
- "xhr",
32
- "ajax",
33
- "data",
34
- "root",
35
- "backup",
36
- "callback",
37
- "ftp"
38
- ];
39
-
40
- /**
41
- * 关键词转义处理:将正则特殊字符转义,避免干扰正则匹配
42
- * @param {string} keyword - 需要转义的关键词
43
- * @returns {string} 转义后的关键词
44
- */
45
- const escapeRegExp = (keyword) => {
46
- return keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
47
- };
48
-
49
- /**
50
- * 检查URL中是否包含敏感关键词
51
- * @param {string} url - 需要检查的URL
52
- * @param {string[]} customKeywords - 自定义关键词列表
53
- * @returns {boolean} 包含敏感词返回true,否则返回false
54
- */
55
- const checkKeywords = (url, customKeywords) => {
56
- // 合并默认关键词和自定义关键词,并进行转义处理
57
- const allKeywords = [
58
- ...defaultKeywords.map(escapeRegExp),
59
- ...customKeywords.map(escapeRegExp)
60
- ];
61
- // 创建不区分大小写的正则表达式
62
- const keywordReg = new RegExp(allKeywords.join("|"), "i");
63
- return keywordReg.test(url);
64
- };
65
-
66
- /**
67
- * 设置X-Frame-Options响应头,防止点击劫持攻击
68
- * @param {Object} res - Express响应对象
69
- * @param {string} frameOption - 可选值:DENY/SAMEORIGIN/ALLOW-FROM
70
- * @param {string} [allowFromUrl] - 当frameOption为ALLOW-FROM时的允许地址
71
- */
72
- const setXFrameOptions = (res, frameOption, allowFromUrl) => {
73
- const options = {
74
- DENY: "DENY", // 禁止任何页面嵌入
75
- SAMEORIGIN: "SAMEORIGIN", // 只允许同源页面嵌入
76
- "ALLOW-FROM": `ALLOW-FROM ${allowFromUrl || ''}` // 允许指定来源嵌入
77
- };
78
-
79
- // 验证参数有效性
80
- if (!Object.keys(options).includes(frameOption)) {
81
- console.warn(`无效的X-Frame-Options配置: ${frameOption},已自动使用SAMEORIGIN`);
82
- res.set("X-Frame-Options", "SAMEORIGIN");
83
- return;
84
- }
85
-
86
- // 处理ALLOW-FROM的特殊情况
87
- if (frameOption === "ALLOW-FROM" && !allowFromUrl) {
88
- console.warn("使用ALLOW-FROM时必须指定allowFromUrl,已自动使用SAMEORIGIN");
89
- res.set("X-Frame-Options", "SAMEORIGIN");
90
- return;
91
- }
92
-
93
- res.set("X-Frame-Options", options[frameOption]);
94
- };
95
-
96
- /**
97
- * 安全中间件主函数
98
- * @param {Object} options - 配置选项
99
- * @param {string[]} [options.keywords=[]] - 自定义敏感关键词列表
100
- * @param {string} [options.sendText="未知请求,已阻止..."] - 拦截时返回的文本
101
- * @param {string} [options.frameOption="SAMEORIGIN"] - X-Frame-Options配置
102
- * @param {string} [options.allowFromUrl] - 当frameOption为ALLOW-FROM时的允许地址
103
- * @returns {Function} Express中间件函数
104
- * @example
105
- app.use(safe({
106
- keywords: ['custom-badword'], // 自定义敏感词
107
- frameOption: 'DENY', // 禁止任何页面嵌入
108
- sendText: '请求被安全策略拦截'
109
- }));
110
- */
111
- export const safe = (options = {}) => {
112
- // 合并默认配置和用户配置
113
- const params = {
114
- keywords: [],
115
- sendText: "未知请求,已阻止...",
116
- frameOption: "SAMEORIGIN",
117
- allowFromUrl: "",
118
- ...options
119
- };
120
-
121
- // 预编译关键词正则(优化性能:只编译一次)
122
- const allKeywords = [
123
- ...defaultKeywords.map(escapeRegExp),
124
- ...params.keywords.map(escapeRegExp)
125
- ];
126
- const keywordReg = new RegExp(allKeywords.join("|"), "i");
127
-
128
- // 返回中间件函数
129
- return (req, res, next) => {
130
- // 1. 设置X-Frame-Options头防止点击劫持
131
- setXFrameOptions(res, params.frameOption, params.allowFromUrl);
132
-
133
- // 2. 检查URL中是否包含敏感关键词
134
- if (keywordReg.test(req.url)) {
135
- console.error(`[安全拦截] 疑似恶意请求: ${req.method} ${req.url} 来自IP: ${req.ip}`);
136
- return res.status(403).send(params.sendText);
137
- }
138
-
139
- // 检查通过,继续处理请求
140
- next();
141
- };
142
- };
143
-
144
-
package/utils/bind.js DELETED
@@ -1,21 +0,0 @@
1
-
2
- /**
3
- * @description 实例化一个类,并将该类的所有方法绑定到一个新的对象上。
4
- * @param {Function} className - 需要实例化的类。
5
- *@returns {Object} 包含绑定方法的对象。
6
- */
7
- export const bindClass = function(className) {
8
- let obj = {};
9
- const cls = new className();
10
- Object.getOwnPropertyNames(cls.constructor.prototype).forEach(
11
- (methodName) => {
12
- if (
13
- methodName !== "constructor" &&
14
- typeof cls[methodName] === "function"
15
- ) {
16
- obj[methodName] = cls[methodName].bind(cls);
17
- }
18
- }
19
- );
20
- return obj;
21
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * 将数组转换为键值对对象
3
- * @param {Array} arr - 包含键值对的数组
4
- * @param {string} keyField - 作为键的字段名
5
- * @param {string} valueField - 作为值的字段名
6
- * @returns {Object} 转换后的对象
7
- */
8
- export const arrToObj = (arr, keyField = 'config_key', valueField = 'config_value') => {
9
- // 输入验证
10
- if (!Array.isArray(arr)) {
11
- throw new Error('arrToObj 期望接收数组作为第一个参数');
12
- }
13
-
14
- return arr.reduce((result, item) => {
15
- if (item && typeof item === 'object') {
16
- const key = item[keyField];
17
- const value = item[valueField];
18
- if (key !== undefined && value !== undefined) {
19
- result[key] = value;
20
- }
21
- }
22
- return result;
23
- }, {});
24
- };
package/utils/db.js DELETED
@@ -1,75 +0,0 @@
1
- import knex from "knex";
2
- export const db = function ({
3
- client = "mysql2",
4
- host = "127.0.0.1",
5
- user = "root",
6
- password = "123456",
7
- database = "test",
8
- port = 3306,
9
- debug = true,
10
- charset = "utf8mb4",
11
- min = 0,
12
- max = 2,
13
- }) {
14
- let config = {
15
- client,
16
- connection: {
17
- host,
18
- port,
19
- user,
20
- password,
21
- database,
22
- charset,
23
- },
24
- debug,
25
- pool: {
26
- //默认为{min: 2, max: 10},连接池配置
27
- min,
28
- max,
29
- },
30
- log: {
31
- warn(message) {
32
- console.error("[knex warn]", message);
33
- },
34
- error(message) {
35
- console.error("[knex error]", message);
36
- },
37
- debug(message) {
38
- console.log("[knex debug]", message);
39
- },
40
- deprecate(message) {
41
- console.warn("[knex deprecate]", message);
42
- },
43
- trace(message) {
44
- console.log("[knex trace]", message);
45
- },
46
- log(message) {
47
- console.log("[knex log]", message);
48
- },
49
- info(message) {
50
- console.log("[knex info]", message);
51
- },
52
- },
53
- };
54
- return knex(config);
55
- };
56
-
57
- const errCode = {
58
- ECONNREFUSED: "数据库连接被拒绝,请检查数据库服务是否正常运行。",
59
- ER_ACCESS_DENIED_ERROR: "无权限访问,账号或密码错误。",
60
- ER_ROW_IS_REFERENCED_2: "无法删除或更新记录,存在关联数据。",
61
- ER_BAD_FIELD_ERROR: "SQL语句中包含无效字段,请检查查询条件或列名。",
62
- ER_DUP_ENTRY: "插入失败:数据重复,违反唯一性约束。",
63
- ER_NO_SUCH_TABLE: "操作失败:目标表不存在。",
64
- ETIMEOUT: "数据库操作超时,请稍后再试。",
65
- };
66
- const getDefaultErrorMessage = (error) => {
67
- if (error.message.includes("syntax") || error.message.includes("SQL")) {
68
- return "数据库语法错误,请检查您的查询语句。";
69
- } else if (error.message.includes("Connection closed")) {
70
- return "数据库连接已关闭,请重试。";
71
- } else if (error.message.includes("permission")) {
72
- return "数据库权限不足,请检查配置。";
73
- }
74
- return "数据库发生未知错误,请稍后重试。";
75
- };
package/utils/index.js DELETED
@@ -1,12 +0,0 @@
1
- import {bindClass} from './bind.js'
2
- import {db} from './db.js'
3
- import {loaderSort,getPackage,loadConfig,loadController} from './loader.js'
4
-
5
- export {
6
- bindClass,
7
- db,
8
- loaderSort,
9
- getPackage,
10
- loadConfig,
11
- loadController
12
- }
package/utils/loader.js DELETED
@@ -1,65 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { bindClass } from "./bind.js";
4
-
5
- /**
6
- *
7
- * @param {*} module 模块目录
8
- * @returns Array
9
- * @description 将web模块放到最后加载
10
- */
11
- export const loaderSort = (modules = []) => {
12
- const index = modules.indexOf("web");
13
- if (index !== -1) {
14
- const web = modules.splice(index, 1);
15
- modules.push(web[0]);
16
- }
17
- return modules;
18
- };
19
-
20
- export const getPackage = async function () {
21
- let pkg = await importFile("package.json");
22
- return pkg;
23
- };
24
-
25
- export const loadConfig = async function () {
26
- let config = await importFile("config/index.js");
27
- console.log("config", config);
28
- return config;
29
- };
30
-
31
- /**
32
- * 加载指定模块名下的所有控制器文件
33
- * @param {string} moduleName - 模块名称
34
- * @returns {Promise<Object>} - 控制器对象
35
- */
36
- export const loadController = async function (moduleName) {
37
- const controller = {};
38
-
39
- const dir = path.join(MODULES_PATH, moduleName, "controller");
40
-
41
- if (!fs.existsSync(dir)) {
42
- console.warn(`模块路径不存在,跳过加载控制器: ${dir}`);
43
- return controller;
44
- }
45
-
46
- const files = fs.readdirSync(dir).filter((file) => file.endsWith(".js"));
47
-
48
- for (const file of files) {
49
- const filePath = path.join(dir, file);
50
- const name = file.replace(/\.js$/i, ""); // 安全处理 .js 后缀
51
-
52
- try {
53
- const module = await importFile(filePath);
54
- let obj = module.default || module;
55
-
56
- controller[name] = obj;
57
- } catch (e) {
58
- console.error(`加载控制器失败: ${filePath}`, e);
59
- // 可选:抛出错误或继续加载其他文件
60
- // throw e;
61
- }
62
- }
63
-
64
- return controller;
65
- };
package/utils/response.js DELETED
@@ -1,20 +0,0 @@
1
- export function parseJsonFields(obj) {
2
- const result = {};
3
- for (const key in obj) {
4
- if (!obj.hasOwnProperty(key)) continue;
5
- const value = obj[key];
6
- // 如果是字符串,并且看起来像 JSON(以 { 或 [ 开头)
7
- if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
8
- try {
9
- result[key] = JSON.parse(value);
10
- } catch (e) {
11
- console.warn(`JSON parse failed for field: ${key}`, e);
12
- result[key] = value; // 保留原始值
13
- }
14
- } else {
15
- result[key] = value;
16
- }
17
- }
18
-
19
- return result;
20
- }