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.
This commit is contained in:
parent
15d0c73eb4
commit
c2eb8fb976
7 changed files with 105 additions and 67 deletions
|
@ -134,7 +134,7 @@
|
||||||
on:blur={onBlur}
|
on:blur={onBlur}
|
||||||
on:focus={onFocus}
|
on:focus={onFocus}
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
{type}
|
type={hbsValue.length ? "text" : type}
|
||||||
style={align ? `text-align: ${align};` : ""}
|
style={align ? `text-align: ${align};` : ""}
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
inputmode={type === "number" ? "decimal" : "text"}
|
inputmode={type === "number" ? "decimal" : "text"}
|
||||||
|
|
|
@ -248,6 +248,7 @@
|
||||||
/>
|
/>
|
||||||
<EnvDropdown
|
<EnvDropdown
|
||||||
label="Password"
|
label="Password"
|
||||||
|
type="password"
|
||||||
bind:value={form.basic.password}
|
bind:value={form.basic.password}
|
||||||
on:change={onFieldChange}
|
on:change={onFieldChange}
|
||||||
on:blur={() => (blurred.basic.password = true)}
|
on:blur={() => (blurred.basic.password = true)}
|
||||||
|
|
|
@ -7,44 +7,3 @@
|
||||||
export interface QueryOptions {
|
export interface QueryOptions {
|
||||||
disableReturning?: boolean
|
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,13 +6,11 @@ import {
|
||||||
IntegrationBase,
|
IntegrationBase,
|
||||||
PaginationValues,
|
PaginationValues,
|
||||||
RestQueryFields as RestQuery,
|
RestQueryFields as RestQuery,
|
||||||
} from "@budibase/types"
|
|
||||||
import {
|
|
||||||
RestConfig,
|
RestConfig,
|
||||||
AuthType,
|
RestAuthType,
|
||||||
BasicAuthConfig,
|
RestBasicAuthConfig,
|
||||||
BearerAuthConfig,
|
RestBearerAuthConfig,
|
||||||
} from "../definitions/datasource"
|
} from "@budibase/types"
|
||||||
import { get } from "lodash"
|
import { get } from "lodash"
|
||||||
import * as https from "https"
|
import * as https from "https"
|
||||||
import qs from "querystring"
|
import qs from "querystring"
|
||||||
|
@ -331,14 +329,14 @@ class RestIntegration implements IntegrationBase {
|
||||||
if (authConfig) {
|
if (authConfig) {
|
||||||
let config
|
let config
|
||||||
switch (authConfig.type) {
|
switch (authConfig.type) {
|
||||||
case AuthType.BASIC:
|
case RestAuthType.BASIC:
|
||||||
config = authConfig.config as BasicAuthConfig
|
config = authConfig.config as RestBasicAuthConfig
|
||||||
headers.Authorization = `Basic ${Buffer.from(
|
headers.Authorization = `Basic ${Buffer.from(
|
||||||
`${config.username}:${config.password}`
|
`${config.username}:${config.password}`
|
||||||
).toString("base64")}`
|
).toString("base64")}`
|
||||||
break
|
break
|
||||||
case AuthType.BEARER:
|
case RestAuthType.BEARER:
|
||||||
config = authConfig.config as BearerAuthConfig
|
config = authConfig.config as RestBearerAuthConfig
|
||||||
headers.Authorization = `Bearer ${config.token}`
|
headers.Authorization = `Bearer ${config.token}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -428,5 +426,4 @@ class RestIntegration implements IntegrationBase {
|
||||||
export default {
|
export default {
|
||||||
schema: SCHEMA,
|
schema: SCHEMA,
|
||||||
integration: RestIntegration,
|
integration: RestIntegration,
|
||||||
AuthType,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ jest.mock("node-fetch", () => {
|
||||||
|
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import { default as RestIntegration } from "../rest"
|
import { default as RestIntegration } from "../rest"
|
||||||
|
import { RestAuthType } from "@budibase/types"
|
||||||
const FormData = require("form-data")
|
const FormData = require("form-data")
|
||||||
const { URLSearchParams } = require("url")
|
const { URLSearchParams } = require("url")
|
||||||
|
|
||||||
|
@ -229,7 +230,7 @@ describe("REST Integration", () => {
|
||||||
const basicAuth = {
|
const basicAuth = {
|
||||||
_id: "c59c14bd1898a43baa08da68959b24686",
|
_id: "c59c14bd1898a43baa08da68959b24686",
|
||||||
name: "basic-1",
|
name: "basic-1",
|
||||||
type: RestIntegration.AuthType.BASIC,
|
type: RestAuthType.BASIC,
|
||||||
config: {
|
config: {
|
||||||
username: "user",
|
username: "user",
|
||||||
password: "password",
|
password: "password",
|
||||||
|
@ -239,7 +240,7 @@ describe("REST Integration", () => {
|
||||||
const bearerAuth = {
|
const bearerAuth = {
|
||||||
_id: "0d91d732f34e4befabeff50b392a8ff3",
|
_id: "0d91d732f34e4befabeff50b392a8ff3",
|
||||||
name: "bearer-1",
|
name: "bearer-1",
|
||||||
type: RestIntegration.AuthType.BEARER,
|
type: RestAuthType.BEARER,
|
||||||
config: {
|
config: {
|
||||||
token: "mytoken",
|
token: "mytoken",
|
||||||
},
|
},
|
||||||
|
@ -581,6 +582,7 @@ describe("REST Integration", () => {
|
||||||
})
|
})
|
||||||
await config.integration.read({})
|
await config.integration.read({})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const calls: any = fetch.mock.calls[0]
|
const calls: any = fetch.mock.calls[0]
|
||||||
const url = calls[0]
|
const url = calls[0]
|
||||||
expect(url).toBe(`${BASE_URL}/`)
|
expect(url).toBe(`${BASE_URL}/`)
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { processObjectSync, findHBSBlocks } from "@budibase/string-templates"
|
import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
|
||||||
import {
|
import {
|
||||||
Datasource,
|
Datasource,
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
Integration,
|
|
||||||
PASSWORD_REPLACEMENT,
|
PASSWORD_REPLACEMENT,
|
||||||
|
RestAuthConfig,
|
||||||
|
RestAuthType,
|
||||||
|
RestBasicAuthConfig,
|
||||||
|
SourceName,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { getEnvironmentVariables } from "../../utils"
|
import { getEnvironmentVariables } from "../../utils"
|
||||||
import { getDefinitions } from "../../../integrations"
|
import { getDefinitions } from "../../../integrations"
|
||||||
|
|
||||||
const ENV_VAR_PREFIX = "env."
|
const ENV_VAR_PREFIX = "env."
|
||||||
const USER_PREFIX = "user"
|
|
||||||
|
|
||||||
async function enrichDatasourceWithValues(datasource: Datasource) {
|
async function enrichDatasourceWithValues(datasource: Datasource) {
|
||||||
const cloned = cloneDeep(datasource)
|
const cloned = cloneDeep(datasource)
|
||||||
|
@ -47,6 +49,18 @@ export async function getWithEnvVars(datasourceId: string) {
|
||||||
return enrichDatasourceWithValues(datasource)
|
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[]) {
|
export async function removeSecrets(datasources: Datasource[]) {
|
||||||
const definitions = await getDefinitions()
|
const definitions = await getDefinitions()
|
||||||
for (let datasource of datasources) {
|
for (let datasource of datasources) {
|
||||||
|
@ -56,17 +70,24 @@ export async function removeSecrets(datasources: Datasource[]) {
|
||||||
if (datasource.config.auth) {
|
if (datasource.config.auth) {
|
||||||
delete datasource.config.auth
|
delete datasource.config.auth
|
||||||
}
|
}
|
||||||
// remove passwords
|
// specific to REST datasources, contains passwords
|
||||||
for (let key of Object.keys(datasource.config)) {
|
if (hasAuthConfigs(datasource)) {
|
||||||
if (typeof datasource.config[key] !== "string") {
|
const configs = datasource.config.authConfigs as RestAuthConfig[]
|
||||||
continue
|
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 =
|
// remove general passwords
|
||||||
blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null
|
for (let key of Object.keys(datasource.config)) {
|
||||||
if (
|
if (
|
||||||
!usesEnvVars &&
|
schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD &&
|
||||||
schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD
|
!useEnvVars(datasource.config[key])
|
||||||
) {
|
) {
|
||||||
datasource.config[key] = PASSWORD_REPLACEMENT
|
datasource.config[key] = PASSWORD_REPLACEMENT
|
||||||
}
|
}
|
||||||
|
@ -84,6 +105,23 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
|
||||||
if (!update.config) {
|
if (!update.config) {
|
||||||
return update
|
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)) {
|
for (let [key, value] of Object.entries(update.config)) {
|
||||||
if (value !== PASSWORD_REPLACEMENT) {
|
if (value !== PASSWORD_REPLACEMENT) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -15,3 +15,44 @@ export interface Datasource extends Document {
|
||||||
[key: string]: Table
|
[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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue