Prevent file upload in demo workspaces (#4503)

* Build demo env guard

* Put guard for auth

* Add todo

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
Thomas Trompette
2024-03-15 19:15:22 +01:00
committed by GitHub
parent 1cc8edd016
commit 8980cc576c
7 changed files with 65 additions and 49 deletions

View File

@ -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;
}
}

View File

@ -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 { GoogleAPIsService } from 'src/engine/modules/auth/services/google-apis.service';
import { TokenService } from 'src/engine/modules/auth/services/token.service'; import { TokenService } from 'src/engine/modules/auth/services/token.service';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
@Controller('auth/google-apis') @Controller('auth/google-apis')
export class GoogleAPIsAuthController { export class GoogleAPIsAuthController {
@ -25,7 +26,7 @@ export class GoogleAPIsAuthController {
} }
@Get('get-access-token') @Get('get-access-token')
@UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard) @UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard, DemoEnvGuard)
async googleAuthGetAccessToken( async googleAuthGetAccessToken(
@Req() req: GoogleAPIsRequest, @Req() req: GoogleAPIsRequest,
@Res() res: Response, @Res() res: Response,
@ -37,12 +38,6 @@ export class GoogleAPIsAuthController {
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); 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) { if (!workspaceId) {
throw new Error('Workspace not found'); throw new Error('Workspace not found');
} }

View File

@ -2,6 +2,7 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
import { Response } from 'express'; 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 { 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 { GoogleAPIsProviderEnabledGuard } from 'src/engine/modules/auth/guards/google-apis-provider-enabled.guard';
import { GoogleAPIsService } from 'src/engine/modules/auth/services/google-apis.service'; import { GoogleAPIsService } from 'src/engine/modules/auth/services/google-apis.service';
@ -25,7 +26,7 @@ export class GoogleGmailAuthController {
} }
@Get('get-access-token') @Get('get-access-token')
@UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard) @UseGuards(GoogleAPIsProviderEnabledGuard, GoogleAPIsOauthGuard, DemoEnvGuard)
async googleAuthGetAccessToken( async googleAuthGetAccessToken(
@Req() req: GoogleAPIsRequest, @Req() req: GoogleAPIsRequest,
@Res() res: Response, @Res() res: Response,
@ -37,25 +38,18 @@ export class GoogleGmailAuthController {
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); 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) { if (!workspaceId) {
throw new Error('Workspace not found'); throw new Error('Workspace not found');
} }
if (workspaceId) await this.googleGmailService.saveConnectedAccount({
await this.googleGmailService.saveConnectedAccount({ handle: email,
handle: email, workspaceMemberId: workspaceMemberId,
workspaceMemberId: workspaceMemberId, workspaceId: workspaceId,
workspaceId: workspaceId, provider: 'gmail',
provider: 'gmail', accessToken,
accessToken, refreshToken,
refreshToken, });
});
return res.redirect( return res.redirect(
`${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`, `${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`,

View File

@ -1,12 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { FileService } from './services/file.service'; import { FileService } from './services/file.service';
import { FileUploadService } from './services/file-upload.service'; import { FileUploadService } from './services/file-upload.service';
import { FileUploadResolver } from './resolvers/file-upload.resolver'; import { FileUploadResolver } from './resolvers/file-upload.resolver';
import { FileController } from './controllers/file.controller'; import { FileController } from './controllers/file.controller';
@Module({ @Module({
providers: [FileService, FileUploadService, FileUploadResolver], providers: [
FileService,
FileUploadService,
FileUploadResolver,
EnvironmentService,
],
exports: [FileService, FileUploadService], exports: [FileService, FileUploadService],
controllers: [FileController], controllers: [FileController],
}) })

View File

@ -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 { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { streamToBuffer } from 'src/utils/stream-to-buffer'; import { streamToBuffer } from 'src/utils/stream-to-buffer';
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard, DemoEnvGuard)
@Resolver() @Resolver()
export class FileUploadResolver { export class FileUploadResolver {
constructor(private readonly fileUploadService: FileUploadService) {} constructor(private readonly fileUploadService: FileUploadService) {}

View File

@ -6,7 +6,7 @@ import {
ResolveField, ResolveField,
Mutation, Mutation,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { ForbiddenException, UseGuards } from '@nestjs/common'; import { UseGuards } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import crypto from 'crypto'; 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 { streamToBuffer } from 'src/utils/stream-to-buffer';
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service'; import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
import { assert } from 'src/utils/assert'; 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 { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { User } from 'src/engine/modules/user/user.entity'; import { User } from 'src/engine/modules/user/user.entity';
import { WorkspaceMember } from 'src/engine/modules/user/dtos/workspace-member.dto'; import { WorkspaceMember } from 'src/engine/modules/user/dtos/workspace-member.dto';
@ -108,20 +109,9 @@ export class UserResolver {
return paths[0]; return paths[0];
} }
@UseGuards(DemoEnvGuard)
@Mutation(() => User) @Mutation(() => User)
async deleteUser(@AuthUser() { id: userId, defaultWorkspace }: User) { async deleteUser(@AuthUser() { id: userId }: 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.',
);
}
// Proceed with user deletion // Proceed with user deletion
return this.userService.deleteUser(userId); return this.userService.deleteUser(userId);
} }

View File

@ -6,7 +6,7 @@ import {
ResolveField, ResolveField,
Parent, Parent,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { ForbiddenException, UseGuards } from '@nestjs/common'; import { UseGuards } from '@nestjs/common';
import { FileUpload, GraphQLUpload } from 'graphql-upload'; 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 { assert } from 'src/utils/assert';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { UpdateWorkspaceInput } from 'src/engine/modules/workspace/dtos/update-workspace-input'; 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 { User } from 'src/engine/modules/user/user.entity';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { ActivateWorkspaceInput } from 'src/engine/modules/workspace/dtos/activate-workspace-input'; import { ActivateWorkspaceInput } from 'src/engine/modules/workspace/dtos/activate-workspace-input';
import { BillingSubscription } from 'src/engine/modules/billing/entities/billing-subscription.entity'; import { BillingSubscription } from 'src/engine/modules/billing/entities/billing-subscription.entity';
import { BillingService } from 'src/engine/modules/billing/billing.service'; import { BillingService } from 'src/engine/modules/billing/billing.service';
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
import { Workspace } from './workspace.entity'; import { Workspace } from './workspace.entity';
@ -35,7 +35,6 @@ export class WorkspaceResolver {
constructor( constructor(
private readonly workspaceService: WorkspaceService, private readonly workspaceService: WorkspaceService,
private readonly fileUploadService: FileUploadService, private readonly fileUploadService: FileUploadService,
private readonly environmentService: EnvironmentService,
private readonly billingService: BillingService, private readonly billingService: BillingService,
) {} ) {}
@ -89,15 +88,9 @@ export class WorkspaceResolver {
return paths[0]; return paths[0];
} }
@UseGuards(DemoEnvGuard)
@Mutation(() => Workspace) @Mutation(() => Workspace)
async deleteCurrentWorkspace(@AuthWorkspace() { id }: 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); return this.workspaceService.deleteWorkspace(id);
} }