From 69e92054717f8ae6ddb53943a8f155bce6377590 Mon Sep 17 00:00:00 2001 From: DarkPhoenix2704 Date: Wed, 2 Oct 2024 05:54:21 +0000 Subject: [PATCH] feat: scripts wip --- packages/nc-gui/components/scripts/index.html | 152 +++++++++++ packages/nc-gui/components/scripts/index.vue | 148 +++++++++++ .../nc-gui/components/smartsheet/Topbar.vue | 28 ++ .../nc-gui/components/tabs/Smartsheet.vue | 4 + packages/nc-gui/composables/useScripts.ts | 17 ++ packages/nc-gui/lang/en.json | 1 + .../src/controllers/scripts.controller.ts | 245 ++++++++++++++++++ packages/nocodb/src/modules/noco.module.ts | 2 + packages/nocodb/src/schema/swagger.json | 35 +++ 9 files changed, 632 insertions(+) create mode 100644 packages/nc-gui/components/scripts/index.html create mode 100644 packages/nc-gui/components/scripts/index.vue create mode 100644 packages/nc-gui/composables/useScripts.ts create mode 100644 packages/nocodb/src/controllers/scripts.controller.ts diff --git a/packages/nc-gui/components/scripts/index.html b/packages/nc-gui/components/scripts/index.html new file mode 100644 index 0000000000..7bca79636f --- /dev/null +++ b/packages/nc-gui/components/scripts/index.html @@ -0,0 +1,152 @@ + + + + + + Secure Script Executor + + + + + + +
+
+
+
Console Output
+
+
+
+ + + + + diff --git a/packages/nc-gui/components/scripts/index.vue b/packages/nc-gui/components/scripts/index.vue new file mode 100644 index 0000000000..b7149bac51 --- /dev/null +++ b/packages/nc-gui/components/scripts/index.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/packages/nc-gui/components/smartsheet/Topbar.vue b/packages/nc-gui/components/smartsheet/Topbar.vue index d39ff80d02..1eefd896b8 100644 --- a/packages/nc-gui/components/smartsheet/Topbar.vue +++ b/packages/nc-gui/components/smartsheet/Topbar.vue @@ -8,6 +8,8 @@ const isPublic = inject(IsPublicInj, ref(false)) const { isViewsLoading } = storeToRefs(useViewsStore()) +const { isScriptsEnabled } = useScripts() + const { isMobileMode } = storeToRefs(useConfigStore()) const { appInfo } = useGlobal() @@ -78,6 +80,32 @@ const topbarBreadcrumbItemWidth = computed(() => {
+ + +
+ + + {{ $t('general.scripts') }} + +
+
+
diff --git a/packages/nc-gui/components/tabs/Smartsheet.vue b/packages/nc-gui/components/tabs/Smartsheet.vue index 655a55e995..228087c5ea 100644 --- a/packages/nc-gui/components/tabs/Smartsheet.vue +++ b/packages/nc-gui/components/tabs/Smartsheet.vue @@ -18,6 +18,8 @@ const { isMobileMode } = useGlobal() const activeTab = toRef(props, 'activeTab') +const { isScriptsEnabled } = useScripts() + const route = useRoute() const meta = computed(() => { @@ -236,6 +238,8 @@ const onReady = () => {
+ + diff --git a/packages/nc-gui/composables/useScripts.ts b/packages/nc-gui/composables/useScripts.ts new file mode 100644 index 0000000000..8bbb05e24a --- /dev/null +++ b/packages/nc-gui/composables/useScripts.ts @@ -0,0 +1,17 @@ +export const useScripts = createSharedComposable(() => { + const isScriptsEnabled = ref(false) + + const { $api } = useNuxtApp() + + const execScript = async (code: string) => { + const data = $api.scripts.scriptExec({ + body: { code }, + }) + + console.log(data) + + return data + } + + return { isScriptsEnabled, execScript } +}) diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index 9e117d58f9..496aa9c8c6 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -93,6 +93,7 @@ "none": "None" }, "general": { + "scripts": "Scripts", "configure": "Configure", "switch": "Switch", "on": "On", diff --git a/packages/nocodb/src/controllers/scripts.controller.ts b/packages/nocodb/src/controllers/scripts.controller.ts new file mode 100644 index 0000000000..56c7c50591 --- /dev/null +++ b/packages/nocodb/src/controllers/scripts.controller.ts @@ -0,0 +1,245 @@ +import { + Body, + Controller, + Get, + Post, + Req, + Res, + UseGuards, +} from '@nestjs/common'; +import { CodeInterpreter } from '@e2b/code-interpreter'; +import Sandbox from 'v8-sandbox'; +import { Response } from 'express'; +import request from 'supertest'; +import { GlobalGuard } from '~/guards/global/global.guard'; +import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard'; +import { TenantContext } from '~/decorators/tenant-context.decorator'; +import { NcContext, NcRequest } from '~/interface/config'; + +@Controller() +@UseGuards(MetaApiLimiterGuard, GlobalGuard) +export class ScriptsController { + constructor() {} + + @Post(['/api/v2/scripts/exec']) + async scriptExec( + @TenantContext() context: NcContext, + @Body() body, + @Req() req: NcRequest, + ) { + console.log(body); + + const sandbox = new Sandbox(); + + const { error, output, value } = await sandbox.execute({ + code: body.code, + timeout: 1000, + }); + + console.log(value, output, error); + + // return output; + + const sandboxE2b = await CodeInterpreter.create({ + apiKey: 'e2b_28eedec39c0398d545f5cfa3e2b2332a13d3c4e2', + }); + const x = await sandboxE2b.notebook.execCell(body.code); + + console.log(x); + + return { x, output }; + } + + @Get(['/api/v2/scripts']) + async scriptHTML(@Res() res: Response, @Req() req: NcRequest) { + res.setHeader('Content-Type', 'text/html'); + res.setHeader('Cache-Control', 'no-store, max-age=0'); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('Referrer-Policy', 'no-referrer'); + res.setHeader( + 'Permissions-Policy', + 'geolocation=(), microphone=(), camera=(), midi=(), encrypted-media=()', + ); + res.setHeader( + 'Strict-Transport-Security', + 'max-age=31536000; includeSubDomains; preload', + ); + + const cspDirectives = [ + "default-src 'none'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net", + "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com", + `connect-src 'self' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net http://localhost:8080`, + "font-src 'self' https://cdnjs.cloudflare.com", + "worker-src 'self' blob:", + 'child-src blob:', + "base-uri 'none'", + "form-action 'none'", + ]; + + // res.setHeader('Content-Security-Policy', cspDirectives.join('; ')); + + res.send( + generateHTML({ + token: req.headers['Xc-Auth'] as string, + url: 'http://localhost:8080', + }), + ); + } +} + +const generateHTML = (config: { token: string; url: string }) => { + return ` + + + + + Secure Script Executor + + + + + + +
+
+
+
Console Output
+
+
+
+
+ Secure Script Executor © 2023 +
+ + + + +`; +}; diff --git a/packages/nocodb/src/modules/noco.module.ts b/packages/nocodb/src/modules/noco.module.ts index c235337380..c88214b2e0 100644 --- a/packages/nocodb/src/modules/noco.module.ts +++ b/packages/nocodb/src/modules/noco.module.ts @@ -124,6 +124,7 @@ import { CalendarDatasController } from '~/controllers/calendars-datas.controlle import { CalendarDatasService } from '~/services/calendar-datas.service'; import { IntegrationsController } from '~/controllers/integrations.controller'; import { IntegrationsService } from '~/services/integrations.service'; +import { ScriptsController } from '~/controllers/scripts.controller'; export const nocoModuleMetadata = { imports: [ @@ -196,6 +197,7 @@ export const nocoModuleMetadata = { OldDatasController, PublicDatasController, PublicDatasExportController, + ScriptsController, ] : []), ], diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index 3feb763c8d..b2f08423e7 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/packages/nocodb/src/schema/swagger.json @@ -18130,6 +18130,41 @@ "description": "Get dynamic command palette suggestions based on scope" } }, + "/api/v2/scripts/exec": { + "post": { + "summary": "Exec Scripts", + "operationId": "script-exec", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + }, + "tags": [ + "Scripts" + ], + "description": "Execute scripts", + "parameters": [ + { + "$ref": "#/components/parameters/xc-auth" + } + ] + } + }, "/api/v2/extensions/{baseId}": { "parameters": [ {