diff --git a/packages/backend-core/src/cache/docWritethrough.ts b/packages/backend-core/src/cache/docWritethrough.ts index 1b129bb26a..05f13a0d91 100644 --- a/packages/backend-core/src/cache/docWritethrough.ts +++ b/packages/backend-core/src/cache/docWritethrough.ts @@ -1,6 +1,6 @@ import { AnyDocument, Database } from "@budibase/types" -import { JobQueue, createQueue } from "../queue" +import { JobQueue, Queue, createQueue } from "../queue" import * as dbUtils from "../db" interface ProcessDocMessage { @@ -12,18 +12,26 @@ interface ProcessDocMessage { const PERSIST_MAX_ATTEMPTS = 100 let processor: DocWritethroughProcessor | undefined -export const docWritethroughProcessorQueue = createQueue( - JobQueue.DOC_WRITETHROUGH_QUEUE, - { - jobOptions: { - attempts: PERSIST_MAX_ATTEMPTS, - }, - } -) +export class DocWritethroughProcessor { + private static _queue: Queue + + public static get queue() { + if (!DocWritethroughProcessor._queue) { + DocWritethroughProcessor._queue = createQueue( + JobQueue.DOC_WRITETHROUGH_QUEUE, + { + jobOptions: { + attempts: PERSIST_MAX_ATTEMPTS, + }, + } + ) + } + + return DocWritethroughProcessor._queue + } -class DocWritethroughProcessor { init() { - docWritethroughProcessorQueue.process(async message => { + DocWritethroughProcessor.queue.process(async message => { try { await this.persistToDb(message.data) } catch (err: any) { @@ -76,7 +84,7 @@ export class DocWritethrough { } async patch(data: Record) { - await docWritethroughProcessorQueue.add({ + await DocWritethroughProcessor.queue.add({ dbName: this.db.name, docId: this.docId, data, diff --git a/packages/backend-core/src/cache/tests/docWritethrough.spec.ts b/packages/backend-core/src/cache/tests/docWritethrough.spec.ts index 1ae85cfd0b..b40338c408 100644 --- a/packages/backend-core/src/cache/tests/docWritethrough.spec.ts +++ b/packages/backend-core/src/cache/tests/docWritethrough.spec.ts @@ -6,7 +6,7 @@ import { getDB } from "../../db" import { DocWritethrough, - docWritethroughProcessorQueue, + DocWritethroughProcessor, init, } from "../docWritethrough" @@ -15,7 +15,7 @@ import InMemoryQueue from "../../queue/inMemoryQueue" const initialTime = Date.now() async function waitForQueueCompletion() { - const queue: InMemoryQueue = docWritethroughProcessorQueue as never + const queue: InMemoryQueue = DocWritethroughProcessor.queue as never await queue.waitForCompletion() } @@ -235,7 +235,7 @@ describe("docWritethrough", () => { return acc }, {}) } - const queueMessageSpy = jest.spyOn(docWritethroughProcessorQueue, "add") + const queueMessageSpy = jest.spyOn(DocWritethroughProcessor.queue, "add") await config.doInTenant(async () => { let patches = await parallelPatch(5) diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json index f89ad95c28..e52d886194 100644 --- a/packages/cli/tsconfig.build.json +++ b/packages/cli/tsconfig.build.json @@ -11,6 +11,7 @@ "types": ["node", "jest"], "outDir": "dist", "skipLibCheck": true, + "baseUrl": ".", "paths": { "@budibase/types": ["../types/src"], "@budibase/backend-core": ["../backend-core/src"], diff --git a/packages/server/src/api/controllers/query/index.ts b/packages/server/src/api/controllers/query/index.ts index 3c21537484..0dba20dacd 100644 --- a/packages/server/src/api/controllers/query/index.ts +++ b/packages/server/src/api/controllers/query/index.ts @@ -14,22 +14,35 @@ import { SessionCookie, JsonFieldSubType, QueryResponse, - QueryPreview, QuerySchema, FieldType, ExecuteQueryRequest, ExecuteQueryResponse, - Row, QueryParameter, PreviewQueryRequest, PreviewQueryResponse, } from "@budibase/types" import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core" +import { findHBSBlocks } from "@budibase/string-templates" const Runner = new Thread(ThreadType.QUERY, { timeoutMs: env.QUERY_THREAD_TIMEOUT, }) +function validateQueryInputs(parameters: Record) { + for (let entry of Object.entries(parameters)) { + const [key, value] = entry + if (typeof value !== "string") { + continue + } + if (findHBSBlocks(value).length !== 0) { + throw new Error( + `Parameter '${key}' input contains a handlebars binding - this is not allowed.` + ) + } + } +} + export async function fetch(ctx: UserCtx) { ctx.body = await sdk.queries.fetch() } @@ -123,10 +136,10 @@ function getAuthConfig(ctx: UserCtx) { function enrichParameters( queryParameters: QueryParameter[], - requestParameters: { [key: string]: string } = {} -): { - [key: string]: string -} { + requestParameters: Record = {} +): Record { + // first check parameters are all valid + validateQueryInputs(requestParameters) // make sure parameters are fully enriched with defaults for (let parameter of queryParameters) { if (!requestParameters[parameter.name]) { diff --git a/packages/server/src/api/routes/tests/queries/query.seq.spec.ts b/packages/server/src/api/routes/tests/queries/query.seq.spec.ts index 4347ed9044..fc90ad4247 100644 --- a/packages/server/src/api/routes/tests/queries/query.seq.spec.ts +++ b/packages/server/src/api/routes/tests/queries/query.seq.spec.ts @@ -408,6 +408,21 @@ describe("/queries", () => { }, }) }) + + it("shouldn't allow handlebars to be passed as parameters", async () => { + const res = await request + .post(`/api/queries/${query._id}`) + .send({ + parameters: { + a: "{{ 'test' }}", + }, + }) + .set(config.defaultHeaders()) + .expect(400) + expect(res.body.message).toEqual( + "Parameter 'a' input contains a handlebars binding - this is not allowed." + ) + }) }) describe("variables", () => { diff --git a/packages/types/src/api/web/query.ts b/packages/types/src/api/web/query.ts index 3959cdea19..66f0248647 100644 --- a/packages/types/src/api/web/query.ts +++ b/packages/types/src/api/web/query.ts @@ -11,7 +11,7 @@ export interface PreviewQueryResponse { } export interface ExecuteQueryRequest { - parameters?: { [key: string]: string } + parameters?: Record pagination?: any }