2024-02-15 15:48:47 +01:00
|
|
|
import {
|
|
|
|
Response,
|
|
|
|
default as fetch,
|
|
|
|
type RequestInit,
|
|
|
|
Headers,
|
|
|
|
HeadersInit,
|
|
|
|
} from "node-fetch"
|
2022-11-22 13:41:36 +01:00
|
|
|
import env from "../environment"
|
|
|
|
import { checkSlashesInUrl } from "./index"
|
2023-01-11 21:39:33 +01:00
|
|
|
import {
|
|
|
|
db as dbCore,
|
|
|
|
constants,
|
|
|
|
tenancy,
|
|
|
|
logging,
|
2023-03-13 16:02:59 +01:00
|
|
|
env as coreEnv,
|
2024-02-15 16:47:56 +01:00
|
|
|
utils,
|
2023-01-11 21:39:33 +01:00
|
|
|
} from "@budibase/backend-core"
|
2023-08-22 19:14:08 +02:00
|
|
|
import { Ctx, User, EmailInvite } from "@budibase/types"
|
2021-04-09 16:11:49 +02:00
|
|
|
|
2024-02-15 15:48:47 +01:00
|
|
|
function ensureHeadersIsObject(headers: HeadersInit | undefined): Headers {
|
|
|
|
if (headers instanceof Headers) {
|
|
|
|
return headers
|
|
|
|
}
|
|
|
|
|
|
|
|
const headersObj = new Headers()
|
|
|
|
if (headers === undefined) {
|
|
|
|
return headersObj
|
2021-04-09 16:11:49 +02:00
|
|
|
}
|
2024-02-15 14:45:08 +01:00
|
|
|
|
2024-02-15 15:48:47 +01:00
|
|
|
if (Array.isArray(headers)) {
|
|
|
|
for (const [key, value] of headers) {
|
|
|
|
headersObj.append(key, value)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (const key in headers) {
|
|
|
|
headersObj.append(key, headers[key])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return headersObj
|
|
|
|
}
|
|
|
|
|
2024-02-15 16:49:30 +01:00
|
|
|
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<string, string> = {}
|
|
|
|
const requestInit: RequestInit = {
|
|
|
|
method: request.method,
|
|
|
|
}
|
|
|
|
|
2024-02-15 15:48:47 +01:00
|
|
|
const ctx = request.ctx
|
|
|
|
|
2024-02-15 16:49:30 +01:00
|
|
|
if (!ctx && coreEnv.INTERNAL_API_KEY) {
|
|
|
|
headers[constants.Header.API_KEY] = coreEnv.INTERNAL_API_KEY
|
|
|
|
} else if (ctx && ctx.headers) {
|
2024-02-15 14:45:08 +01:00
|
|
|
// 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)) {
|
2024-02-15 15:48:47 +01:00
|
|
|
const value = ctx.headers[header]
|
|
|
|
if (value === undefined) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
2024-02-15 16:49:30 +01:00
|
|
|
headers[header] = value[0]
|
2024-02-15 15:48:47 +01:00
|
|
|
} else {
|
2024-02-15 16:49:30 +01:00
|
|
|
headers[header] = value
|
2024-02-15 14:45:08 +01:00
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
2024-02-15 16:47:56 +01:00
|
|
|
// 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
|
|
|
|
} else if (apiKey) {
|
|
|
|
request.headers[constants.Header.API_KEY] = apiKey
|
|
|
|
}
|
2021-06-04 13:13:29 +02:00
|
|
|
}
|
2024-02-15 14:45:08 +01:00
|
|
|
|
|
|
|
// apply tenancy if its available
|
|
|
|
if (tenancy.isTenantIdSet()) {
|
2024-02-15 16:49:30 +01:00
|
|
|
headers[constants.Header.TENANT_ID] = tenancy.getTenantId()
|
2024-02-15 14:45:08 +01:00
|
|
|
}
|
2024-02-15 16:49:30 +01:00
|
|
|
|
2021-05-10 14:18:05 +02:00
|
|
|
if (request.body && Object.keys(request.body).length > 0) {
|
2024-02-15 16:49:30 +01:00
|
|
|
headers["Content-Type"] = "application/json"
|
|
|
|
requestInit.body = JSON.stringify(request.body)
|
2021-04-09 16:11:49 +02:00
|
|
|
}
|
2023-01-11 21:39:33 +01:00
|
|
|
|
2024-02-15 16:49:30 +01:00
|
|
|
logging.correlation.setHeader(headers)
|
|
|
|
return requestInit
|
2021-04-09 16:11:49 +02:00
|
|
|
}
|
|
|
|
|
2022-11-22 13:41:36 +01:00
|
|
|
async function checkResponse(
|
2024-02-02 16:25:18 +01:00
|
|
|
response: Response,
|
2022-11-22 13:41:36 +01:00
|
|
|
errorMsg: string,
|
2023-08-22 19:14:08 +02:00
|
|
|
{ ctx }: { ctx?: Ctx } = {}
|
2022-11-22 13:41:36 +01:00
|
|
|
) {
|
2024-02-02 16:25:18 +01:00
|
|
|
if (response.status >= 300) {
|
|
|
|
let responseErrorMessage
|
|
|
|
if (response.headers.get("content-type")?.includes("json")) {
|
|
|
|
const error = await response.json()
|
|
|
|
responseErrorMessage = error.message ?? JSON.stringify(error)
|
|
|
|
} else {
|
|
|
|
responseErrorMessage = await response.text()
|
2022-02-25 20:00:12 +01:00
|
|
|
}
|
2024-02-02 16:25:18 +01:00
|
|
|
const msg = `Unable to ${errorMsg} - ${responseErrorMessage}`
|
2022-02-25 20:00:12 +01:00
|
|
|
if (ctx) {
|
2024-02-15 14:45:08 +01:00
|
|
|
ctx.throw(response.status || 500, msg)
|
2022-02-25 20:00:12 +01:00
|
|
|
} else {
|
|
|
|
throw msg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return response.json()
|
|
|
|
}
|
|
|
|
|
2021-08-05 10:59:08 +02:00
|
|
|
// have to pass in the tenant ID as this could be coming from an automation
|
2023-06-08 15:25:35 +02:00
|
|
|
export async function sendSmtpEmail({
|
|
|
|
to,
|
|
|
|
from,
|
|
|
|
subject,
|
|
|
|
contents,
|
|
|
|
cc,
|
|
|
|
bcc,
|
|
|
|
automation,
|
|
|
|
invite,
|
|
|
|
}: {
|
|
|
|
to: string
|
|
|
|
from: string
|
|
|
|
subject: string
|
|
|
|
contents: string
|
|
|
|
cc: string
|
|
|
|
bcc: string
|
2022-11-25 20:57:07 +01:00
|
|
|
automation: boolean
|
2023-06-08 15:25:35 +02:00
|
|
|
invite?: EmailInvite
|
|
|
|
}) {
|
2021-08-05 10:59:08 +02:00
|
|
|
// tenant ID will be set in header
|
2021-05-11 13:02:29 +02:00
|
|
|
const response = await fetch(
|
2021-08-05 10:59:08 +02:00
|
|
|
checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`),
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({
|
2021-05-11 13:02:29 +02:00
|
|
|
method: "POST",
|
2024-02-15 16:49:30 +01:00
|
|
|
body: {
|
2021-05-11 16:08:59 +02:00
|
|
|
email: to,
|
|
|
|
from,
|
|
|
|
contents,
|
|
|
|
subject,
|
2022-09-21 16:58:04 +02:00
|
|
|
cc,
|
|
|
|
bcc,
|
2021-05-11 16:08:59 +02:00
|
|
|
purpose: "custom",
|
2021-09-27 17:28:39 +02:00
|
|
|
automation,
|
2023-06-08 15:25:35 +02:00
|
|
|
invite,
|
2024-02-15 16:49:30 +01:00
|
|
|
},
|
2021-05-11 13:02:29 +02:00
|
|
|
})
|
|
|
|
)
|
2022-02-25 20:00:12 +01:00
|
|
|
return checkResponse(response, "send email")
|
2021-05-11 13:02:29 +02:00
|
|
|
}
|
|
|
|
|
2023-08-22 19:14:08 +02:00
|
|
|
export async function removeAppFromUserRoles(ctx: Ctx, appId: string) {
|
2022-11-22 13:41:36 +01:00
|
|
|
const prodAppId = dbCore.getProdAppID(appId)
|
2021-06-01 16:58:40 +02:00
|
|
|
const response = await fetch(
|
2022-01-31 18:42:51 +01:00
|
|
|
checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`),
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({
|
2024-02-15 15:48:47 +01:00
|
|
|
ctx,
|
2021-06-01 16:58:40 +02:00
|
|
|
method: "DELETE",
|
|
|
|
})
|
|
|
|
)
|
2022-02-25 20:00:12 +01:00
|
|
|
return checkResponse(response, "remove app role")
|
|
|
|
}
|
|
|
|
|
2023-08-22 19:14:08 +02:00
|
|
|
export async function allGlobalUsers(ctx: Ctx) {
|
2022-02-25 20:00:12 +01:00
|
|
|
const response = await fetch(
|
|
|
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
|
|
|
|
// we don't want to use API key when getting self
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({ ctx, method: "GET" })
|
2022-02-25 20:00:12 +01:00
|
|
|
)
|
|
|
|
return checkResponse(response, "get users", { ctx })
|
|
|
|
}
|
|
|
|
|
2023-08-22 19:14:08 +02:00
|
|
|
export async function saveGlobalUser(ctx: Ctx) {
|
2022-02-25 20:00:12 +01:00
|
|
|
const response = await fetch(
|
|
|
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/users"),
|
|
|
|
// we don't want to use API key when getting self
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({ ctx, method: "POST", body: ctx.request.body })
|
2022-02-25 20:00:12 +01:00
|
|
|
)
|
|
|
|
return checkResponse(response, "save user", { ctx })
|
|
|
|
}
|
|
|
|
|
2023-08-22 19:14:08 +02:00
|
|
|
export async function deleteGlobalUser(ctx: Ctx) {
|
2022-02-25 20:00:12 +01:00
|
|
|
const response = await fetch(
|
2022-02-25 20:01:17 +01:00
|
|
|
checkSlashesInUrl(
|
|
|
|
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
|
|
|
),
|
2022-02-25 20:00:12 +01:00
|
|
|
// we don't want to use API key when getting self
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({ ctx, method: "DELETE" })
|
2022-02-25 20:00:12 +01:00
|
|
|
)
|
2022-11-22 13:41:36 +01:00
|
|
|
return checkResponse(response, "delete user", { ctx })
|
2022-02-25 20:00:12 +01:00
|
|
|
}
|
|
|
|
|
2023-08-22 19:14:08 +02:00
|
|
|
export async function readGlobalUser(ctx: Ctx): Promise<User> {
|
2022-02-25 20:00:12 +01:00
|
|
|
const response = await fetch(
|
2022-02-25 20:01:17 +01:00
|
|
|
checkSlashesInUrl(
|
|
|
|
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`
|
|
|
|
),
|
2022-02-25 20:00:12 +01:00
|
|
|
// we don't want to use API key when getting self
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({ ctx, method: "GET" })
|
2022-02-25 20:00:12 +01:00
|
|
|
)
|
|
|
|
return checkResponse(response, "get user", { ctx })
|
2021-04-09 16:11:49 +02:00
|
|
|
}
|
2022-06-30 12:28:52 +02:00
|
|
|
|
2023-11-21 18:30:11 +01:00
|
|
|
export async function getChecklist(): Promise<{
|
|
|
|
adminUser: { checked: boolean }
|
|
|
|
}> {
|
2022-06-30 12:28:52 +02:00
|
|
|
const response = await fetch(
|
|
|
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/configs/checklist"),
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({ method: "GET" })
|
2022-06-30 12:28:52 +02:00
|
|
|
)
|
|
|
|
return checkResponse(response, "get checklist")
|
|
|
|
}
|
2022-09-05 19:28:53 +02:00
|
|
|
|
2022-11-22 13:41:36 +01:00
|
|
|
export async function generateApiKey(userId: string) {
|
2022-09-05 19:28:53 +02:00
|
|
|
const response = await fetch(
|
|
|
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/self/api_key"),
|
2024-02-15 16:49:30 +01:00
|
|
|
createRequest({ method: "POST", body: { userId } })
|
2022-09-05 19:28:53 +02:00
|
|
|
)
|
|
|
|
return checkResponse(response, "generate API key")
|
|
|
|
}
|