Просмотр исходного кода

✨ feat(middleware): add enhanced permission logging middleware

- create permission-auto-log.middleware.ts with automatic resource type detection
- implement permissionWithAutoLog function that combines permission check and logging
- add intelligent resource type detection from permissions and URL paths
- record permission denied and error events with detailed logs
- provide simplified usage with permissionLog export and backward compatibility with permissionWithLog
- generate unique log IDs and extract related resource IDs from request parameters
yourname 7 месяцев назад
Родитель
Сommit
e3d0ee67c8
1 измененных файлов с 134 добавлено и 0 удалено
  1. 134 0
      src/server/middleware/permission-auto-log.middleware.ts

+ 134 - 0
src/server/middleware/permission-auto-log.middleware.ts

@@ -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);