Fix permissions for serverless functions (#6555)
Fixes #6525 (@martmull fyi it was not related to AWS but linked to the fact that we recently enforced passing a token to access files)
This commit is contained in:
@ -32,7 +32,9 @@ export class FileController {
|
||||
const workspaceId = (req as any)?.workspaceId;
|
||||
|
||||
if (!workspaceId) {
|
||||
return res.status(401).send({ error: 'Unauthorized' });
|
||||
return res
|
||||
.status(401)
|
||||
.send({ error: 'Unauthorized, missing workspaceId' });
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@ -2,17 +2,17 @@ import { join } from 'path';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name';
|
||||
import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content';
|
||||
import { compileTypescript } from 'src/engine/integrations/serverless/drivers/utils/compile-typescript';
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content';
|
||||
import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name';
|
||||
import { compileTypescript } from 'src/engine/integrations/serverless/drivers/utils/compile-typescript';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
|
||||
export class BaseServerlessDriver {
|
||||
getFolderPath(serverlessFunction: ServerlessFunctionEntity) {
|
||||
return join(
|
||||
'workspace-' + serverlessFunction.workspaceId,
|
||||
FileFolder.ServerlessFunction,
|
||||
serverlessFunction.workspaceId,
|
||||
serverlessFunction.id,
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { Observable, map } from 'rxjs';
|
||||
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionInterceptor implements NestInterceptor {
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(
|
||||
map(async (data) => {
|
||||
if (data.edges && Array.isArray(data.edges)) {
|
||||
return {
|
||||
...data,
|
||||
edges: Promise.all(
|
||||
data.edges.map((item) => ({
|
||||
...item,
|
||||
node: this.processItem(item.node),
|
||||
})),
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return this.processItem(data);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async processItem(item: any): Promise<any> {
|
||||
if (item && item.sourceCodeFullPath) {
|
||||
const workspaceId = item.workspace?.id || item.workspaceId;
|
||||
|
||||
if (!workspaceId) {
|
||||
return item;
|
||||
}
|
||||
|
||||
const signedPayload = await this.fileService.encodeFileToken({
|
||||
serverlessFunctionId: item.id,
|
||||
workspace_id: workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
...item,
|
||||
sourceCodeFullPath: `${item.sourceCodeFullPath}?token=${signedPayload}`,
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,23 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
import {
|
||||
NestjsQueryGraphQLModule,
|
||||
PagingStrategies,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { ServerlessModule } from 'src/engine/integrations/serverless/serverless.module';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { ServerlessModule } from 'src/engine/integrations/serverless/serverless.module';
|
||||
import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { ServerlessFunctionInterceptor } from 'src/engine/metadata-modules/serverless-function/serverless-function.interceptor';
|
||||
import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -27,6 +29,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
|
||||
'metadata',
|
||||
),
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
FileModule,
|
||||
],
|
||||
services: [ServerlessFunctionService],
|
||||
resolvers: [
|
||||
@ -42,6 +45,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
|
||||
update: { disabled: true },
|
||||
delete: { disabled: true },
|
||||
guards: [JwtAuthGuard],
|
||||
interceptors: [ServerlessFunctionInterceptor],
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { ServerlessFunctionInterceptor } from 'src/engine/metadata-modules/serverless-function/serverless-function.interceptor';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils';
|
||||
|
||||
@ -66,6 +67,7 @@ export class ServerlessFunctionResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@UseInterceptors(ServerlessFunctionInterceptor)
|
||||
@Mutation(() => ServerlessFunctionDTO)
|
||||
async updateOneServerlessFunction(
|
||||
@Args('input')
|
||||
@ -84,6 +86,7 @@ export class ServerlessFunctionResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@UseInterceptors(ServerlessFunctionInterceptor)
|
||||
@Mutation(() => ServerlessFunctionDTO)
|
||||
async createOneServerlessFunction(
|
||||
@Args('input')
|
||||
@ -106,6 +109,7 @@ export class ServerlessFunctionResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@UseInterceptors(ServerlessFunctionInterceptor)
|
||||
@Mutation(() => ServerlessFunctionDTO)
|
||||
async createOneServerlessFunctionFromFile(
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
|
||||
@ -3,15 +3,20 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { FileUpload } from 'graphql-upload';
|
||||
import { Repository } from 'typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { ServerlessExecuteResult } from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content';
|
||||
import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name';
|
||||
import { ServerlessService } from 'src/engine/integrations/serverless/serverless.service';
|
||||
import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
import {
|
||||
ServerlessFunctionEntity,
|
||||
ServerlessFunctionSyncStatus,
|
||||
@ -20,12 +25,7 @@ import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content';
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name';
|
||||
import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils';
|
||||
import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
|
||||
@ -119,8 +119,8 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
|
||||
if (codeHasChanged) {
|
||||
const fileFolder = join(
|
||||
'workspace-' + workspaceId,
|
||||
FileFolder.ServerlessFunction,
|
||||
workspaceId,
|
||||
existingServerlessFunction.id,
|
||||
);
|
||||
|
||||
@ -164,13 +164,18 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
|
||||
const serverlessFunctionId = v4();
|
||||
|
||||
const fileFolder = join(
|
||||
const fileFolderWithoutWorkspace = join(
|
||||
FileFolder.ServerlessFunction,
|
||||
workspaceId,
|
||||
serverlessFunctionId,
|
||||
);
|
||||
|
||||
const sourceCodeFullPath = fileFolder + '/' + SOURCE_FILE_NAME;
|
||||
const fileFolder = join(
|
||||
'workspace-' + workspaceId,
|
||||
fileFolderWithoutWorkspace,
|
||||
);
|
||||
|
||||
const sourceCodeFullPath =
|
||||
fileFolderWithoutWorkspace + '/' + SOURCE_FILE_NAME;
|
||||
|
||||
const serverlessFunction = await super.createOne({
|
||||
...serverlessFunctionInput,
|
||||
|
||||
@ -180,7 +180,6 @@ yarn command:prod cron:calendar:calendar-event-list-fetch
|
||||
### Data enrichment and AI
|
||||
|
||||
<ArticleTable options={[
|
||||
['OPENROUTER_API_KEY', '', "The API key for openrouter.ai, an abstraction layer over models from Mistral, OpenAI and more"],
|
||||
['OPENAI_API_KEY', 'sk-proj-abcdabcd...', "OpenAI API key"],
|
||||
['LLM_CHAT_MODEL_DRIVER', 'openai', "LLM provider"],
|
||||
['LLM_TRACING_DRIVER', 'langfuse', "Where to output LangChain logs. 'langfuse' or 'console'."],
|
||||
@ -188,6 +187,16 @@ yarn command:prod cron:calendar:calendar-event-list-fetch
|
||||
['LANGFUSE_PUBLIC_KEY', 'pk-lf-abcdabcd-abcd...', "Langfuse public key"],
|
||||
]}></ArticleTable>
|
||||
|
||||
### Serverless functions
|
||||
This feature is WIP and is not yet useful for most users.
|
||||
<ArticleTable options={[
|
||||
['SERVERLESS_TYPE', 'local', "Functions can either be executed through Lambda or directly by the main node process"],
|
||||
['SERVERLESS_LAMBDA_REGION', 'us-east-1', 'If you use the Lambda driver, region of the Lambda function'],
|
||||
['SERVERLESS_LAMBDA_ROLE', 'arn:aws:iam::121334:role/lambda-execution-role', "If you use the Lambda drive, name of the IAM role with the right permissions"],
|
||||
]}></ArticleTable>
|
||||
|
||||
|
||||
|
||||
|
||||
### Support Chat
|
||||
|
||||
|
||||
Reference in New Issue
Block a user