83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
// 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 });
|
||
}
|
||
}); |