From 6988c135d38ad6a9506b97bbf1daa7cca6309f66 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sat, 18 May 2024 21:04:08 +0300 Subject: [PATCH 1/8] feat(authorization): add casl module and casl-ability factory class --- package-lock.json | 43 +++++++++++++++++ package.json | 1 + src/app.module.ts | 2 + src/casl/action.enum.ts | 7 +++ .../casl-ability.factory.ts | 47 +++++++++++++++++++ src/casl/casl.module.ts | 8 ++++ 6 files changed, 108 insertions(+) create mode 100644 src/casl/action.enum.ts create mode 100644 src/casl/casl-ability.factory/casl-ability.factory.ts create mode 100644 src/casl/casl.module.ts diff --git a/package-lock.json b/package-lock.json index 2d5a32c1..18c2bcfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@aws-sdk/client-s3": "3.504.0", "@aws-sdk/s3-request-presigner": "^3.498.0", + "@casl/ability": "^6.7.1", "@nestjs/common": "10.3.2", "@nestjs/config": "3.2.0", "@nestjs/core": "10.3.2", @@ -2174,6 +2175,17 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@casl/ability": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.7.1.tgz", + "integrity": "sha512-e+Vgrehd1/lzOSwSqKHtmJ6kmIuZbGBlM2LBS5IuYGGKmVHuhUuyh3XgTn1VIw9+TO4gqU+uptvxfIRBUEdJuw==", + "dependencies": { + "@ucast/mongo2js": "^1.3.0" + }, + "funding": { + "url": "https://github.com/stalniy/casl/blob/master/BACKERS.md" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -5788,6 +5800,37 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ucast/core": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz", + "integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g==" + }, + "node_modules/@ucast/js": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.4.tgz", + "integrity": "sha512-TgG1aIaCMdcaEyckOZKQozn1hazE0w90SVdlpIJ/er8xVumE11gYAtSbw/LBeUnA4fFnFWTcw3t6reqseeH/4Q==", + "dependencies": { + "@ucast/core": "^1.0.0" + } + }, + "node_modules/@ucast/mongo": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz", + "integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==", + "dependencies": { + "@ucast/core": "^1.4.1" + } + }, + "node_modules/@ucast/mongo2js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz", + "integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==", + "dependencies": { + "@ucast/core": "^1.6.1", + "@ucast/js": "^3.0.0", + "@ucast/mongo": "^2.4.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", diff --git a/package.json b/package.json index 028d3ca3..f7b021ec 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dependencies": { "@aws-sdk/client-s3": "3.504.0", "@aws-sdk/s3-request-presigner": "^3.498.0", + "@casl/ability": "^6.7.1", "@nestjs/common": "10.3.2", "@nestjs/config": "3.2.0", "@nestjs/core": "10.3.2", diff --git a/src/app.module.ts b/src/app.module.ts index 636c0f7d..2b6f6891 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -37,6 +37,7 @@ import { ThreadsModule } from './threads/threads.module'; import { EventsModule } from './events/events.module'; import { InvitesModule } from './invites/invites.module'; import { WorkspaceChannelModule } from './workspace-channel/workspace-channel.module'; +import { CaslModule } from './casl/casl.module'; @Module({ imports: [ @@ -106,6 +107,7 @@ import { WorkspaceChannelModule } from './workspace-channel/workspace-channel.mo EventsModule, InvitesModule, WorkspaceChannelModule, + CaslModule, ], providers: [], }) diff --git a/src/casl/action.enum.ts b/src/casl/action.enum.ts new file mode 100644 index 00000000..b398b218 --- /dev/null +++ b/src/casl/action.enum.ts @@ -0,0 +1,7 @@ +export enum Action { + Manage = 'manage', + Create = 'create', + Read = 'read', + Update = 'update', + Delete = 'delete', +} diff --git a/src/casl/casl-ability.factory/casl-ability.factory.ts b/src/casl/casl-ability.factory/casl-ability.factory.ts new file mode 100644 index 00000000..ed2bd2db --- /dev/null +++ b/src/casl/casl-ability.factory/casl-ability.factory.ts @@ -0,0 +1,47 @@ +import { + AbilityBuilder, + ExtractSubjectType, + InferSubjects, + MongoAbility, + MongoQuery, + createMongoAbility, +} from '@casl/ability'; +import { Channel } from 'src/channels/domain/channel'; +import { Message } from 'src/messages/domain/message'; +import { Workspace } from 'src/workspaces/domain/workspace'; +import { Action } from '../action.enum'; +import { Injectable } from '@nestjs/common'; +import { User } from 'src/users/domain/user'; +import { RoleEnum } from 'src/roles/roles.enum'; + +type Subjects = + | InferSubjects + | 'all'; + +export type AppAbility = MongoAbility; +type PossibleAbilities = [Action, Subjects]; +type Conditions = MongoQuery; + +@Injectable() +export class CaslAbilityFactory { + createForUser(user: User) { + const { can, cannot, build } = new AbilityBuilder( + createMongoAbility, + ); + + if (user.role?.id === RoleEnum.admin) { + can(Action.Manage, 'all'); + } else { + can(Action.Read, Channel); + can(Action.Create, Channel); + can(Action.Update, Channel, { owner: user }); + can(Action.Delete, Channel, { owner: user }); + } + + return build({ + // Read https://casl.js.org/v5/en/guide/subject-type-detection#use-classes-as-subject-types for details + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); + } +} diff --git a/src/casl/casl.module.ts b/src/casl/casl.module.ts new file mode 100644 index 00000000..14296fa6 --- /dev/null +++ b/src/casl/casl.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { CaslAbilityFactory } from './casl-ability.factory/casl-ability.factory'; + +@Module({ + providers: [CaslAbilityFactory], + exports: [CaslAbilityFactory], +}) +export class CaslModule {} From 105a6380c281e96b4d805b3ef388419747b1550c Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sat, 18 May 2024 21:05:26 +0300 Subject: [PATCH 2/8] feat(authorization): add CheckPolicies Decorator --- src/authz/check-policies/check-policies.decorator.ts | 6 ++++++ src/authz/check-policies/policy-handler.type.ts | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/authz/check-policies/check-policies.decorator.ts create mode 100644 src/authz/check-policies/policy-handler.type.ts diff --git a/src/authz/check-policies/check-policies.decorator.ts b/src/authz/check-policies/check-policies.decorator.ts new file mode 100644 index 00000000..747ead7a --- /dev/null +++ b/src/authz/check-policies/check-policies.decorator.ts @@ -0,0 +1,6 @@ +import { SetMetadata } from '@nestjs/common'; +import { PolicyHandler } from './policy-handler.type'; + +export const CHECK_POLICIES_KEY = 'check_policy'; +export const CheckPolicies = (...handlers: PolicyHandler[]) => + SetMetadata(CHECK_POLICIES_KEY, handlers); diff --git a/src/authz/check-policies/policy-handler.type.ts b/src/authz/check-policies/policy-handler.type.ts new file mode 100644 index 00000000..b8dfa3d2 --- /dev/null +++ b/src/authz/check-policies/policy-handler.type.ts @@ -0,0 +1,9 @@ +import { AppAbility } from 'src/casl/casl-ability.factory/casl-ability.factory'; + +interface IPolicyHandler { + handle(ability: AppAbility): boolean; +} + +type PolicyHandlerCallback = (ability: AppAbility) => boolean; + +export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback; From bb63796dcaa7f221d1db0621e5fc00c74f715a21 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sat, 18 May 2024 21:05:52 +0300 Subject: [PATCH 3/8] feat(authorization): add Policies Guard --- src/authz/policies.guard/policies.guard.ts | 39 +++++++++++++++++++ .../casl-ability.factory.ts | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/authz/policies.guard/policies.guard.ts diff --git a/src/authz/policies.guard/policies.guard.ts b/src/authz/policies.guard/policies.guard.ts new file mode 100644 index 00000000..d030d010 --- /dev/null +++ b/src/authz/policies.guard/policies.guard.ts @@ -0,0 +1,39 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + AppAbility, + CaslAbilityFactory, +} from 'src/casl/casl-ability.factory/casl-ability.factory'; +import { PolicyHandler } from '../check-policies/policy-handler.type'; +import { CHECK_POLICIES_KEY } from '../check-policies/check-policies.decorator'; + +@Injectable() +export class PoliciesGuard implements CanActivate { + constructor( + private reflector: Reflector, + private caslAbilityFactory: CaslAbilityFactory, + ) {} + + // eslint-disable-next-line @typescript-eslint/require-await + async canActivate(context: ExecutionContext): Promise { + const policyHandlers = + this.reflector.get( + 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); + } +} diff --git a/src/casl/casl-ability.factory/casl-ability.factory.ts b/src/casl/casl-ability.factory/casl-ability.factory.ts index ed2bd2db..da2bc048 100644 --- a/src/casl/casl-ability.factory/casl-ability.factory.ts +++ b/src/casl/casl-ability.factory/casl-ability.factory.ts @@ -25,7 +25,7 @@ type Conditions = MongoQuery; @Injectable() export class CaslAbilityFactory { createForUser(user: User) { - const { can, cannot, build } = new AbilityBuilder( + const { can, build } = new AbilityBuilder( createMongoAbility, ); From 1fb7f2d700d58aa3b04b6e27b4b5901166095a68 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sat, 18 May 2024 21:06:28 +0300 Subject: [PATCH 4/8] feat(authorization): add authorization to create, delete channel --- src/channels/channels.controller.ts | 15 ++++++++------- src/channels/channels.module.ts | 3 ++- src/channels/channels.service.ts | 6 +++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index 3c3dcc93..5eaa965d 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -21,17 +21,22 @@ import { UpdateChannelDto } from './dto/update-channel.dto'; import { QueryUserDto } from '../users/dto/query-user.dto'; import { infinityPagination } from '../utils/infinity-pagination'; import { QueryMessageDto } from 'src/messages/dto/query-message.dto'; +import { CheckPolicies } from 'src/authz/check-policies/check-policies.decorator'; +import { AppAbility } from 'src/casl/casl-ability.factory/casl-ability.factory'; +import { Action } from 'src/casl/action.enum'; +import { PoliciesGuard } from 'src/authz/policies.guard/policies.guard'; @ApiTags('Channels') @Controller({ path: 'channels', version: '1', }) +@UseGuards(AuthGuard('jwt'), PoliciesGuard) export class ChannelsController { constructor(private readonly channelsService: ChannelsService) {} @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) + @CheckPolicies((ability: AppAbility) => ability.can(Action.Create, Channel)) @Post() @HttpCode(HttpStatus.CREATED) createChannel( @@ -42,7 +47,6 @@ export class ChannelsController { } @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) @Get(':id') @ApiParam({ name: 'id', @@ -53,7 +57,6 @@ export class ChannelsController { } @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) @Patch(':id') @ApiParam({ name: 'id', @@ -68,7 +71,6 @@ export class ChannelsController { } @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) @Get(':id/messages') @ApiParam({ name: 'id', @@ -94,7 +96,6 @@ export class ChannelsController { } @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) @Get(':id/users') @ApiParam({ name: 'id', @@ -127,13 +128,13 @@ export class ChannelsController { } @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) + @CheckPolicies((ability: AppAbility) => ability.can(Action.Delete, Channel)) @Delete(':id') @ApiParam({ name: 'id', }) @HttpCode(HttpStatus.NO_CONTENT) remove(@Param('id') id: Channel['id'], @Request() request): Promise { - return this.channelsService.softDelete(request.user, id); + return this.channelsService.softDelete(request, id); } } diff --git a/src/channels/channels.module.ts b/src/channels/channels.module.ts index ef51570a..c5242d71 100644 --- a/src/channels/channels.module.ts +++ b/src/channels/channels.module.ts @@ -4,11 +4,12 @@ import { ChannelsController } from './channels.controller'; import { ChannelPersistenceModule } from './infrastructure/persistence/persistence.module'; import { MessagesModule } from 'src/messages/messages.module'; import { UsersModule } from '../users/users.module'; +import { CaslAbilityFactory } from 'src/casl/casl-ability.factory/casl-ability.factory'; @Module({ imports: [ChannelPersistenceModule, MessagesModule, UsersModule], controllers: [ChannelsController], - providers: [ChannelsService], + providers: [ChannelsService, CaslAbilityFactory], exports: [ChannelsService], }) export class ChannelsModule {} diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index bf0ffdf8..c17a051a 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -21,6 +21,8 @@ import { SortMessageDto, } from 'src/messages/dto/query-message.dto'; import { FilterChannelDto, SortChannelDto } from './dto/query-channel.dto'; +import { CaslAbilityFactory } from 'src/casl/casl-ability.factory/casl-ability.factory'; +import { Action } from 'src/casl/action.enum'; @Injectable() export class ChannelsService { @@ -28,6 +30,7 @@ export class ChannelsService { private readonly channelRepostory: ChannelRepository, private readonly usersService: UsersService, private readonly messagesService: MessagesService, + private readonly caslAbilityFactory: CaslAbilityFactory, ) {} async createChannel(user: User, createChannelDto: CreateChannelDto) { @@ -140,7 +143,8 @@ export class ChannelsService { throw new NotFoundException(); } - if (channel.owner.id !== user.id) { + const ability = this.caslAbilityFactory.createForUser(user); + if (ability.cannot(Action.Delete, channel)) { throw new ForbiddenException(); } From 60f0447bc0cd64ec1c02b510e21945fa4f01561b Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sun, 19 May 2024 13:20:34 +0300 Subject: [PATCH 5/8] refactor(casl): update action enum to use uppercase --- src/casl/action.enum.ts | 10 +++++----- src/casl/casl-ability.factory/casl-ability.factory.ts | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/casl/action.enum.ts b/src/casl/action.enum.ts index b398b218..c448a742 100644 --- a/src/casl/action.enum.ts +++ b/src/casl/action.enum.ts @@ -1,7 +1,7 @@ export enum Action { - Manage = 'manage', - Create = 'create', - Read = 'read', - Update = 'update', - Delete = 'delete', + MANAGE = 'manage', + CREATE = 'create', + READ = 'read', + UPDATE = 'update', + DELETE = 'delete', } diff --git a/src/casl/casl-ability.factory/casl-ability.factory.ts b/src/casl/casl-ability.factory/casl-ability.factory.ts index da2bc048..9a6a69af 100644 --- a/src/casl/casl-ability.factory/casl-ability.factory.ts +++ b/src/casl/casl-ability.factory/casl-ability.factory.ts @@ -30,12 +30,12 @@ export class CaslAbilityFactory { ); if (user.role?.id === RoleEnum.admin) { - can(Action.Manage, 'all'); + can(Action.MANAGE, 'all'); } else { - can(Action.Read, Channel); - can(Action.Create, Channel); - can(Action.Update, Channel, { owner: user }); - can(Action.Delete, Channel, { owner: user }); + can(Action.READ, Channel); + can(Action.CREATE, Channel); + can(Action.UPDATE, Channel, { owner: user }); + can(Action.DELETE, Channel, { owner: user }); } return build({ From 25d4cfeaeed01e3e22bf5406f686605defc9e1ff Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sun, 19 May 2024 13:21:47 +0300 Subject: [PATCH 6/8] refactor(authorization): add CheckPolicies to update channel --- src/channels/channels.controller.ts | 5 +++-- src/channels/channels.service.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index 5eaa965d..25a6defe 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -36,7 +36,7 @@ export class ChannelsController { constructor(private readonly channelsService: ChannelsService) {} @ApiBearerAuth() - @CheckPolicies((ability: AppAbility) => ability.can(Action.Create, Channel)) + @CheckPolicies((ability: AppAbility) => ability.can(Action.CREATE, Channel)) @Post() @HttpCode(HttpStatus.CREATED) createChannel( @@ -57,6 +57,7 @@ export class ChannelsController { } @ApiBearerAuth() + @CheckPolicies((ability: AppAbility) => ability.can(Action.UPDATE, Channel)) @Patch(':id') @ApiParam({ name: 'id', @@ -128,7 +129,7 @@ export class ChannelsController { } @ApiBearerAuth() - @CheckPolicies((ability: AppAbility) => ability.can(Action.Delete, Channel)) + @CheckPolicies((ability: AppAbility) => ability.can(Action.DELETE, Channel)) @Delete(':id') @ApiParam({ name: 'id', diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index c17a051a..cbb829e4 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -74,7 +74,8 @@ export class ChannelsService { throw new NotFoundException(); } - if (channel.owner.id !== user.id) { + const ability = this.caslAbilityFactory.createForUser(user); + if (ability.cannot(Action.UPDATE, channel)) { throw new ForbiddenException(); } @@ -144,7 +145,7 @@ export class ChannelsService { } const ability = this.caslAbilityFactory.createForUser(user); - if (ability.cannot(Action.Delete, channel)) { + if (ability.cannot(Action.DELETE, channel)) { throw new ForbiddenException(); } From 4b86c40b2eac0fa38fd3c1065795e85e0f208014 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sun, 19 May 2024 14:33:54 +0300 Subject: [PATCH 7/8] refactor(channel): revert condition checks on update and delete --- src/casl/casl-ability.factory/casl-ability.factory.ts | 6 +++--- src/channels/channels.service.ts | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/casl/casl-ability.factory/casl-ability.factory.ts b/src/casl/casl-ability.factory/casl-ability.factory.ts index 9a6a69af..1f0a6866 100644 --- a/src/casl/casl-ability.factory/casl-ability.factory.ts +++ b/src/casl/casl-ability.factory/casl-ability.factory.ts @@ -18,9 +18,9 @@ type Subjects = | InferSubjects | 'all'; -export type AppAbility = MongoAbility; type PossibleAbilities = [Action, Subjects]; type Conditions = MongoQuery; +export type AppAbility = MongoAbility; @Injectable() export class CaslAbilityFactory { @@ -34,8 +34,8 @@ export class CaslAbilityFactory { } else { can(Action.READ, Channel); can(Action.CREATE, Channel); - can(Action.UPDATE, Channel, { owner: user }); - can(Action.DELETE, Channel, { owner: user }); + can(Action.UPDATE, Channel); + can(Action.DELETE, Channel); } return build({ diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index cbb829e4..bf0ffdf8 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -21,8 +21,6 @@ import { SortMessageDto, } from 'src/messages/dto/query-message.dto'; import { FilterChannelDto, SortChannelDto } from './dto/query-channel.dto'; -import { CaslAbilityFactory } from 'src/casl/casl-ability.factory/casl-ability.factory'; -import { Action } from 'src/casl/action.enum'; @Injectable() export class ChannelsService { @@ -30,7 +28,6 @@ export class ChannelsService { private readonly channelRepostory: ChannelRepository, private readonly usersService: UsersService, private readonly messagesService: MessagesService, - private readonly caslAbilityFactory: CaslAbilityFactory, ) {} async createChannel(user: User, createChannelDto: CreateChannelDto) { @@ -74,8 +71,7 @@ export class ChannelsService { throw new NotFoundException(); } - const ability = this.caslAbilityFactory.createForUser(user); - if (ability.cannot(Action.UPDATE, channel)) { + if (channel.owner.id !== user.id) { throw new ForbiddenException(); } @@ -144,8 +140,7 @@ export class ChannelsService { throw new NotFoundException(); } - const ability = this.caslAbilityFactory.createForUser(user); - if (ability.cannot(Action.DELETE, channel)) { + if (channel.owner.id !== user.id) { throw new ForbiddenException(); } From 697a5f38f837d8f7e980d38efccd35ce6ac4f769 Mon Sep 17 00:00:00 2001 From: moaaz Date: Mon, 20 May 2024 21:15:23 +0300 Subject: [PATCH 8/8] fix(casl): add flatten type for nested properties --- src/casl/casl-ability.factory/casl-ability.factory.ts | 7 +++++-- src/casl/casl-ability.factory/typs.ts | 5 +++++ src/channels/channels.controller.ts | 2 +- src/channels/channels.service.ts | 4 ---- 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 src/casl/casl-ability.factory/typs.ts diff --git a/src/casl/casl-ability.factory/casl-ability.factory.ts b/src/casl/casl-ability.factory/casl-ability.factory.ts index 1f0a6866..730037b8 100644 --- a/src/casl/casl-ability.factory/casl-ability.factory.ts +++ b/src/casl/casl-ability.factory/casl-ability.factory.ts @@ -7,6 +7,7 @@ import { createMongoAbility, } from '@casl/ability'; import { Channel } from 'src/channels/domain/channel'; +import { FlatChannel } from './typs'; import { Message } from 'src/messages/domain/message'; import { Workspace } from 'src/workspaces/domain/workspace'; import { Action } from '../action.enum'; @@ -32,10 +33,12 @@ export class CaslAbilityFactory { if (user.role?.id === RoleEnum.admin) { can(Action.MANAGE, 'all'); } else { + console.log(user); + console.log(Channel); can(Action.READ, Channel); can(Action.CREATE, Channel); - can(Action.UPDATE, Channel); - can(Action.DELETE, Channel); + can(Action.UPDATE, Channel, { 'owner.id': user.id }); + can(Action.DELETE, Channel, { 'owner.id': user.id }); } return build({ diff --git a/src/casl/casl-ability.factory/typs.ts b/src/casl/casl-ability.factory/typs.ts new file mode 100644 index 00000000..6a192eab --- /dev/null +++ b/src/casl/casl-ability.factory/typs.ts @@ -0,0 +1,5 @@ +import { Channel } from 'src/channels/domain/channel'; + +export type FlatChannel = Channel & { + 'owner.id': Channel['owner']['id']; +}; diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index 25a6defe..9ee1dff8 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -136,6 +136,6 @@ export class ChannelsController { }) @HttpCode(HttpStatus.NO_CONTENT) remove(@Param('id') id: Channel['id'], @Request() request): Promise { - return this.channelsService.softDelete(request, id); + return this.channelsService.softDelete(request.user, id); } } diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index bf0ffdf8..6f43356b 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -140,10 +140,6 @@ export class ChannelsService { throw new NotFoundException(); } - if (channel.owner.id !== user.id) { - throw new ForbiddenException(); - } - await this.channelRepostory.softDelete(id); }