epic-006-shared-crud-data-permission-enhancement.md 13 KB

Epic-006: Shared-CRUD 数据权限控制增强 - Brownfield Enhancement

Epic Goal

增强 shared-crud 包的数据权限控制能力,支持基于用户ID的数据访问权限控制,使业务模块能够轻松实现"用户只能操作自己的数据"的安全需求,同时保持向后兼容性和配置灵活性。

Epic Description

Existing System Context

Current relevant functionality:

  • shared-crud 包已提供基础的 CRUD 操作和用户跟踪字段自动填充
  • 现有 userTracking 配置支持创建和更新时自动设置用户ID字段
  • 认证系统通过 JWT token 在上下文中提供用户信息
  • 业务模块使用 shared-crud 作为数据访问层基础设施

Current limitations:

  • 查询操作没有基于用户ID的数据过滤
  • 删除操作没有权限验证
  • 创建操作没有权限限制(用户可以创建不属于自己的数据)
  • 更新操作没有权限验证(用户可以更新不属于自己的数据)
  • 缺乏统一的数据权限控制配置
  • 业务模块需要手动实现数据权限逻辑

Technology stack:

  • Backend: Node.js, TypeScript, Hono, TypeORM, PostgreSQL
  • Authentication: JWT, Redis session management
  • Database: PostgreSQL with user tracking fields
  • Package: shared-crud, shared-types, shared-utils

Integration points:

  • 现有认证中间件提供用户上下文
  • 业务模块实体中的用户关联字段(userId, createdBy, updatedBy)
  • 现有的 CRUD 路由和服务层
  • 现有的用户跟踪字段自动填充机制

Enhancement Details

What's being added/changed:

  • 数据权限控制配置选项,支持基于用户ID的数据过滤
  • 查询操作的自动权限过滤
  • 创建操作的权限限制(防止创建不属于自己的数据)
  • 更新操作的权限验证
  • 删除操作的权限验证
  • 管理员权限覆盖机制
  • 向后兼容的权限控制配置

Enhanced architecture:

shared-crud/
├── src/
│   ├── services/
│   │   ├── generic-crud.service.ts (增强权限控制)
│   │   └── concrete-crud.service.ts
│   ├── routes/
│   │   └── generic-crud.routes.ts (增强权限中间件)
│   ├── types/
│   │   └── data-permission.types.ts (新增权限类型定义)
│   └── middleware/
│       └── data-permission.middleware.ts (新增权限中间件)

How it integrates:

  • 扩展现有的 CrudOptions 接口,添加数据权限配置
  • 复用现有的用户跟踪字段配置
  • 保持现有 API 不变,新增配置为可选
  • 与现有认证系统无缝集成
  • 提供灵活的权限控制粒度

Success criteria:

  • 现有功能不受影响,保持向后兼容
  • 业务模块可以轻松启用数据权限控制
  • 查询、创建、更新、删除操作自动进行权限验证
  • 创建操作防止用户创建不属于自己的数据
  • 提供管理员权限覆盖机制
  • 完整的单元测试和集成测试覆盖
  • 清晰的配置文档和使用示例

Stories

阶段 1: 核心权限控制功能

  1. Story 1: Shared-CRUD 数据权限控制完整实现 - 实现完整的数据权限控制功能,包括:
    • 数据权限控制类型定义和配置接口
    • 查询操作的自动权限过滤(getList 和 getById)
    • 创建操作的权限限制(防止创建不属于自己的数据)
    • 更新操作的权限验证
    • 删除操作的权限验证
    • 权限控制中间件
    • 管理员权限覆盖机制
    • 完整的单元测试和集成测试

阶段 2: 高级功能和集成

  1. Story 2: 高级权限功能和业务模块集成 - 实现高级权限功能和集成示例:
    • 多租户数据隔离支持
    • 自定义权限验证钩子
    • 业务模块集成示例(用户模块)
    • 性能优化和基准测试
    • 完整的使用文档和迁移指南

Compatibility Requirements

  • 现有 APIs 保持不变,权限控制为可选配置
  • 现有业务模块无需修改即可继续使用
  • 数据库 schema 保持不变
  • 性能影响最小化,权限过滤使用数据库索引
  • 配置向后兼容,现有 userTracking 配置继续有效

Risk Mitigation

Primary Risk: 权限控制影响现有查询性能 Mitigation: 使用数据库索引优化权限过滤查询,提供性能基准测试 Rollback Plan: 权限控制为可选配置,可随时禁用

Primary Risk: 复杂的权限配置影响开发体验 Mitigation: 提供简化的默认配置和清晰的文档 Rollback Plan: 保持现有配置方式不变,新增配置可选

