'Nestjs authorization with casl. User is not allowed to update own article
I leaning nestjs and have such back end web api code parts.
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 with handlers included
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;
}
}
The user object is present in the req object and has no access to update own article. I'm sending this http request.
PATCH http://localhost:3000/article/1 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkxlYW5uZSIsInN1YiI6MSwiaWF0IjoxNjQ4MDQyNzQ3LCJleHAiOjE2NDgxNDI3NDZ9.Zy06Wciaq9p3RYfakft_lV5aPwmzaiGJedtl2vSn8QE
content-type: application/json
{
"authorId": 1,
"isPublished": true
}
How can I fix this and where is my fault ?
Solution 1:[1]
You're using CASL library. We don't have explicit solution in nestjs advanced-implementing-a-policiesguard docs. CASL compares object by retrieving article.constructor.name as its subject type. By default everything is forbidden in CASL. You have to initialize user and article classes. Make such changes that shown below in policies.guard.ts file.
const { user: user0, body } = context.switchToHttp().getRequest();
const user = new User(user0);
const article = new Article(body);
Now user has access to update own article.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | serhii kuzmych |
