背景
我已经使用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);
我还使用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.can
为任何操作返回false
。
我猜CASL无法使用能力构建器InferSubject
接口中的Cat
类来推断Mongoose模型的主题类型,但我不知道使用哪个类来正确推断主题类型。
我错过了什么?
2条答案
按热度按时间plicqrtu1#
所以关键是在CASL能力工厂中的Nest JS注入之上注入Cat模型,所以像这样的工厂应该可以工作:
完整示例项目:https://github.com/Wenish/nestjs-mongoose-casl
oewdyzsn2#
我的解决方案是丑陋的,但它是我发现的最好的,不涉及从Mongoose迁移。
基本上,问题的根源是mongoDB查询(如findOne)返回Documents,这是类型为
Cat & Document
的并集的普通javascript对象。在模式文件中,如果您遵循默认指令,则会出现类似export type CatDocument = Cat & Document
的内容,其中Cat是您的模式,CatDocument只是为了Typescript而生成的类型。但是,Casl需要一个类来验证,因此尝试使用该文档将导致类似
'CatDocument' only refers to a type, but is being used as a value here.
的结果我的解决方案并不漂亮,但它很简单而且有效,当你得到一个CatDocument时,把它传递给一个文档,这个文档把它变成一个类,因为你的模式没有构造函数,你必须这样做:
并在将对象传递给“can”函数之前运行它:
在另一个建议的解决方案中,这个.catModel可以编译,但实际上并不像注解中提到的那样进行验证。我也尝试过使用js-mixer来创建一个在Cat和Document之间具有多重继承的类的解决方案。然而,使用contractor几乎是不可能的,无论如何仍然需要一些转换。
我希望有一天我们能找到更好的解决办法!