Primary Risk: 权限验证逻辑与业务逻辑耦合 Mitigation: 权限控制作为基础设施层,与业务逻辑分离 Rollback Plan: 权限验证可独立禁用

Definition of Done

  • Story 1 完成且验收标准满足
  • 现有功能通过回归测试验证
  • 权限控制配置灵活且易于使用
  • 性能基准测试通过,无明显性能下降
  • 完整的单元测试和集成测试覆盖
  • 使用文档和示例代码完整
  • 向后兼容性验证通过

架构设计详情

权限控制配置设计

// 扩展现有的 CrudOptions
export interface DataPermissionOptions {
  enabled: boolean;
  userIdField: string; // 用户ID字段名,如 'userId', 'createdBy'
  adminOverride?: {
    enabled: boolean;
    adminRole?: string; // 管理员角色标识
    userIdField?: string; // 管理员用户ID字段
  };
  multiTenant?: {
    enabled: boolean;
    tenantIdField: string; // 租户ID字段名
  };
  customValidator?: (userId: string | number, entity: any) => Promise<boolean>;
}

// 扩展 CrudOptions
export type CrudOptions<T extends ObjectLiteral, ...> = {
  // ... 现有配置
  userTracking?: UserTrackingOptions;
  dataPermission?: DataPermissionOptions; // 新增权限控制配置
  // ...
};

权限控制实现设计

查询权限过滤

// 在 getList 方法中添加权限过滤
async getList(
  page: number = 1,
  pageSize: number = 10,
  keyword?: string,
  searchFields?: string[],
  where?: Partial<T>,
  relations: string[] = [],
  order: { [P in keyof T]?: 'ASC' | 'DESC' } = {},
  filters?: { [key: string]: any },
  userId?: string | number // 新增用户ID参数
): Promise<[T[], number]> {
  // 构建基础查询
  const query = this.repository.createQueryBuilder('entity');

  // 添加权限过滤条件
  if (this.dataPermissionOptions?.enabled && userId) {
    const userIdField = this.dataPermissionOptions.userIdField;
    query.andWhere(`entity.${userIdField} = :userId`, { userId });
  }

  // ... 现有查询逻辑
}

创建权限限制

async create(data: DeepPartial<T>, userId?: string | number): Promise<T> {
  // 权限验证:防止用户创建不属于自己的数据
  if (this.dataPermissionOptions?.enabled && userId) {
    const userIdField = this.dataPermissionOptions.userIdField;

    // 如果数据中已经包含用户ID字段,验证是否与当前用户匹配
    if (data[userIdField] && data[userIdField] !== userId) {
      throw new Error('无权创建该资源');
    }
  }

  const entityData = { ...data };
  this.setUserFields(entityData, userId, true);

  // ... 现有创建逻辑
}

更新权限验证

async update(id: number, data: Partial<T>, userId?: string | number): Promise<T | null> {
  // 权限验证
  if (this.dataPermissionOptions?.enabled && userId) {
    const entity = await this.getById(id);
    if (!entity) return null;

    const userIdField = this.dataPermissionOptions.userIdField;
    const entityUserId = (entity as any)[userIdField];

    if (entityUserId !== userId) {
      throw new Error('无权更新该资源');
    }
  }

  const updateData = { ...data };
  this.setUserFields(updateData, userId, false);

  // ... 现有更新逻辑
}

删除权限验证

async delete(id: number, userId?: string | number): Promise<boolean> {
  // 权限验证
  if (this.dataPermissionOptions?.enabled && userId) {
    const entity = await this.getById(id);
    if (!entity) return false;

    const userIdField = this.dataPermissionOptions.userIdField;
    const entityUserId = (entity as any)[userIdField];

    if (entityUserId !== userId) {
      throw new Error('无权删除该资源');
    }
  }

  // 执行删除
  const result = await this.repository.delete(id);
  return result.affected === 1;
}

路由层集成设计

权限控制中间件

export function dataPermissionMiddleware(
  options: DataPermissionOptions
): MiddlewareHandler<AuthContext> {
  return async (c, next) => {
    if (!options.enabled) {
      return next();
    }

    const user = c.get('user');
    if (!user?.id) {
      return c.json({ code: 401, message: '未授权访问' }, 401);
    }

    // 设置用户ID到上下文,供服务层使用
    c.set('dataPermissionUserId', user.id);

    await next();
  };
}

路由配置示例

