From 8980cc576ce5bc9df75a2972dcccbbc91ebe0396 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 15 Mar 2024 19:15:22 +0100 Subject: [PATCH] Prevent file upload in demo workspaces (#4503) * Build demo env guard * Put guard for auth * Add todo --------- Co-authored-by: Thomas Trompette --- .../src/engine/guards/demo.env.guard.ts | 36 +++++++++++++++++++ .../google-apis-auth.controller.ts | 9 ++--- .../google-gmail-auth.controller.ts | 26 ++++++-------- .../src/engine/modules/file/file.module.ts | 9 ++++- .../file/resolvers/file-upload.resolver.ts | 3 +- .../src/engine/modules/user/user.resolver.ts | 18 +++------- .../modules/workspace/workspace.resolver.ts | 13 ++----- 7 files changed, 65 insertions(+), 49 deletions(-) create mode 100644 packages/twenty-server/src/engine/guards/demo.env.guard.ts diff --git a/packages/twenty-server/src/engine/guards/demo.env.guard.ts b/packages/twenty-server/src/engine/guards/demo.env.guard.ts new file mode 100644 index 000000000..49fe9ae47 --- /dev/null +++ b/packages/twenty-server/src/engine/guards/demo.env.guard.ts @@ -0,0 +1,36 @@ +import { + Injectable, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; +import { getRequest } from 'src/utils/extract-request'; + +@Injectable() +export class DemoEnvGuard extends AuthGuard(['jwt']) { + constructor(private readonly environmentService: EnvironmentService) { + super(); + } + + getRequest(context: ExecutionContext) { + return getRequest(context); + } + + // TODO: input should be typed + handleRequest(err: any, user: any) { + const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); + const currentUserWorkspaceId = user?.workspace?.id; + + if (!currentUserWorkspaceId) { + throw new UnauthorizedException('Unauthorized for not logged in user'); + } + + if (demoWorkspaceIds.includes(currentUserWorkspaceId)) { + throw new UnauthorizedException('Unauthorized for demo workspace'); + } + + return user; + } +} diff --git a/packages/twenty-server/src/engine/modules/auth/controllers/google-apis-auth.controller.ts b/packages/twenty-server/src/engine/modules/auth/controllers/google-apis-auth.controller.ts index 7cdb6279e..1001cb073 100644 --- a/packages/twenty-server/src/engine/modules/auth/controllers/google-apis-auth.controller.ts +++ b/packages/twenty-server/src/engine/modules/auth/controllers/google-apis-auth.controller.ts @@ -8,6 +8,7 @@ import { GoogleAPIsRequest } from 'src/engine/modules/auth/strategies/google-api import { GoogleAPIsService } from 'src/engine/modules/auth/services/google-apis.service'; import { TokenService } from 'src/engine/modules/auth/services/token.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; +import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard'; @Controller('auth/google-apis') export class GoogleAPIsAuthController { @@ -25,7 +26,7 @@ export class GoogleAPIsAuthController { } @Get('get-access-token') - @UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard) + @UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard, DemoEnvGuard) async googleAuthGetAccessToken( @Req() req: GoogleAPIsRequest, @Res() res: Response, @@ -37,12 +38,6 @@ export class GoogleAPIsAuthController { const { workspaceMemberId, workspaceId } = await this.tokenService.verifyTransientToken(transientToken); - const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); - - if (demoWorkspaceIds.includes(workspaceId)) { - throw new Error('Cannot connect Google account to demo workspace'); - } - if (!workspaceId) { throw new Error('Workspace not found'); } diff --git a/packages/twenty-server/src/engine/modules/auth/controllers/google-gmail-auth.controller.ts b/packages/twenty-server/src/engine/modules/auth/controllers/google-gmail-auth.controller.ts index 18e15db37..96262fe24 100644 --- a/packages/twenty-server/src/engine/modules/auth/controllers/google-gmail-auth.controller.ts +++ b/packages/twenty-server/src/engine/modules/auth/controllers/google-gmail-auth.controller.ts @@ -2,6 +2,7 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common'; import { Response } from 'express'; +import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard'; import { GoogleAPIsOauthGuard } from 'src/engine/modules/auth/guards/google-apis-oauth.guard'; import { GoogleAPIsProviderEnabledGuard } from 'src/engine/modules/auth/guards/google-apis-provider-enabled.guard'; import { GoogleAPIsService } from 'src/engine/modules/auth/services/google-apis.service'; @@ -25,7 +26,7 @@ export class GoogleGmailAuthController { } @Get('get-access-token') - @UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard) + @UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard, DemoEnvGuard) async googleAuthGetAccessToken( @Req() req: GoogleAPIsRequest, @Res() res: Response, @@ -37,25 +38,18 @@ export class GoogleGmailAuthController { const { workspaceMemberId, workspaceId } = await this.tokenService.verifyTransientToken(transientToken); - const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); - - if (demoWorkspaceIds.includes(workspaceId)) { - throw new Error('Cannot connect Gmail account to demo workspace'); - } - if (!workspaceId) { throw new Error('Workspace not found'); } - if (workspaceId) - await this.googleGmailService.saveConnectedAccount({ - handle: email, - workspaceMemberId: workspaceMemberId, - workspaceId: workspaceId, - provider: 'gmail', - accessToken, - refreshToken, - }); + await this.googleGmailService.saveConnectedAccount({ + handle: email, + workspaceMemberId: workspaceMemberId, + workspaceId: workspaceId, + provider: 'gmail', + accessToken, + refreshToken, + }); return res.redirect( `${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`, diff --git a/packages/twenty-server/src/engine/modules/file/file.module.ts b/packages/twenty-server/src/engine/modules/file/file.module.ts index 7d68ad3ce..76d145d78 100644 --- a/packages/twenty-server/src/engine/modules/file/file.module.ts +++ b/packages/twenty-server/src/engine/modules/file/file.module.ts @@ -1,12 +1,19 @@ import { Module } from '@nestjs/common'; +import { EnvironmentService } from 'src/integrations/environment/environment.service'; + import { FileService } from './services/file.service'; import { FileUploadService } from './services/file-upload.service'; import { FileUploadResolver } from './resolvers/file-upload.resolver'; import { FileController } from './controllers/file.controller'; @Module({ - providers: [FileService, FileUploadService, FileUploadResolver], + providers: [ + FileService, + FileUploadService, + FileUploadResolver, + EnvironmentService, + ], exports: [FileService, FileUploadService], controllers: [FileController], }) diff --git a/packages/twenty-server/src/engine/modules/file/resolvers/file-upload.resolver.ts b/packages/twenty-server/src/engine/modules/file/resolvers/file-upload.resolver.ts index 23b459d57..fc7312371 100644 --- a/packages/twenty-server/src/engine/modules/file/resolvers/file-upload.resolver.ts +++ b/packages/twenty-server/src/engine/modules/file/resolvers/file-upload.resolver.ts @@ -8,8 +8,9 @@ import { FileFolder } from 'src/engine/modules/file/interfaces/file-folder.inter import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { streamToBuffer } from 'src/utils/stream-to-buffer'; +import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard'; -@UseGuards(JwtAuthGuard) +@UseGuards(JwtAuthGuard, DemoEnvGuard) @Resolver() export class FileUploadResolver { constructor(private readonly fileUploadService: FileUploadService) {} diff --git a/packages/twenty-server/src/engine/modules/user/user.resolver.ts b/packages/twenty-server/src/engine/modules/user/user.resolver.ts index a909829f2..b9b051edd 100644 --- a/packages/twenty-server/src/engine/modules/user/user.resolver.ts +++ b/packages/twenty-server/src/engine/modules/user/user.resolver.ts @@ -6,7 +6,7 @@ import { ResolveField, Mutation, } from '@nestjs/graphql'; -import { ForbiddenException, UseGuards } from '@nestjs/common'; +import { UseGuards } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import crypto from 'crypto'; @@ -22,6 +22,7 @@ import { EnvironmentService } from 'src/engine/integrations/environment/environm import { streamToBuffer } from 'src/utils/stream-to-buffer'; import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service'; import { assert } from 'src/utils/assert'; +import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { User } from 'src/engine/modules/user/user.entity'; import { WorkspaceMember } from 'src/engine/modules/user/dtos/workspace-member.dto'; @@ -108,20 +109,9 @@ export class UserResolver { return paths[0]; } + @UseGuards(DemoEnvGuard) @Mutation(() => User) - async deleteUser(@AuthUser() { id: userId, defaultWorkspace }: User) { - // Get the list of demo workspace IDs - const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); - - const currentUserWorkspaceId = defaultWorkspace.id; - - // Check if the user's default workspace ID is in the list of demo workspace IDs - if (demoWorkspaceIds.includes(currentUserWorkspaceId)) { - throw new ForbiddenException( - 'Deletion of users with a default demo workspace is not allowed.', - ); - } - + async deleteUser(@AuthUser() { id: userId }: User) { // Proceed with user deletion return this.userService.deleteUser(userId); } diff --git a/packages/twenty-server/src/engine/modules/workspace/workspace.resolver.ts b/packages/twenty-server/src/engine/modules/workspace/workspace.resolver.ts index b83cad9f2..061f4a8bd 100644 --- a/packages/twenty-server/src/engine/modules/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/engine/modules/workspace/workspace.resolver.ts @@ -6,7 +6,7 @@ import { ResolveField, Parent, } from '@nestjs/graphql'; -import { ForbiddenException, UseGuards } from '@nestjs/common'; +import { UseGuards } from '@nestjs/common'; import { FileUpload, GraphQLUpload } from 'graphql-upload'; @@ -18,12 +18,12 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { assert } from 'src/utils/assert'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { UpdateWorkspaceInput } from 'src/engine/modules/workspace/dtos/update-workspace-input'; -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { User } from 'src/engine/modules/user/user.entity'; import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { ActivateWorkspaceInput } from 'src/engine/modules/workspace/dtos/activate-workspace-input'; import { BillingSubscription } from 'src/engine/modules/billing/entities/billing-subscription.entity'; import { BillingService } from 'src/engine/modules/billing/billing.service'; +import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard'; import { Workspace } from './workspace.entity'; @@ -35,7 +35,6 @@ export class WorkspaceResolver { constructor( private readonly workspaceService: WorkspaceService, private readonly fileUploadService: FileUploadService, - private readonly environmentService: EnvironmentService, private readonly billingService: BillingService, ) {} @@ -89,15 +88,9 @@ export class WorkspaceResolver { return paths[0]; } + @UseGuards(DemoEnvGuard) @Mutation(() => Workspace) async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) { - const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); - - // Check if the id is in the list of demo workspaceIds - if (demoWorkspaceIds.includes(id)) { - throw new ForbiddenException('Demo workspaces cannot be deleted.'); - } - return this.workspaceService.deleteWorkspace(id); }