From c2eb8fb97633dc5df6a947bb55535053f49faaa9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 30 Jan 2023 18:50:35 +0000 Subject: [PATCH] Update to cover the authentication for REST, replacing it with secret value and converting to password field, as well as minor update to env dropdown to only be a password field when not containing an environment variable - #9480. --- .../bbui/src/Form/Core/EnvDropdown.svelte | 2 +- .../rest/auth/RestAuthenticationModal.svelte | 1 + packages/server/src/definitions/datasource.ts | 41 ------------ packages/server/src/integrations/rest.ts | 19 +++--- .../src/integrations/tests/rest.spec.ts | 6 +- .../src/sdk/app/datasources/datasources.ts | 62 +++++++++++++++---- .../types/src/documents/app/datasource.ts | 41 ++++++++++++ 7 files changed, 105 insertions(+), 67 deletions(-) diff --git a/packages/bbui/src/Form/Core/EnvDropdown.svelte b/packages/bbui/src/Form/Core/EnvDropdown.svelte index f51704248b..f40d6556e5 100644 --- a/packages/bbui/src/Form/Core/EnvDropdown.svelte +++ b/packages/bbui/src/Form/Core/EnvDropdown.svelte @@ -134,7 +134,7 @@ on:blur={onBlur} on:focus={onFocus} on:input={onInput} - {type} + type={hbsValue.length ? "text" : type} style={align ? `text-align: ${align};` : ""} class="spectrum-Textfield-input" inputmode={type === "number" ? "decimal" : "text"} diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte index 80c5a18ade..109217a8d2 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte @@ -248,6 +248,7 @@ /> (blurred.basic.password = true)} diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index 1ef5206fff..1e436670a8 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -7,44 +7,3 @@ export interface QueryOptions { disableReturning?: boolean } - -export enum AuthType { - BASIC = "basic", - BEARER = "bearer", -} - -interface AuthConfig { - _id: string - name: string - type: AuthType - config: BasicAuthConfig | BearerAuthConfig -} - -export interface BasicAuthConfig { - username: string - password: string -} - -export interface BearerAuthConfig { - token: string -} - -export interface RestConfig { - url: string - rejectUnauthorized: boolean - defaultHeaders: { - [key: string]: any - } - legacyHttpParser: boolean - authConfigs: AuthConfig[] - staticVariables: { - [key: string]: string - } - dynamicVariables: [ - { - name: string - queryId: string - value: string - } - ] -} diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts index f13c14e504..c24636a5fd 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -6,13 +6,11 @@ import { IntegrationBase, PaginationValues, RestQueryFields as RestQuery, -} from "@budibase/types" -import { RestConfig, - AuthType, - BasicAuthConfig, - BearerAuthConfig, -} from "../definitions/datasource" + RestAuthType, + RestBasicAuthConfig, + RestBearerAuthConfig, +} from "@budibase/types" import { get } from "lodash" import * as https from "https" import qs from "querystring" @@ -331,14 +329,14 @@ class RestIntegration implements IntegrationBase { if (authConfig) { let config switch (authConfig.type) { - case AuthType.BASIC: - config = authConfig.config as BasicAuthConfig + case RestAuthType.BASIC: + config = authConfig.config as RestBasicAuthConfig headers.Authorization = `Basic ${Buffer.from( `${config.username}:${config.password}` ).toString("base64")}` break - case AuthType.BEARER: - config = authConfig.config as BearerAuthConfig + case RestAuthType.BEARER: + config = authConfig.config as RestBearerAuthConfig headers.Authorization = `Bearer ${config.token}` break } @@ -428,5 +426,4 @@ class RestIntegration implements IntegrationBase { export default { schema: SCHEMA, integration: RestIntegration, - AuthType, } diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts index d7bd2965fd..3295405ce2 100644 --- a/packages/server/src/integrations/tests/rest.spec.ts +++ b/packages/server/src/integrations/tests/rest.spec.ts @@ -15,6 +15,7 @@ jest.mock("node-fetch", () => { import fetch from "node-fetch" import { default as RestIntegration } from "../rest" +import { RestAuthType } from "@budibase/types" const FormData = require("form-data") const { URLSearchParams } = require("url") @@ -229,7 +230,7 @@ describe("REST Integration", () => { const basicAuth = { _id: "c59c14bd1898a43baa08da68959b24686", name: "basic-1", - type: RestIntegration.AuthType.BASIC, + type: RestAuthType.BASIC, config: { username: "user", password: "password", @@ -239,7 +240,7 @@ describe("REST Integration", () => { const bearerAuth = { _id: "0d91d732f34e4befabeff50b392a8ff3", name: "bearer-1", - type: RestIntegration.AuthType.BEARER, + type: RestAuthType.BEARER, config: { token: "mytoken", }, @@ -581,6 +582,7 @@ describe("REST Integration", () => { }) await config.integration.read({}) + // @ts-ignore const calls: any = fetch.mock.calls[0] const url = calls[0] expect(url).toBe(`${BASE_URL}/`) diff --git a/packages/server/src/sdk/app/datasources/datasources.ts b/packages/server/src/sdk/app/datasources/datasources.ts index 7a324b86f9..dfcde96cfc 100644 --- a/packages/server/src/sdk/app/datasources/datasources.ts +++ b/packages/server/src/sdk/app/datasources/datasources.ts @@ -1,17 +1,19 @@ import { context } from "@budibase/backend-core" -import { processObjectSync, findHBSBlocks } from "@budibase/string-templates" +import { findHBSBlocks, processObjectSync } from "@budibase/string-templates" import { Datasource, DatasourceFieldType, - Integration, PASSWORD_REPLACEMENT, + RestAuthConfig, + RestAuthType, + RestBasicAuthConfig, + SourceName, } from "@budibase/types" import { cloneDeep } from "lodash/fp" import { getEnvironmentVariables } from "../../utils" import { getDefinitions } from "../../../integrations" const ENV_VAR_PREFIX = "env." -const USER_PREFIX = "user" async function enrichDatasourceWithValues(datasource: Datasource) { const cloned = cloneDeep(datasource) @@ -47,6 +49,18 @@ export async function getWithEnvVars(datasourceId: string) { return enrichDatasourceWithValues(datasource) } +function hasAuthConfigs(datasource: Datasource) { + return datasource.source === SourceName.REST && datasource.config?.authConfigs +} + +function useEnvVars(str: any) { + if (typeof str !== "string") { + return false + } + const blocks = findHBSBlocks(str) + return blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null +} + export async function removeSecrets(datasources: Datasource[]) { const definitions = await getDefinitions() for (let datasource of datasources) { @@ -56,17 +70,24 @@ export async function removeSecrets(datasources: Datasource[]) { if (datasource.config.auth) { delete datasource.config.auth } - // remove passwords - for (let key of Object.keys(datasource.config)) { - if (typeof datasource.config[key] !== "string") { - continue + // specific to REST datasources, contains passwords + if (hasAuthConfigs(datasource)) { + const configs = datasource.config.authConfigs as RestAuthConfig[] + for (let config of configs) { + if (config.type !== RestAuthType.BASIC) { + continue + } + const basic = config.config as RestBasicAuthConfig + if (!useEnvVars(basic.password)) { + basic.password = PASSWORD_REPLACEMENT + } } - const blocks = findHBSBlocks(datasource.config[key] as string) - const usesEnvVars = - blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null + } + // remove general passwords + for (let key of Object.keys(datasource.config)) { if ( - !usesEnvVars && - schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD + schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD && + !useEnvVars(datasource.config[key]) ) { datasource.config[key] = PASSWORD_REPLACEMENT } @@ -84,6 +105,23 @@ export function mergeConfigs(update: Datasource, old: Datasource) { if (!update.config) { return update } + // specific to REST datasources, fix the auth configs again if required + if (hasAuthConfigs(update)) { + const configs = update.config.authConfigs as RestAuthConfig[] + const oldConfigs = old.config?.authConfigs as RestAuthConfig[] + for (let config of configs) { + if (config.type !== RestAuthType.BASIC) { + continue + } + const basic = config.config as RestBasicAuthConfig + const oldBasic = oldConfigs.find(old => old.name === config.name) + ?.config as RestBasicAuthConfig + if (basic.password === PASSWORD_REPLACEMENT) { + basic.password = oldBasic.password + } + } + } + // update back to actual passwords for everything else for (let [key, value] of Object.entries(update.config)) { if (value !== PASSWORD_REPLACEMENT) { continue diff --git a/packages/types/src/documents/app/datasource.ts b/packages/types/src/documents/app/datasource.ts index a37e3d3ddc..8dfdfe6d0f 100644 --- a/packages/types/src/documents/app/datasource.ts +++ b/packages/types/src/documents/app/datasource.ts @@ -15,3 +15,44 @@ export interface Datasource extends Document { [key: string]: Table } } + +export enum RestAuthType { + BASIC = "basic", + BEARER = "bearer", +} + +export interface RestBasicAuthConfig { + username: string + password: string +} + +export interface RestBearerAuthConfig { + token: string +} + +export interface RestAuthConfig { + _id: string + name: string + type: RestAuthType + config: RestBasicAuthConfig | RestBearerAuthConfig +} + +export interface RestConfig { + url: string + rejectUnauthorized: boolean + defaultHeaders: { + [key: string]: any + } + legacyHttpParser: boolean + authConfigs: RestAuthConfig[] + staticVariables: { + [key: string]: string + } + dynamicVariables: [ + { + name: string + queryId: string + value: string + } + ] +}