简体   繁体   English

NestJS + CASL + Mongoose:CASL 无法从 Mongoose Schema 推断主题类型

[英]NestJS + CASL + Mongoose: CASL cannot infer subject type from Mongoose Schema

Context语境

I have defined a Cat schema using Mongoose and NestJS:我已经使用 Mongoose 和 NestJS 定义了一个Cat模式:

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

I have also defined an ability factory using CASL to handle authorization for my API:我还定义了一个使用 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>,
    });
  }
}

Problem问题

When I try to check permissions in my service (note: the user is not an administrator):当我尝试检查我的服务中的权限时(注意:用户不是管理员):

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) always return false , while it should be true . ability.can(Action.Update, cat)总是返回false ,而它应该是true

Additional information附加信息

ability.can returns false for any action when the user is not administrator.当用户不是管理员时, ability.can对任何操作都返回false

My guess is that CASL cannot infer the subject type of my Mongoose model using the Cat class in InferSubject interface of the ability builder.我的猜测是 CASL 无法使用能力构建器的InferSubject接口中的Cat类来推断我的 Mongoose 模型的主题类型。 But I don't know which class to use to infer subject type correctly.但我不知道使用哪个类来正确推断主题类型。

What did I miss?我错过了什么?

So the key is to inject the cat model over nest js injection in the casl ability factory.所以关键是在casl能力工厂中注入cat模型而不是nest js注入。 So a factory like this should work:所以像这样的工厂应该可以工作:

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'>,
      });
    }
  }

full example project: https://github.com/Wenish/nestjs-mongoose-casl完整示例项目: https ://github.com/Wenish/nestjs-mongoose-casl

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM