This commit is contained in:
2025-11-27 18:22:26 +08:00
commit 90eb1970e1
12 changed files with 767 additions and 0 deletions

83
src/security.ts Normal file
View File

@@ -0,0 +1,83 @@
// src/security.ts
import { Elysia, Context } from 'elysia';
// 脱敏规则配置
const MASK_RULES: Record<string, (value: string) => string> = {
// 手机号138****1234
mobile: (v) => v.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
phone: (v) => v.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
// 邮箱ab***@example.com
email: (v) => v.replace(/(.{2}).+(@.*)/, '$1***$2'),
// 身份证110***********1234
id_card: (v) => v.length > 8
? v.substring(0, 3) + '*'.repeat(v.length - 7) + v.substring(v.length - 4)
: v,
// 银行卡6222 **** **** 1234
bank_card: (v) => v.replace(/(\d{4})\d+(\d{4})/, '$1 **** **** $2'),
// 用户名:如果长度>4则中间用*
nickname: (v) => v.length > 4
? v.substring(0, 2) + '*'.repeat(v.length - 4) + v.substring(v.length - 2)
: v,
// 默认规则
default: (v) => v.length > 6
? v.substring(0, 2) + '*'.repeat(v.length - 4) + v.substring(v.length - 2)
: v
};
// 判断是否为敏感字段(支持模糊匹配)
const isSensitiveField = (key: string): boolean => {
const sensitiveKeys = ['mobile', 'phone', 'email', 'id_card', 'bank_card', 'nickname', 'realname', 'address'];
return sensitiveKeys.some(term => key.toLowerCase().includes(term));
};
// 获取脱敏函数
const getMaskFn = (key: string): ((v: string) => string) => {
for (const [term, fn] of Object.entries(MASK_RULES)) {
if (key.toLowerCase().includes(term)) {
return fn;
}
}
return MASK_RULES.default || ((v: string) => v);
};
// 递归脱敏
export const sanitizeResult = (data: any): any => {
if (Array.isArray(data)) {
return data.map(sanitizeResult);
}
if (typeof data === 'object' && data !== null) {
const result: Record<string, any> = {};
for (const [key, value] of Object.entries(data)) {
if (value == null) {
result[key] = value;
} else if (isSensitiveField(key)) {
result[key] = typeof value === 'string' ? getMaskFn(key)(value) : value;
} else if (typeof value === 'object') {
result[key] = sanitizeResult(value);
} else {
result[key] = value;
}
}
return result;
}
return data;
};
// API Key 验证(修复:使用 Elysia 插件类型)
export const verifyApiKey = (app: Elysia) => app.onBeforeHandle((ctx) => {
const authHeader = ctx.request.headers.get('authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 });
}
const token = authHeader.substring(7);
if (token !== process.env.ADMIN_API_KEY) {
return new Response('Forbidden', { status: 403 });
}
});