// 现有配置方式(保持兼容)
createCrudRoutes({
  entity: UserEntity,
  createSchema: CreateUserSchema,
  updateSchema: UpdateUserSchema,
  getSchema: GetUserSchema,
  listSchema: ListUserSchema,
  userTracking: { userIdField: 'userId' },
  // 新增权限控制配置
  dataPermission: {
    enabled: true,
    userIdField: 'userId',
    adminOverride: {
      enabled: true,
      adminRole: 'admin'
    }
  }
});

管理员权限覆盖机制

// 在权限验证中添加管理员检查
private async checkPermission(
  entity: any,
  userId: string | number
): Promise<boolean> {
  const options = this.dataPermissionOptions;
  if (!options?.enabled) return true;

  // 管理员权限覆盖
  if (options.adminOverride?.enabled) {
    const user = await this.getCurrentUser(userId);
    if (user?.role === options.adminOverride.adminRole) {
      return true;
    }
  }

  // 普通用户权限验证
  const userIdField = options.userIdField;
  const entityUserId = (entity as any)[userIdField];
  return entityUserId === userId;
}

使用示例

基础权限控制配置

// 用户只能操作自己的数据
createCrudRoutes({
  entity: UserProfileEntity,
  // ... 其他配置
  userTracking: { userIdField: 'userId' },
  dataPermission: {
    enabled: true,
    userIdField: 'userId'
  }
});

管理员权限覆盖配置

// 管理员可以查看所有用户数据
createCrudRoutes({
  entity: UserProfileEntity,
  // ... 其他配置
  userTracking: { userIdField: 'userId' },
  dataPermission: {
    enabled: true,
    userIdField: 'userId',
    adminOverride: {
      enabled: true,
      adminRole: 'admin'
    }
  }
});

多租户数据隔离

// 支持多租户数据隔离
createCrudRoutes({
  entity: TenantDataEntity,
  // ... 其他配置
  dataPermission: {
    enabled: true,
    userIdField: 'createdBy',
    multiTenant: {
      enabled: true,
      tenantIdField: 'tenantId'
    }
  }
});

自定义权限验证

// 自定义复杂的权限验证逻辑
createCrudRoutes({
  entity: ComplexEntity,
  // ... 其他配置
  dataPermission: {
    enabled: true,
    userIdField: 'ownerId',
    customValidator: async (userId, entity) => {
      // 自定义权限验证逻辑
      return await someComplexPermissionCheck(userId, entity);
    }
  }
});

技术实现要点

向后兼容性保证

  1. 配置可选: 数据权限控制为可选配置,默认禁用
  2. API 不变: 现有 API 接口和行为保持不变
  3. 性能优化: 权限过滤使用数据库索引,性能影响最小化
  4. 错误处理: 权限验证失败返回明确的错误信息

安全性考虑

  1. 默认安全: 启用权限控制后,默认遵循最小权限原则
  2. 输入验证: 所有用户输入进行严格的验证和转义
  3. SQL 注入防护: 使用参数化查询防止 SQL 注入
  4. 错误信息: 权限错误信息不泄露敏感数据

性能优化

  1. 数据库索引: 确保用户ID字段有合适的索引
  2. 查询优化: 权限过滤条件与现有查询条件合并
  3. 缓存策略: 考虑权限验证结果的缓存
  4. 批量操作: 支持批量操作的权限验证优化

测试策略

单元测试

  • 权限配置验证测试
  • 查询权限过滤测试
  • 创建权限限制测试
  • 更新权限验证测试
  • 删除权限验证测试
  • 管理员权限覆盖测试
  • 自定义权限验证测试

集成测试

  • 完整 CRUD 操作的权限控制测试(创建、查询、更新、删除)
  • 多用户场景的权限隔离测试
  • 创建操作权限限制场景测试
  • 更新操作权限验证场景测试
  • 管理员权限覆盖场景测试
  • 性能基准测试

回归测试

  • 现有功能回归测试
  • 向后兼容性验证测试
  • 配置迁移测试

Story Manager Handoff

"请为这个brownfield epic开发详细的用户故事。关键考虑因素:

  • 这是一个对现有 shared-crud 包的增强,运行在 Node.js + TypeScript + Hono + TypeORM + PostgreSQL 技术栈上
  • 集成点:现有认证系统、用户跟踪字段、CRUD 服务层、路由配置
  • 需要遵循的现有模式:TypeORM 查询构建、Hono 中间件、配置驱动开发
  • 关键兼容性要求:现有配置和 API 保持不变、性能影响最小化、错误处理一致
  • 故事必须包含验证现有功能保持完整的回归测试

该epic应该保持系统完整性,同时实现 shared-crud 包的数据权限控制增强,为业务模块提供安全、灵活的数据访问控制能力。"