I am trying to implement a generic policy based guard in NestJS and CASL for list / get endpoints. I am following the documentation here .
A user could be part of multiple groups. I need to check if Article
or Books
entity which has groupId
matches any of the user's groups. And only allow user to read them if the user's list of groups matches Article.id
and Books.id
However I can't get it seems to be working
// User Object - from request.user after decoded from AuthGuard('jwt')
user = {
groups: ['department-1', 'department-2']
}
// Article Entity
export class Article extends UUIDBase {
@Column({ nullable: false })
groupId: string
@Column({ nullable: true })
description: string
@Column({ nullable: true })
name: string
}
// Books Entity
export class Books extends UUIDBase {
@Column({ nullable: false })
groupId: string
@Column({ nullable: true })
description: string
@Column({ nullable: true })
name: string
@Column({ nullable: true })
description: string
}
// CASL-ability.factory.ts
type Subjects = typeof Articles | typeof User | Articles | User | 'all'
export type AppAbility = Ability<[Action, Subjects]>
@Injectable()
export class CaslAbilityFactory {
createForUser(user: any) {
const { can, build } = new AbilityBuilder<Ability<[Action, Subjects]>>(
Ability as AbilityClass<AppAbility>,
)
console.log('USER IS ADMIN', user.groups)
if (user.groups.includes(Groups.ADMIN)) {
can(Action.Manage, 'all') // read-write access to everything
} else {
can(Action.Read, 'all') // read-only access to everything
}
return build()
}
}
// @CheckPolicies decorator
interface IPolicyHandler {
handle(ability: AppAbility): boolean
}
type PolicyHandlerCallback = (ability: AppAbility, can?, user?) => boolean
export declare type PolicyHandler = IPolicyHandler | PolicyHandlerCallback
import { SetMetadata } from '@nestjs/mon'
export const CHECK_POLICIES_KEY = 'check_policy'
export const CheckPolicies: any = (...handlers: PolicyHandler[]) =>
SetMetadata(CHECK_POLICIES_KEY, handlers)
// PoliciesGuard.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 { user } = context.switchToHttp().getRequest()
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)
}
}
And I am using it like this in Articles controller
@ApiTags('Articles')
@Controller('Articles')
@UseGuards(AuthGuard('jwt'))
export class ArticlesController {
constructor(public service: ArticlesService) {}
@Get('/articles/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Articles)
})
async listArticles(@Query() query, @Param() param) {
return this.service.listArticles(query, param)
}
@Get('/articles/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Articles)
})
async getArticle(@Param() params, @Query() query) {
return this.service.getArticle(params, query)
}
And I am using it like this in Books controller
@ApiTags('Books')
@Controller('Books')
@UseGuards(AuthGuard('jwt'))
export class BooksController {
constructor(public service: BooksService) {}
@Get('/books/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Books)
})
async listBooks(@Query() query, @Param() param, @Body() body) {
return this.service.listBooks(query, param, body)
}
@Get('/books/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Books)
})
async getBooks(@Param() params, @Query() query) {
return this.service.getBooks(params, query)
}
I am trying to implement a generic policy based guard in NestJS and CASL for list / get endpoints. I am following the documentation here https://docs.nestjs./security/authorization#integrating-casl.
A user could be part of multiple groups. I need to check if Article
or Books
entity which has groupId
matches any of the user's groups. And only allow user to read them if the user's list of groups matches Article.id
and Books.id
However I can't get it seems to be working
// User Object - from request.user after decoded from AuthGuard('jwt')
user = {
groups: ['department-1', 'department-2']
}
// Article Entity
export class Article extends UUIDBase {
@Column({ nullable: false })
groupId: string
@Column({ nullable: true })
description: string
@Column({ nullable: true })
name: string
}
// Books Entity
export class Books extends UUIDBase {
@Column({ nullable: false })
groupId: string
@Column({ nullable: true })
description: string
@Column({ nullable: true })
name: string
@Column({ nullable: true })
description: string
}
// CASL-ability.factory.ts
type Subjects = typeof Articles | typeof User | Articles | User | 'all'
export type AppAbility = Ability<[Action, Subjects]>
@Injectable()
export class CaslAbilityFactory {
createForUser(user: any) {
const { can, build } = new AbilityBuilder<Ability<[Action, Subjects]>>(
Ability as AbilityClass<AppAbility>,
)
console.log('USER IS ADMIN', user.groups)
if (user.groups.includes(Groups.ADMIN)) {
can(Action.Manage, 'all') // read-write access to everything
} else {
can(Action.Read, 'all') // read-only access to everything
}
return build()
}
}
// @CheckPolicies decorator
interface IPolicyHandler {
handle(ability: AppAbility): boolean
}
type PolicyHandlerCallback = (ability: AppAbility, can?, user?) => boolean
export declare type PolicyHandler = IPolicyHandler | PolicyHandlerCallback
import { SetMetadata } from '@nestjs/mon'
export const CHECK_POLICIES_KEY = 'check_policy'
export const CheckPolicies: any = (...handlers: PolicyHandler[]) =>
SetMetadata(CHECK_POLICIES_KEY, handlers)
// PoliciesGuard.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 { user } = context.switchToHttp().getRequest()
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)
}
}
And I am using it like this in Articles controller
@ApiTags('Articles')
@Controller('Articles')
@UseGuards(AuthGuard('jwt'))
export class ArticlesController {
constructor(public service: ArticlesService) {}
@Get('/articles/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Articles)
})
async listArticles(@Query() query, @Param() param) {
return this.service.listArticles(query, param)
}
@Get('/articles/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Articles)
})
async getArticle(@Param() params, @Query() query) {
return this.service.getArticle(params, query)
}
And I am using it like this in Books controller
@ApiTags('Books')
@Controller('Books')
@UseGuards(AuthGuard('jwt'))
export class BooksController {
constructor(public service: BooksService) {}
@Get('/books/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Books)
})
async listBooks(@Query() query, @Param() param, @Body() body) {
return this.service.listBooks(query, param, body)
}
@Get('/books/:id')
@UseGuards(PoliciesGuard)
@CheckPolicies((ability: AppAbility) => {
ability.can(Action.Read, Books)
})
async getBooks(@Param() params, @Query() query) {
return this.service.getBooks(params, query)
}
Share
Improve this question
asked Dec 3, 2020 at 11:45
MonteCristoMonteCristo
1,5502 gold badges21 silver badges44 bronze badges
1
- 1 how did you fix this issue? – rukshiin Commented Sep 21, 2021 at 8:57
1 Answer
Reset to default 5Try to change @CheckPolicies((ability: AppAbility) => {ability.can(Action.Read, Books)})
by adding a return before ability.can...
or removing the brackets.
With what you have, that policy handler always returns undefined
, so it always gives you an Unauthorized
.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744291897a4567072.html
评论列表(0条)