2

语境

我已经Cat使用 Mongoose 和 NestJS 定义了一个模式:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type CatDocument = Cat & Document;

@Schema()
export class Cat {
  @Prop({ required: true })
  name: string;

  @Prop({ required: true })
  breed: string;

  @Prop({ required: true })
  createdBy: string;

  // Other fields...
}

export const CatSchema = SchemaFactory.createForClass(Cat);

我还定义了一个使用 CASL 处理我的 API 授权的能力工厂:

import {
  Ability,
  AbilityBuilder,
  AbilityClass,
  ExtractSubjectType,
  InferSubjects,
} from '@casl/ability';
import { Injectable } from '@nestjs/common';
import { Cat } from '../cats/schemas/cat.schema';
import { User } from '../users/models/user.model';

type Subjects = InferSubjects<typeof Cat | typeof User> | 'all';

export enum Action {
  Manage = 'manage',
  Create = 'create',
  Read = 'read',
  Update = 'update',
  Delete = 'delete',
}

export type AppAbility = Ability<[Action, Subjects]>;

@Injectable()
export class CaslAbilityFactory {
  createForUser(user: User) {
    const { can, build } = new AbilityBuilder<
      Ability<[Action, Subjects]>
    >(Ability as AbilityClass<AppAbility>);

    can(Action.Read, Cat);
    can(Action.Create, Cat);
    can(Action.Update, Cat, {
      createdBy: user.id,
    });

    if (user.isAdmin()) {
      can(Action.Manage, 'all');
    }

    return build({
      detectSubjectType: (item) =>
        item.constructor as ExtractSubjectType<Subjects>,
    });
  }
}

问题

当我尝试检查我的服务中的权限时(注意:用户不是管理员):

import {
  ForbiddenException,
  Injectable,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { EditCatInput } from './dto/edit-cat.input';
import { Cat, CatDocument } from './schemas/cat.schema';
import { Action, CaslAbilityFactory } from '../casl/casl-ability.factory';
import { User } from '../users/models/user.model';

@Injectable()
export class CatsService {
  constructor(
    private readonly configService: ConfigService,
    private readonly caslAbilityFactory: CaslAbilityFactory,
    @InjectModel(Cat.name) private catModel: Model<CatDocument>,
  ) {}

  // Other methods...

  async update(id: string, input: EditCatInput, user: User): Promise<Cat> {
    const updatedCat = await this.catModel
      .findOne({
        _id: { $eq: id },
        deleted: { $eq: false },
      })
      .exec();
    const ability = this.caslAbilityFactory.createForUser(user);
    if (!ability.can(Action.Update, updatedCat)) {
      throw new ForbiddenException(
        'You cannot edit this cat.',
      );
    }
    Object.assign(updatedCat, input);
    await updatedCat.save();

    return updatedCat;
  }
}

ability.can(Action.Update, cat)总是返回false,而它应该是true

附加信息

ability.canfalse当用户不是管理员时返回任何操作。

我的猜测是 CASL 无法使用能力构建器接口中的Cat类来推断我的 Mongoose 模型的主题类型。InferSubject但我不知道使用哪个类来正确推断主题类型。

我错过了什么?

4

1 回答 1

0

所以关键是在casl能力工厂中注入cat模型而不是nest js注入。所以像这样的工厂应该可以工作:

import {
    Ability,
    AbilityBuilder,
    AbilityClass,
    ExtractSubjectType,
    InferSubjects,
  } from '@casl/ability';
  import { Injectable } from '@nestjs/common';
  import { InjectModel } from '@nestjs/mongoose';
  import { Model } from 'mongoose';
  import { Cat, CatDocument } from '../cats/schemas/cat.schema';
  import { User } from '../users/models/user.model';
  
  export enum Action {
    Manage = 'manage',
    Create = 'create',
    Read = 'read',
    Update = 'update',
    Delete = 'delete',
  }
  
  @Injectable()
  export class CaslAbilityFactory {
    constructor(
        @InjectModel(Cat.name)
        private catModel: Model<CatDocument>,
    ) {}
    createForUser(user: User) {
      const { can, build } = new AbilityBuilder(
        Ability as AbilityClass<
          Ability<[Action, InferSubjects<typeof this.catModel> | 'all']>
        >,
      );
  
      can(Action.Read, this.catModel);
      can(Action.Create, this.catModel);
      can(Action.Update, this.catModel, {
        createdBy: user.id,
      });
  
      if (user.isAdmin()) {
        can(Action.Manage, 'all');
      }
  
      return build({
        detectSubjectType: (item) =>
          item.constructor as ExtractSubjectType<InferSubjects<typeof this.catModel> | 'all'>,
      });
    }
  }
于 2022-02-08T18:31:21.647 回答