'Nest JS authorization with CASL doesn't work as expected
EXPECTING:
- Be able to get user info with id equal to my id only (which is saved in JWT token).
CURRENT RESULT:
- I am able to get info about all users with some id.
Used Nest Js docs while creating this solution. Do appreciate your help.
- /casl-ability.factory.ts
type Subjects = InferSubjects<typeof User | typeof Role | 'User'> | 'all';
export type AppAbility = Ability<[Action, Subjects]>;
export class CaslAbilityFactory {
createForUser(userDataFromJWT: JwtAccessTokenInput) {
const { can, cannot, build } = new AbilityBuilder<
Ability<[Action, Subjects]>
>(Ability as AbilityClass<AppAbility>);
// TESTING THIS CASE
can(Action.Read, User, {
id: userDataFromJWT.sub,
});
return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}
private hasRole(roles: unknown[], role: UserRoles): boolean {
return roles.includes(role);
}
}
- /getUser.policyHandler.ts
export class GetUserPolicyHandler implements IPolicyHandler {
handle(ability: AppAbility) {
return ability.can(Action.Read, User);
}
}
- /types.ts
export enum Action {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
export interface IPolicyHandler {
handle(ability: AppAbility): boolean;
}
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
- /policies.guard.ts
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const ctx = GqlExecutionContext.create(context);
const { user }: { user: JwtAccessTokenInput } = ctx.getContext().req;
const ability = this.caslAbilityFactory.createForUser(user);
return policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability),
);
}
private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
if (typeof handler === 'function') {
return handler(ability);
}
return handler.handle(ability);
}
}
- user.resolver.ts
@Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
@Query(() => User, { name: 'user' })
@UseGuards(PoliciesGuard)
@CheckPolicies(new GetUserPolicyHandler())
@UseInterceptors(UserNotExistsByIDInterceptor)
async findOne(@Args('id', { type: () => Int }) id: number): Promise<User> {
return await this.userService.findOne(id);
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|