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 69ce58c8a9..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 { @@ -7,36 +13,62 @@ import { tenancy, logging, env as coreEnv, + utils, } 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 - if (tenancy.isTenantIdSet()) { - request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId() + + 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)) { + 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) { + headers[constants.Header.COOKIE] = cookie + } else if (apiKey) { + headers[constants.Header.API_KEY] = Array.isArray(apiKey) + ? apiKey[0] + : apiKey } } + + // apply tenancy if its available + if (tenancy.isTenantIdSet()) { + 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 - } - if (ctx && ctx.headers) { - request.headers = ctx.headers + 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( @@ -54,7 +86,7 @@ async function checkResponse( } const msg = `Unable to ${errorMsg} - ${responseErrorMessage}` if (ctx) { - ctx.throw(msg, response.status) + ctx.throw(response.status || 500, msg) } else { throw msg } @@ -85,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, @@ -107,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", }) ) @@ -118,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 }) } @@ -127,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 }) } @@ -138,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 }) } @@ -149,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 }) } @@ -159,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") } @@ -167,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") } diff --git a/packages/shared-core/src/constants/api.ts b/packages/shared-core/src/constants/api.ts index d6633649e6..f63849bf3d 100644 --- a/packages/shared-core/src/constants/api.ts +++ b/packages/shared-core/src/constants/api.ts @@ -16,4 +16,5 @@ export enum Header { CORRELATION_ID = "x-budibase-correlation-id", AUTHORIZATION = "authorization", MIGRATING_APP = "x-budibase-migrating-app", + COOKIE = "cookie", }