diff --git a/packages/backend-core/src/logging/correlation/correlation.ts b/packages/backend-core/src/logging/correlation/correlation.ts index 0498bd43d5..13cc7aff8f 100644 --- a/packages/backend-core/src/logging/correlation/correlation.ts +++ b/packages/backend-core/src/logging/correlation/correlation.ts @@ -2,11 +2,12 @@ import { Header } from "../../constants" const correlator = require("correlation-id") -export const setHeader = (headers: any) => { +export const setHeader = (headers: Record) => { const correlationId = correlator.getId() - if (correlationId) { - headers[Header.CORRELATION_ID] = correlationId + if (!correlationId) { + return } + headers[Header.CORRELATION_ID] = correlationId } export function getId() { diff --git a/packages/server/src/api/controllers/dev.ts b/packages/server/src/api/controllers/dev.ts index d8c26725f1..497da088c6 100644 --- a/packages/server/src/api/controllers/dev.ts +++ b/packages/server/src/api/controllers/dev.ts @@ -1,7 +1,7 @@ import fetch from "node-fetch" import env from "../../environment" import { checkSlashesInUrl } from "../../utilities" -import { request } from "../../utilities/workerRequests" +import { createRequest } from "../../utilities/workerRequests" import { clearLock as redisClearLock } from "../../utilities/redis" import { DocumentType } from "../../db/utils" import { @@ -13,14 +13,19 @@ import { } from "@budibase/backend-core" import { App } from "@budibase/types" -async function redirect(ctx: any, method: string, path: string = "global") { +async function redirect( + ctx: any, + method: "GET" | "POST" | "DELETE", + path: string = "global" +) { const { devPath } = ctx.params const queryString = ctx.originalUrl.split("?")[1] || "" const response = await fetch( checkSlashesInUrl( `${env.WORKER_URL}/api/${path}/${devPath}?${queryString}` ), - request(ctx, { + createRequest({ + ctx, method, body: ctx.request.body, }) diff --git a/packages/server/src/utilities/workerRequests.ts b/packages/server/src/utilities/workerRequests.ts index b5c3bfcd4a..91340c8d78 100644 --- a/packages/server/src/utilities/workerRequests.ts +++ b/packages/server/src/utilities/workerRequests.ts @@ -1,4 +1,10 @@ -import { Response, default as fetch } from "node-fetch" +import { + Response, + default as fetch, + type RequestInit, + Headers, + HeadersInit, +} from "node-fetch" import env from "../environment" import { checkSlashesInUrl } from "./index" import { @@ -11,49 +17,58 @@ import { } from "@budibase/backend-core" import { Ctx, User, EmailInvite } from "@budibase/types" -export function request(ctx?: Ctx, request?: any) { - if (!request.headers) { - request.headers = {} +interface Request { + ctx?: Ctx + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" + headers?: { [key: string]: string } + body?: { [key: string]: any } +} + +export function createRequest(request: Request): RequestInit { + const headers: Record = {} + const requestInit: RequestInit = { + method: request.method, } - if (!ctx) { - request.headers[constants.Header.API_KEY] = coreEnv.INTERNAL_API_KEY - } else if (ctx.headers) { + const ctx = request.ctx + + if (!ctx && coreEnv.INTERNAL_API_KEY) { + headers[constants.Header.API_KEY] = coreEnv.INTERNAL_API_KEY + } else if (ctx && ctx.headers) { // copy all Budibase utilised headers over - copying everything can have // side effects like requests being rejected due to odd content types etc for (let header of Object.values(constants.Header)) { - if (ctx.headers[header]) { - request.headers[header] = ctx.headers[header] + const value = ctx.headers[header] + if (value === undefined) { + continue } + headers[header] = Array.isArray(value) ? value[0] : value } // be specific about auth headers const cookie = ctx.headers[constants.Header.COOKIE], apiKey = ctx.headers[constants.Header.API_KEY] if (cookie) { - request.headers[constants.Header.COOKIE] = cookie + headers[constants.Header.COOKIE] = cookie } else if (apiKey) { - request.headers[constants.Header.API_KEY] = apiKey + headers[constants.Header.API_KEY] = Array.isArray(apiKey) + ? apiKey[0] + : apiKey } } // apply tenancy if its available if (tenancy.isTenantIdSet()) { - request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId() + headers[constants.Header.TENANT_ID] = tenancy.getTenantId() } + if (request.body && Object.keys(request.body).length > 0) { - request.headers["Content-Type"] = "application/json" - request.body = - typeof request.body === "object" - ? JSON.stringify(request.body) - : request.body - } else { - delete request.body + headers["Content-Type"] = "application/json" + requestInit.body = JSON.stringify(request.body) } - // add x-budibase-correlation-id header - logging.correlation.setHeader(request.headers) - - return request + logging.correlation.setHeader(headers) + requestInit.headers = headers + return requestInit } async function checkResponse( @@ -102,7 +117,7 @@ export async function sendSmtpEmail({ // tenant ID will be set in header const response = await fetch( checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`), - request(undefined, { + createRequest({ method: "POST", body: { email: to, @@ -124,7 +139,8 @@ export async function removeAppFromUserRoles(ctx: Ctx, appId: string) { const prodAppId = dbCore.getProdAppID(appId) const response = await fetch( checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`), - request(ctx, { + createRequest({ + ctx, method: "DELETE", }) ) @@ -135,7 +151,7 @@ export async function allGlobalUsers(ctx: Ctx) { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + "/api/global/users"), // we don't want to use API key when getting self - request(ctx, { method: "GET" }) + createRequest({ ctx, method: "GET" }) ) return checkResponse(response, "get users", { ctx }) } @@ -144,7 +160,7 @@ export async function saveGlobalUser(ctx: Ctx) { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + "/api/global/users"), // we don't want to use API key when getting self - request(ctx, { method: "POST", body: ctx.request.body }) + createRequest({ ctx, method: "POST", body: ctx.request.body }) ) return checkResponse(response, "save user", { ctx }) } @@ -155,7 +171,7 @@ export async function deleteGlobalUser(ctx: Ctx) { env.WORKER_URL + `/api/global/users/${ctx.params.userId}` ), // we don't want to use API key when getting self - request(ctx, { method: "DELETE" }) + createRequest({ ctx, method: "DELETE" }) ) return checkResponse(response, "delete user", { ctx }) } @@ -166,7 +182,7 @@ export async function readGlobalUser(ctx: Ctx): Promise { env.WORKER_URL + `/api/global/users/${ctx.params.userId}` ), // we don't want to use API key when getting self - request(ctx, { method: "GET" }) + createRequest({ ctx, method: "GET" }) ) return checkResponse(response, "get user", { ctx }) } @@ -176,7 +192,7 @@ export async function getChecklist(): Promise<{ }> { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + "/api/global/configs/checklist"), - request(undefined, { method: "GET" }) + createRequest({ method: "GET" }) ) return checkResponse(response, "get checklist") } @@ -184,7 +200,7 @@ export async function getChecklist(): Promise<{ export async function generateApiKey(userId: string) { const response = await fetch( checkSlashesInUrl(env.WORKER_URL + "/api/global/self/api_key"), - request(undefined, { method: "POST", body: { userId } }) + createRequest({ method: "POST", body: { userId } }) ) return checkResponse(response, "generate API key") }