带有 casl 的 Nestjs 授权。不允许用户更新自己的文章

Nestjs authorization with casl. User is not allowed to update own article

我学习 nestjs 并且有这样的后端 Web api 代码部分。

article.controller.ts

  @UseGuards(JwtAuthGuard, PoliciesGuard)
  @CheckPolicies(new UpdateArticlePolicyHandler())
  @Patch(':id')
  update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
    return this.articleService.update(+id, updateArticleDto);
  }

jwt-auth.guard.ts

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) return true;

    const superCanActivateRes = super.canActivate(context);
    return superCanActivateRes;
  }

}

jwt.strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private usersService: UsersService) {
    const configObj = {
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: PRIVATE_JWT_KEY,
    };
    super(configObj);
  }

  async validate(payload: any) {
    const userObj = { userId: payload.sub, username: payload.username };

    const user = await this.usersService.findOne(userObj.username);

    if (!user) {
      throw new NotFoundException(
        `user with username ${userObj.username} is not found`,
      );
    }
    return user;
  }
}

casl-ability.factory.ts

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

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

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

    if (user.isAdmin) {
      can(ActionAbility.Manage, 'all'); 
    } else {
      can(ActionAbility.Read, 'all'); 
    }

    can(ActionAbility.Update, Article, { authorId: user.userId });
    cannot(ActionAbility.Delete, Article, { isPublished: true });

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

policies.guard.ts 包含处理程序


export class ReadArticlePolicyHandler implements IPolicyHandler {
  handle(ability: AppAbility, article: Article) {
    const abilityRes = ability.can(ActionAbility.Read, article);
    return abilityRes;
  }
}

export class UpdateArticlePolicyHandler implements IPolicyHandler {
  handle(ability: AppAbility, article: Article) {
    const abilityRes = ability.can(ActionAbility.Update, article);
    return abilityRes;
  }
}

@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private caslAbilityFactory: CaslAbilityFactory,
  ) {}

  canActivate(context: ExecutionContext): boolean {

    const policyHandlers =
      this.reflector.get<PolicyHandler[]>(
        CHECK_POLICIES_KEY,
        context.getHandler(),
      ) || [];

    const { user, body }: { user: User; body: Article } = context
     .switchToHttp()
     .getRequest();

    const ability = this.caslAbilityFactory.createForUser(user);

    const policiesHandlersRes = policyHandlers.every((handler) =>
      this.execPolicyHandler(handler, ability, body),
    );

    return policiesHandlersRes;
  }

  private execPolicyHandler(
    handler: PolicyHandler,
    ability: AppAbility,
    article: Article,
  ) {
    if (typeof handler === 'function') {
      const abilityFuncRes = handler(ability);
      return abilityFuncRes;
    }

    const abilityHandlerRes = handler.handle(ability, article);
    return abilityHandlerRes;
  }
}

用户对象存在于请求对象中,无权更新自己的文章。 我正在发送这个 http 请求。

PATCH http://localhost:3000/article/1 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkxlYW5uZSIsInN1YiI6MSwiaWF0IjoxNjQ4MDQyNzQ3LCJleHAiOjE2NDgxNDI3NDZ9.Zy06Wciaq9p3RYfakft_lV5aPwmzaiGJedtl2vSn8QE
content-type: application/json

{
    "authorId": 1,
    "isPublished": true
}

我该如何解决这个问题,我的错在哪里?

您正在使用 CASL 库。 我们在 nestjs advanced-implementing-a-policiesguard 文档中没有明确的解决方案。 CASL 通过检索 article.constructor.name 作为其主题类型来比较对象。默认情况下,CASL 中禁止一切。 您必须初始化用户和文章 类。 在 policies.guard.ts 文件中进行如下所示的更改。

const { user: user0, body } = context.switchToHttp().getRequest();
const user = new User(user0);
const article = new Article(body);

现在用户可以更新自己的文章了。