|
|
@@ -0,0 +1,134 @@
|
|
|
+import { Context, Next } from 'hono';
|
|
|
+import { checkPermission } from './permission.middleware';
|
|
|
+import { LogfileService } from '@/server/modules/logs/logfile.service';
|
|
|
+import { AppDataSource } from '@/server/data-source';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 增强版权限日志中间件
|
|
|
+ * 自动从权限字符串和URL路径提取资源类型
|
|
|
+ */
|
|
|
+export function permissionWithAutoLog(requiredPermissions: string[]) {
|
|
|
+ const logService = new LogfileService(AppDataSource);
|
|
|
+ const permissionChecker = checkPermission(requiredPermissions);
|
|
|
+
|
|
|
+ return async (c: Context, next: Next) => {
|
|
|
+ const user = c.get('user');
|
|
|
+ const method = c.req.method;
|
|
|
+ const url = new URL(c.req.url);
|
|
|
+ const path = url.pathname;
|
|
|
+ const params = c.req.param();
|
|
|
+
|
|
|
+ // 自动检测资源类型
|
|
|
+ const resourceClass = detectResourceType(requiredPermissions, path);
|
|
|
+
|
|
|
+ // 获取相关ID
|
|
|
+ const relatedId = params.id || null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 执行权限检查
|
|
|
+ const hasPermission = await permissionChecker(user);
|
|
|
+
|
|
|
+ if (!hasPermission) {
|
|
|
+ // 记录权限拒绝
|
|
|
+ await logService.create({
|
|
|
+ id: generateLogId(),
|
|
|
+ class: resourceClass,
|
|
|
+ action: 'permission_denied',
|
|
|
+ relatedId: relatedId ? String(relatedId) : undefined,
|
|
|
+ reason: `权限被拒绝: 需要权限 ${requiredPermissions.join(', ')}`,
|
|
|
+ logTime: new Date()
|
|
|
+ }, user?.id);
|
|
|
+
|
|
|
+ return c.json({ message: '没有权限访问该资源', code: 403 }, 403);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 继续执行后续处理
|
|
|
+ await next();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ // 记录权限检查异常
|
|
|
+ await logService.create({
|
|
|
+ id: generateLogId(),
|
|
|
+ class: resourceClass,
|
|
|
+ action: 'permission_error',
|
|
|
+ relatedId: relatedId ? String(relatedId) : undefined,
|
|
|
+ reason: `权限检查异常: ${error instanceof Error ? error.message : '未知错误'}`,
|
|
|
+ logTime: new Date()
|
|
|
+ }, user?.id);
|
|
|
+
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 智能资源类型检测
|
|
|
+function detectResourceType(permissions: string[], path: string): string {
|
|
|
+ if (!permissions || permissions.length === 0) {
|
|
|
+ return extractFromPath(path);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从权限字符串提取
|
|
|
+ const permission = permissions[0];
|
|
|
+ const resource = extractFromPermission(permission);
|
|
|
+ if (resource !== 'unknown') {
|
|
|
+ return resource;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从路径提取
|
|
|
+ return extractFromPath(path);
|
|
|
+}
|
|
|
+
|
|
|
+// 从权限字符串提取资源类型
|
|
|
+function extractFromPermission(permission: string): string {
|
|
|
+ const parts = permission.split(':');
|
|
|
+
|
|
|
+ if (parts.length >= 2) {
|
|
|
+ // 处理 system:user:create 格式
|
|
|
+ if (parts[0] === 'system' && parts[1]) {
|
|
|
+ return parts[1];
|
|
|
+ }
|
|
|
+ // 处理 client:view 或 client:view:all 格式
|
|
|
+ return parts[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ return parts[0] || 'unknown';
|
|
|
+}
|
|
|
+
|
|
|
+// 从URL路径提取资源类型
|
|
|
+function extractFromPath(path: string): string {
|
|
|
+ // 匹配 /api/v1/users 或 /api/v1/users/123
|
|
|
+ const match = path.match(/\/api\/v\d+\/([^/]+)/);
|
|
|
+ if (match && match[1]) {
|
|
|
+ let resource = match[1];
|
|
|
+ // 转换为单数形式
|
|
|
+ resource = resource.replace(/s$/, '');
|
|
|
+ return resource;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 匹配 /api/v1/contract-renews -> contract_renew
|
|
|
+ const hyphenMatch = path.match(/\/api\/v\d+\/([^/]+)/);
|
|
|
+ if (hyphenMatch && hyphenMatch[1]) {
|
|
|
+ return hyphenMatch[1].replace(/-/g, '_');
|
|
|
+ }
|
|
|
+
|
|
|
+ return 'unknown';
|
|
|
+}
|
|
|
+
|
|
|
+// 生成唯一日志ID
|
|
|
+function generateLogId(): string {
|
|
|
+ const timestamp = Date.now();
|
|
|
+ const random = Math.random().toString(36).substring(2, 8);
|
|
|
+ return `LOG${timestamp}${random}`;
|
|
|
+}
|
|
|
+
|
|
|
+// 简化使用方式 - 直接导入使用
|
|
|
+export const permissionLog = permissionWithAutoLog;
|
|
|
+
|
|
|
+// 向后兼容的简化版本
|
|
|
+export function permissionWithLog(requiredPermissions: string[]) {
|
|
|
+ return permissionWithAutoLog(requiredPermissions);
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例:
|
|
|
+// app.get('/api/users', authMiddleware, permissionWithLog(['system:user:view']), handler);
|
|
|
+// app.post('/api/clients', authMiddleware, permissionWithLog(['client:create']), handler);
|