feat: refactoring casl permission checks for recursive nested operations (#778)

* feat: nested casl abilities

* fix: remove unused packages

* Fixes

* Fix createMany broken

* Fix lint

* Fix lint

* Fix lint

* Fix lint

* Fixes

* Fix CommentThread

* Fix bugs

* Fix lint

* Fix bugs

* Fixed auto routing

* Fixed app path

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Jérémy M
2023-07-26 01:37:22 +02:00
committed by GitHub
parent 92b9e987a5
commit 51cfc0d82c
69 changed files with 1192 additions and 883 deletions

View File

@ -1,106 +0,0 @@
import {
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CreateOneCommentThreadGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
// TODO: type request
const request = gqlContext.getContext().req;
const args = gqlContext.getArgs();
const targets = args.data?.commentThreadTargets?.createMany?.data;
const comments = args.data?.comments?.createMany?.data;
const workspace = request.user.workspace;
if (!targets || targets.length === 0) {
throw new HttpException(
{ reason: 'Missing commentThreadTargets' },
HttpStatus.BAD_REQUEST,
);
}
await targets.map(async (target) => {
if (!target.commentableId || !target.commentableType) {
throw new HttpException(
{
reason:
'Missing commentThreadTarget.commentableId or commentThreadTarget.commentableType',
},
HttpStatus.BAD_REQUEST,
);
}
if (!['Person', 'Company'].includes(target.commentableType)) {
throw new HttpException(
{ reason: 'Invalid commentThreadTarget.commentableType' },
HttpStatus.BAD_REQUEST,
);
}
const targetEntity = await this.prismaService[
target.commentableType
].findUnique({
where: { id: target.commentableId },
});
if (!targetEntity || targetEntity.workspaceId !== workspace.id) {
throw new HttpException(
{ reason: 'CommentThreadTarget not found' },
HttpStatus.NOT_FOUND,
);
}
});
if (!comments) {
return true;
}
await comments.map(async (comment) => {
if (!comment.authorId) {
throw new HttpException(
{ reason: 'Missing comment.authorId' },
HttpStatus.BAD_REQUEST,
);
}
const author = await this.prismaService.user.findUnique({
where: { id: comment.authorId },
});
if (!author) {
throw new HttpException(
{ reason: 'Comment.authorId not found' },
HttpStatus.NOT_FOUND,
);
}
const userWorkspaceMember =
await this.prismaService.workspaceMember.findFirst({
where: { userId: author.id },
});
if (
!userWorkspaceMember ||
userWorkspaceMember.workspaceId !== workspace.id
) {
throw new HttpException(
{ reason: 'userWorkspaceMember.workspaceId not found' },
HttpStatus.NOT_FOUND,
);
}
});
return true;
}
}

View File

@ -1,72 +0,0 @@
import {
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CreateOneCommentGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
const args = gqlContext.getArgs();
const authorId = args.data?.author?.connect?.id;
const commentThreadId = args.data?.commentThread?.connect?.id;
if (!authorId || !commentThreadId) {
throw new HttpException(
{ reason: 'Missing author or commentThread' },
HttpStatus.BAD_REQUEST,
);
}
const author = await this.prismaService.user.findUnique({
where: { id: authorId },
});
const commentThread = await this.prismaService.commentThread.findUnique({
where: { id: commentThreadId },
});
if (!author || !commentThread) {
throw new HttpException(
{ reason: 'Author or commentThread not found' },
HttpStatus.NOT_FOUND,
);
}
const userWorkspaceMember =
await this.prismaService.workspaceMember.findFirst({
where: { userId: author.id },
});
if (!userWorkspaceMember) {
throw new HttpException(
{ reason: 'Author or commentThread not found' },
HttpStatus.NOT_FOUND,
);
}
const workspace = request.user.workspace;
if (
userWorkspaceMember.workspaceId !== workspace.id ||
commentThread.workspaceId !== workspace.id
) {
throw new HttpException(
{ reason: 'Author or commentThread not found' },
HttpStatus.NOT_FOUND,
);
}
return true;
}
}

View File

@ -1,13 +0,0 @@
import { CanActivate, Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CreateOneGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(): Promise<boolean> {
// TODO
return true;
}
}

View File

@ -1,13 +0,0 @@
import { CanActivate, Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class DeleteManyGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(): Promise<boolean> {
// TODO
return true;
}
}

View File

@ -1,50 +0,0 @@
import {
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class UpdateOneGuard implements CanActivate {
constructor(private prismaService: PrismaService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
const entity = gqlContext.getArgByIndex(3).returnType?.name;
const args = gqlContext.getArgs();
if (!entity || !args.where?.id) {
throw new HttpException(
{ reason: 'Invalid Request' },
HttpStatus.BAD_REQUEST,
);
}
const object = await this.prismaService[entity].findUniqueOrThrow({
where: { id: args.where.id },
});
if (!object) {
throw new HttpException(
{ reason: 'Record not found' },
HttpStatus.NOT_FOUND,
);
}
const workspace = request.user.workspace;
if (object.workspaceId !== workspace.id) {
throw new HttpException(
{ reason: 'Record not found' },
HttpStatus.NOT_FOUND,
);
}
return true;
}
}