Merge branch 'develop' of github.com:Budibase/budibase into feature/rest-bodies
This commit is contained in:
commit
12b11ed3d9
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
export let showSecondaryButton = false
|
export let showSecondaryButton = false
|
||||||
export let secondaryButtonText = undefined
|
export let secondaryButtonText = undefined
|
||||||
export let secondaryAction = undefined
|
export let secondaryAction = undefined
|
||||||
|
export let secondaryButtonWarning = false
|
||||||
|
|
||||||
const { hide, cancel } = getContext(Context.Modal)
|
const { hide, cancel } = getContext(Context.Modal)
|
||||||
let loading = false
|
let loading = false
|
||||||
|
@ -88,8 +89,11 @@
|
||||||
|
|
||||||
{#if showSecondaryButton && secondaryButtonText && secondaryAction}
|
{#if showSecondaryButton && secondaryButtonText && secondaryAction}
|
||||||
<div class="secondary-action">
|
<div class="secondary-action">
|
||||||
<Button group secondary on:click={secondary}
|
<Button
|
||||||
>{secondaryButtonText}</Button
|
group
|
||||||
|
secondary
|
||||||
|
warning={secondaryButtonWarning}
|
||||||
|
on:click={secondary}>{secondaryButtonText}</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.19-alpha.1",
|
"@budibase/bbui": "^1.0.19-alpha.3",
|
||||||
"@budibase/client": "^1.0.19-alpha.1",
|
"@budibase/client": "^1.0.19-alpha.3",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^1.0.19-alpha.1",
|
"@budibase/string-templates": "^1.0.19-alpha.3",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
import { datasources, integrations, tables } from "stores/backend"
|
import { datasources, integrations, tables } from "stores/backend"
|
||||||
import CreateEditRelationship from "components/backend/Datasources/CreateEditRelationship.svelte"
|
import CreateEditRelationship from "components/backend/Datasources/CreateEditRelationship.svelte"
|
||||||
import CreateExternalTableModal from "./CreateExternalTableModal.svelte"
|
import CreateExternalTableModal from "./CreateExternalTableModal.svelte"
|
||||||
import ArrayRenderer from "components/common/ArrayRenderer.svelte"
|
import ArrayRenderer from "components/common/renderers/ArrayRenderer.svelte"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Divider, Heading, ActionButton, Badge, Body } from "@budibase/bbui"
|
import { Divider, Heading, ActionButton, Badge, Body } from "@budibase/bbui"
|
||||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||||
|
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
|
||||||
|
|
||||||
export let datasource
|
export let datasource
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="query-header">
|
<div class="section-header">
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<Heading size="S">Headers</Heading>
|
<Heading size="S">Headers</Heading>
|
||||||
<Badge quiet grey>Optional</Badge>
|
<Badge quiet grey>Optional</Badge>
|
||||||
|
@ -30,8 +31,20 @@
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Divider size="S" />
|
||||||
|
<div class="section-header">
|
||||||
|
<div class="badge">
|
||||||
|
<Heading size="S">Authentication</Heading>
|
||||||
|
<Badge quiet grey>Optional</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Body size="S">
|
||||||
|
Create an authentication config that can be shared with queries.
|
||||||
|
</Body>
|
||||||
|
<RestAuthenticationBuilder bind:configs={datasource.config.authConfigs} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.query-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
import { AUTH_TYPE_LABELS } from "./authTypes"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const renderAuthType = value => {
|
||||||
|
return AUTH_TYPE_LABELS.filter(type => type.value === value).map(
|
||||||
|
type => type.label
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{renderAuthType(value)}
|
|
@ -0,0 +1,65 @@
|
||||||
|
<script>
|
||||||
|
import { Table, Modal, Layout, ActionButton } from "@budibase/bbui"
|
||||||
|
import AuthTypeRenderer from "./AuthTypeRenderer.svelte"
|
||||||
|
import RestAuthenticationModal from "./RestAuthenticationModal.svelte"
|
||||||
|
import { uuid } from "builderStore/uuid"
|
||||||
|
|
||||||
|
export let configs = []
|
||||||
|
|
||||||
|
let currentConfig = null
|
||||||
|
let modal
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
name: "",
|
||||||
|
type: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
const openConfigModal = config => {
|
||||||
|
currentConfig = config
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirm = config => {
|
||||||
|
if (currentConfig) {
|
||||||
|
configs = configs.map(c => {
|
||||||
|
// replace the current config with the new one
|
||||||
|
if (c._id === currentConfig._id) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
config._id = uuid()
|
||||||
|
configs = [...configs, config]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRemove = () => {
|
||||||
|
configs = configs.filter(c => {
|
||||||
|
return c._id !== currentConfig._id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<RestAuthenticationModal {configs} {currentConfig} {onConfirm} {onRemove} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
|
{#if configs && configs.length > 0}
|
||||||
|
<Table
|
||||||
|
on:click={({ detail }) => openConfigModal(detail)}
|
||||||
|
{schema}
|
||||||
|
data={configs}
|
||||||
|
allowEditColumns={false}
|
||||||
|
allowEditRows={false}
|
||||||
|
allowSelectRows={false}
|
||||||
|
customRenderers={[{ column: "type", component: AuthTypeRenderer }]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<ActionButton on:click={() => openConfigModal()} con="Add"
|
||||||
|
>Add authentication</ActionButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
|
@ -0,0 +1,218 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { ModalContent, Layout, Select, Body, Input } from "@budibase/bbui"
|
||||||
|
import { AUTH_TYPE_LABELS, AUTH_TYPES } from "./authTypes"
|
||||||
|
|
||||||
|
export let configs
|
||||||
|
export let currentConfig
|
||||||
|
export let onConfirm
|
||||||
|
export let onRemove
|
||||||
|
|
||||||
|
let form = {
|
||||||
|
basic: {},
|
||||||
|
bearer: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
let errors = {
|
||||||
|
basic: {},
|
||||||
|
bearer: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
let blurred = {
|
||||||
|
basic: {},
|
||||||
|
bearer: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasErrors = false
|
||||||
|
let hasChanged = false
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (currentConfig) {
|
||||||
|
deconstructConfig()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map the current config's data into the form by type
|
||||||
|
*/
|
||||||
|
const deconstructConfig = () => {
|
||||||
|
form.name = currentConfig.name
|
||||||
|
form.type = currentConfig.type
|
||||||
|
|
||||||
|
if (currentConfig.type === AUTH_TYPES.BASIC) {
|
||||||
|
form.basic = {
|
||||||
|
...currentConfig.config,
|
||||||
|
}
|
||||||
|
} else if (currentConfig.type === AUTH_TYPES.BEARER) {
|
||||||
|
form.bearer = {
|
||||||
|
...currentConfig.config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map the form into a new config to save by type
|
||||||
|
*/
|
||||||
|
const constructConfig = () => {
|
||||||
|
const newConfig = {
|
||||||
|
name: form.name,
|
||||||
|
type: form.type,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentConfig) {
|
||||||
|
newConfig._id = currentConfig._id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.type === AUTH_TYPES.BASIC) {
|
||||||
|
newConfig.config = {
|
||||||
|
...form.basic,
|
||||||
|
}
|
||||||
|
} else if (form.type === AUTH_TYPES.BEARER) {
|
||||||
|
newConfig.config = {
|
||||||
|
...form.bearer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compare the existing config with the new config to see if there are any changes
|
||||||
|
*/
|
||||||
|
const checkChanged = () => {
|
||||||
|
if (currentConfig) {
|
||||||
|
hasChanged =
|
||||||
|
JSON.stringify(currentConfig) !== JSON.stringify(constructConfig())
|
||||||
|
} else {
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkErrors = () => {
|
||||||
|
hasErrors = false
|
||||||
|
|
||||||
|
// NAME
|
||||||
|
const nameError = () => {
|
||||||
|
// Unique name
|
||||||
|
if (form.name) {
|
||||||
|
errors.name =
|
||||||
|
// check for duplicate excluding the current config
|
||||||
|
configs.find(
|
||||||
|
c => c.name === form.name && c.name !== currentConfig?.name
|
||||||
|
) !== undefined
|
||||||
|
? "Name must be unique"
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
// Name required
|
||||||
|
else {
|
||||||
|
errors.name = "Name is required"
|
||||||
|
}
|
||||||
|
return !!errors.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE
|
||||||
|
const typeError = () => {
|
||||||
|
errors.type = form.type ? null : "Type is required"
|
||||||
|
return !!errors.type
|
||||||
|
}
|
||||||
|
|
||||||
|
// BASIC AUTH
|
||||||
|
const basicAuthErrors = () => {
|
||||||
|
errors.basic.username = form.basic.username
|
||||||
|
? null
|
||||||
|
: "Username is required"
|
||||||
|
errors.basic.password = form.basic.password
|
||||||
|
? null
|
||||||
|
: "Password is required"
|
||||||
|
|
||||||
|
return !!(errors.basic.username || errors.basic.password || commonError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BEARER TOKEN
|
||||||
|
const bearerTokenErrors = () => {
|
||||||
|
errors.bearer.token = form.bearer.token ? null : "Token is required"
|
||||||
|
return !!(errors.bearer.token || commonError)
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonError = nameError() || typeError()
|
||||||
|
if (form.type === AUTH_TYPES.BASIC) {
|
||||||
|
hasErrors = basicAuthErrors() || commonError
|
||||||
|
} else if (form.type === AUTH_TYPES.BEARER) {
|
||||||
|
hasErrors = bearerTokenErrors() || commonError
|
||||||
|
} else {
|
||||||
|
hasErrors = !!commonError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFieldChange = () => {
|
||||||
|
checkErrors()
|
||||||
|
checkChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirmInternal = () => {
|
||||||
|
onConfirm(constructConfig())
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title={currentConfig ? "Update Authentication" : "Add Authentication"}
|
||||||
|
onConfirm={onConfirmInternal}
|
||||||
|
confirmText={currentConfig ? "Update" : "Add"}
|
||||||
|
disabled={hasErrors || !hasChanged}
|
||||||
|
cancelText={"Cancel"}
|
||||||
|
size="M"
|
||||||
|
showSecondaryButton={!!currentConfig}
|
||||||
|
secondaryButtonText={"Remove"}
|
||||||
|
secondaryAction={onRemove}
|
||||||
|
secondaryButtonWarning={true}
|
||||||
|
>
|
||||||
|
<Layout gap="S">
|
||||||
|
<Body size="S">
|
||||||
|
The authorization header will be automatically generated when you send the
|
||||||
|
request.
|
||||||
|
</Body>
|
||||||
|
<Input
|
||||||
|
label="Name"
|
||||||
|
bind:value={form.name}
|
||||||
|
on:change={onFieldChange}
|
||||||
|
on:blur={() => (blurred.name = true)}
|
||||||
|
error={blurred.name ? errors.name : null}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Type"
|
||||||
|
bind:value={form.type}
|
||||||
|
on:change={onFieldChange}
|
||||||
|
options={AUTH_TYPE_LABELS}
|
||||||
|
on:blur={() => (blurred.type = true)}
|
||||||
|
error={blurred.type ? errors.type : null}
|
||||||
|
/>
|
||||||
|
{#if form.type === AUTH_TYPES.BASIC}
|
||||||
|
<Input
|
||||||
|
label="Username"
|
||||||
|
bind:value={form.basic.username}
|
||||||
|
on:change={onFieldChange}
|
||||||
|
on:blur={() => (blurred.basic.username = true)}
|
||||||
|
error={blurred.basic.username ? errors.basic.username : null}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Password"
|
||||||
|
bind:value={form.basic.password}
|
||||||
|
on:change={onFieldChange}
|
||||||
|
on:blur={() => (blurred.basic.password = true)}
|
||||||
|
error={blurred.basic.password ? errors.basic.password : null}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if form.type === AUTH_TYPES.BEARER}
|
||||||
|
<Input
|
||||||
|
label="Token"
|
||||||
|
bind:value={form.bearer.token}
|
||||||
|
on:change={onFieldChange}
|
||||||
|
on:blur={() => (blurred.bearer.token = true)}
|
||||||
|
error={blurred.bearer.token ? errors.bearer.token : null}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const AUTH_TYPES = {
|
||||||
|
BASIC: "basic",
|
||||||
|
BEARER: "bearer",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AUTH_TYPE_LABELS = [
|
||||||
|
{
|
||||||
|
label: "Basic Auth",
|
||||||
|
value: AUTH_TYPES.BASIC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Bearer Token",
|
||||||
|
value: AUTH_TYPES.BEARER,
|
||||||
|
},
|
||||||
|
]
|
|
@ -12,33 +12,29 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { datasources, integrations, queries, tables } from "stores/backend"
|
import { datasources, integrations, queries, tables } from "stores/backend"
|
||||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||||
import RestExtraConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/RestExtraConfigForm.svelte"
|
import RestExtraConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/rest/RestExtraConfigForm.svelte"
|
||||||
import PlusConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte"
|
import PlusConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
import VerbRenderer from "./_components/VerbRenderer.svelte"
|
import CapitaliseRenderer from "components/common/renderers/CapitaliseRenderer.svelte"
|
||||||
import { IntegrationTypes } from "constants/backend"
|
import { IntegrationTypes } from "constants/backend"
|
||||||
import { isEqual } from "lodash"
|
import { isEqual } from "lodash"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
|
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
let importQueriesModal
|
let importQueriesModal
|
||||||
|
|
||||||
let baseDatasource, changed
|
let changed
|
||||||
|
let datasource
|
||||||
const querySchema = {
|
const querySchema = {
|
||||||
name: {},
|
name: {},
|
||||||
queryVerb: { displayName: "Method" },
|
queryVerb: { displayName: "Method" },
|
||||||
}
|
}
|
||||||
|
|
||||||
$: datasource = $datasources.list.find(ds => ds._id === $datasources.selected)
|
$: baseDatasource = $datasources.list.find(
|
||||||
|
ds => ds._id === $datasources.selected
|
||||||
|
)
|
||||||
$: integration = datasource && $integrations[datasource.source]
|
$: integration = datasource && $integrations[datasource.source]
|
||||||
$: {
|
|
||||||
if (
|
|
||||||
datasource &&
|
|
||||||
(!baseDatasource || baseDatasource.source !== datasource.source)
|
|
||||||
) {
|
|
||||||
baseDatasource = cloneDeep(datasource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$: queryList = $queries.list.filter(
|
$: queryList = $queries.list.filter(
|
||||||
query => query.datasourceId === datasource?._id
|
query => query.datasourceId === datasource?._id
|
||||||
)
|
)
|
||||||
|
@ -69,6 +65,10 @@
|
||||||
queries.select(query)
|
queries.select(query)
|
||||||
$goto(`./${query._id}`)
|
$goto(`./${query._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
datasource = cloneDeep(baseDatasource)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={importQueriesModal}>
|
<Modal bind:this={importQueriesModal}>
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
height="26"
|
height="26"
|
||||||
width="26"
|
width="26"
|
||||||
/>
|
/>
|
||||||
<Heading size="M">{baseDatasource.name}</Heading>
|
<Heading size="M">{datasource.name}</Heading>
|
||||||
</header>
|
</header>
|
||||||
<Body size="M">{integration.description}</Body>
|
<Body size="M">{integration.description}</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -135,7 +135,9 @@
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
customRenderers={[{ column: "queryVerb", component: VerbRenderer }]}
|
customRenderers={[
|
||||||
|
{ column: "queryVerb", component: CapitaliseRenderer },
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
let saveId, isGet
|
let saveId, isGet
|
||||||
let response, schema, enabledHeaders
|
let response, schema, enabledHeaders
|
||||||
let datasourceType, integrationInfo, queryConfig, responseSuccess
|
let datasourceType, integrationInfo, queryConfig, responseSuccess
|
||||||
|
let authConfigId
|
||||||
|
|
||||||
$: datasourceType = datasource?.source
|
$: datasourceType = datasource?.source
|
||||||
$: integrationInfo = $integrations[datasourceType]
|
$: integrationInfo = $integrations[datasourceType]
|
||||||
|
@ -60,6 +61,7 @@
|
||||||
$: isGet = query?.queryVerb === "read"
|
$: isGet = query?.queryVerb === "read"
|
||||||
$: responseSuccess =
|
$: responseSuccess =
|
||||||
response?.info?.code >= 200 && response?.info?.code <= 206
|
response?.info?.code >= 200 && response?.info?.code <= 206
|
||||||
|
$: authConfigs = buildAuthConfigs(datasource)
|
||||||
|
|
||||||
function getSelectedQuery() {
|
function getSelectedQuery() {
|
||||||
return cloneDeep(
|
return cloneDeep(
|
||||||
|
@ -95,6 +97,16 @@
|
||||||
return qs.length > 0 ? `${newUrl}?${qs}` : newUrl
|
return qs.length > 0 ? `${newUrl}?${qs}` : newUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildAuthConfigs = datasource => {
|
||||||
|
if (datasource?.config?.authConfigs) {
|
||||||
|
return datasource.config.authConfigs.map(c => ({
|
||||||
|
label: c.name,
|
||||||
|
value: c._id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
function learnMoreBanner() {
|
function learnMoreBanner() {
|
||||||
window.open("https://docs.budibase.com/building-apps/data/transformers")
|
window.open("https://docs.budibase.com/building-apps/data/transformers")
|
||||||
}
|
}
|
||||||
|
@ -104,6 +116,7 @@
|
||||||
const queryString = buildQueryString(breakQs)
|
const queryString = buildQueryString(breakQs)
|
||||||
newQuery.fields.path = url.split("?")[0]
|
newQuery.fields.path = url.split("?")[0]
|
||||||
newQuery.fields.queryString = queryString
|
newQuery.fields.queryString = queryString
|
||||||
|
newQuery.fields.authConfigId = authConfigId
|
||||||
newQuery.fields.disabledHeaders = flipHeaderState(enabledHeaders)
|
newQuery.fields.disabledHeaders = flipHeaderState(enabledHeaders)
|
||||||
newQuery.schema = fieldsToSchema(schema)
|
newQuery.schema = fieldsToSchema(schema)
|
||||||
newQuery.parameters = keyValueToQueryParameters(bindings)
|
newQuery.parameters = keyValueToQueryParameters(bindings)
|
||||||
|
@ -137,8 +150,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
const getAuthConfigId = () => {
|
||||||
|
let id = query.fields.authConfigId
|
||||||
|
if (id) {
|
||||||
|
// find the matching config on the datasource
|
||||||
|
const matchedConfig = datasource?.config?.authConfigs?.filter(
|
||||||
|
c => c._id === id
|
||||||
|
)[0]
|
||||||
|
// clear the id if the config is not found (deleted)
|
||||||
|
// i.e. just show 'None' in the dropdown
|
||||||
|
if (!matchedConfig) {
|
||||||
|
id = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
query = getSelectedQuery()
|
query = getSelectedQuery()
|
||||||
|
// clear any unsaved changes to the datasource
|
||||||
|
await datasources.init()
|
||||||
datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
|
datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
|
||||||
const datasourceUrl = datasource?.config.url
|
const datasourceUrl = datasource?.config.url
|
||||||
const qs = query?.fields.queryString
|
const qs = query?.fields.queryString
|
||||||
|
@ -150,6 +181,7 @@
|
||||||
url = buildUrl(query.fields.path, breakQs)
|
url = buildUrl(query.fields.path, breakQs)
|
||||||
schema = schemaToFields(query.schema)
|
schema = schemaToFields(query.schema)
|
||||||
bindings = queryParametersToKeyValue(query.parameters)
|
bindings = queryParametersToKeyValue(query.parameters)
|
||||||
|
authConfigId = getAuthConfigId()
|
||||||
if (!query.fields.disabledHeaders) {
|
if (!query.fields.disabledHeaders) {
|
||||||
query.fields.disabledHeaders = {}
|
query.fields.disabledHeaders = {}
|
||||||
}
|
}
|
||||||
|
@ -259,6 +291,19 @@
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<div class="auth-container">
|
||||||
|
<div />
|
||||||
|
<!-- spacer -->
|
||||||
|
<div class="auth-select">
|
||||||
|
<Select
|
||||||
|
label="Auth"
|
||||||
|
labelPosition="left"
|
||||||
|
placeholder="None"
|
||||||
|
bind:value={authConfigId}
|
||||||
|
options={authConfigs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -405,4 +450,12 @@
|
||||||
margin-top: var(--spacing-xl);
|
margin-top: var(--spacing-xl);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.auth-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.auth-select {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.19-alpha.1",
|
"@budibase/bbui": "^1.0.19-alpha.3",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^1.0.19-alpha.1",
|
"@budibase/string-templates": "^1.0.19-alpha.3",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.0.3",
|
"@apidevtools/swagger-parser": "^10.0.3",
|
||||||
"@budibase/auth": "^1.0.19-alpha.1",
|
"@budibase/auth": "^1.0.19-alpha.3",
|
||||||
"@budibase/client": "^1.0.19-alpha.1",
|
"@budibase/client": "^1.0.19-alpha.3",
|
||||||
"@budibase/string-templates": "^1.0.19-alpha.1",
|
"@budibase/string-templates": "^1.0.19-alpha.3",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -196,6 +196,27 @@ export interface Datasource extends Base {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 QueryParameter {
|
export interface QueryParameter {
|
||||||
name: string
|
name: string
|
||||||
default: string
|
default: string
|
||||||
|
@ -210,6 +231,7 @@ export interface RestQueryFields {
|
||||||
bodyType: string
|
bodyType: string
|
||||||
json: object
|
json: object
|
||||||
method: string
|
method: string
|
||||||
|
authConfigId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RestConfig {
|
export interface RestConfig {
|
||||||
|
@ -217,6 +239,7 @@ export interface RestConfig {
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
authConfigs: AuthConfig[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import {
|
||||||
QueryTypes,
|
QueryTypes,
|
||||||
RestConfig,
|
RestConfig,
|
||||||
RestQueryFields as RestQuery,
|
RestQueryFields as RestQuery,
|
||||||
|
AuthType,
|
||||||
|
BasicAuthConfig,
|
||||||
|
BearerAuthConfig,
|
||||||
} from "../definitions/datasource"
|
} from "../definitions/datasource"
|
||||||
import { IntegrationBase } from "./base/IntegrationBase"
|
import { IntegrationBase } from "./base/IntegrationBase"
|
||||||
|
|
||||||
|
@ -116,9 +119,17 @@ module RestModule {
|
||||||
if (contentType.includes("application/json")) {
|
if (contentType.includes("application/json")) {
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
raw = JSON.stringify(data)
|
raw = JSON.stringify(data)
|
||||||
} else if (contentType.includes("text/xml") || contentType.includes("application/xml")) {
|
} else if (
|
||||||
|
contentType.includes("text/xml") ||
|
||||||
|
contentType.includes("application/xml")
|
||||||
|
) {
|
||||||
const rawXml = await response.text()
|
const rawXml = await response.text()
|
||||||
data = await xmlParser(rawXml, { explicitArray: false, trim: true, explicitRoot: false }) || {}
|
data =
|
||||||
|
(await xmlParser(rawXml, {
|
||||||
|
explicitArray: false,
|
||||||
|
trim: true,
|
||||||
|
explicitRoot: false,
|
||||||
|
})) || {}
|
||||||
// there is only one structure, its an array, return the array so it appears as rows
|
// there is only one structure, its an array, return the array so it appears as rows
|
||||||
const keys = Object.keys(data)
|
const keys = Object.keys(data)
|
||||||
if (keys.length === 1 && Array.isArray(data[keys[0]])) {
|
if (keys.length === 1 && Array.isArray(data[keys[0]])) {
|
||||||
|
@ -210,6 +221,35 @@ module RestModule {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAuthHeaders(authConfigId: string): { [key: string]: any } {
|
||||||
|
let headers: any = {}
|
||||||
|
|
||||||
|
if (this.config.authConfigs && authConfigId) {
|
||||||
|
const authConfig = this.config.authConfigs.filter(
|
||||||
|
c => c._id === authConfigId
|
||||||
|
)[0]
|
||||||
|
// check the config still exists before proceeding
|
||||||
|
// if not - do nothing
|
||||||
|
if (authConfig) {
|
||||||
|
let config
|
||||||
|
switch (authConfig.type) {
|
||||||
|
case AuthType.BASIC:
|
||||||
|
config = authConfig.config as BasicAuthConfig
|
||||||
|
headers.Authorization = `Basic ${Buffer.from(
|
||||||
|
`${config.username}:${config.password}`
|
||||||
|
).toString("base64")}`
|
||||||
|
break
|
||||||
|
case AuthType.BEARER:
|
||||||
|
config = authConfig.config as BearerAuthConfig
|
||||||
|
headers.Authorization = `Bearer ${config.token}`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
async _req(query: RestQuery) {
|
async _req(query: RestQuery) {
|
||||||
const {
|
const {
|
||||||
path = "",
|
path = "",
|
||||||
|
@ -219,10 +259,14 @@ module RestModule {
|
||||||
disabledHeaders,
|
disabledHeaders,
|
||||||
bodyType,
|
bodyType,
|
||||||
requestBody,
|
requestBody,
|
||||||
|
authConfigId,
|
||||||
} = query
|
} = query
|
||||||
|
const authHeaders = this.getAuthHeaders(authConfigId)
|
||||||
|
|
||||||
this.headers = {
|
this.headers = {
|
||||||
...this.config.defaultHeaders,
|
...this.config.defaultHeaders,
|
||||||
...headers,
|
...headers,
|
||||||
|
...authHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disabledHeaders) {
|
if (disabledHeaders) {
|
||||||
|
@ -239,7 +283,8 @@ module RestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startTimeMs = performance.now()
|
this.startTimeMs = performance.now()
|
||||||
const response = await fetch(this.getUrl(path, queryString), input)
|
const url = this.getUrl(path, queryString)
|
||||||
|
const response = await fetch(url, input)
|
||||||
return await this.parseResponse(response)
|
return await this.parseResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,5 +312,6 @@ module RestModule {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
schema: SCHEMA,
|
schema: SCHEMA,
|
||||||
integration: RestIntegration,
|
integration: RestIntegration,
|
||||||
|
AuthType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ jest.mock("node-fetch", () =>
|
||||||
)
|
)
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const RestIntegration = require("../rest")
|
const RestIntegration = require("../rest")
|
||||||
|
const { AuthType } = require("../rest")
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
constructor(config = {}) {
|
constructor(config = {}) {
|
||||||
|
@ -112,4 +113,58 @@ describe("REST Integration", () => {
|
||||||
body: '{"name":"test"}',
|
body: '{"name":"test"}',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("authentication", () => {
|
||||||
|
const basicAuth = {
|
||||||
|
_id: "c59c14bd1898a43baa08da68959b24686",
|
||||||
|
name: "basic-1",
|
||||||
|
type : AuthType.BASIC,
|
||||||
|
config : {
|
||||||
|
username: "user",
|
||||||
|
password: "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bearerAuth = {
|
||||||
|
_id: "0d91d732f34e4befabeff50b392a8ff3",
|
||||||
|
name: "bearer-1",
|
||||||
|
type : AuthType.BEARER,
|
||||||
|
config : {
|
||||||
|
"token": "mytoken"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config = new TestConfiguration({
|
||||||
|
url: BASE_URL,
|
||||||
|
authConfigs : [basicAuth, bearerAuth]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("adds basic auth", async () => {
|
||||||
|
const query = {
|
||||||
|
authConfigId: "c59c14bd1898a43baa08da68959b24686"
|
||||||
|
}
|
||||||
|
await config.integration.read(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Basic dXNlcjpwYXNzd29yZA=="
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("adds bearer auth", async () => {
|
||||||
|
const query = {
|
||||||
|
authConfigId: "0d91d732f34e4befabeff50b392a8ff3"
|
||||||
|
}
|
||||||
|
await config.integration.read(query)
|
||||||
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/?`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer mytoken"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.19-alpha.1",
|
"version": "1.0.19-alpha.3",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^1.0.19-alpha.1",
|
"@budibase/auth": "^1.0.19-alpha.3",
|
||||||
"@budibase/string-templates": "^1.0.19-alpha.1",
|
"@budibase/string-templates": "^1.0.19-alpha.3",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "^6.0.0",
|
"@sentry/node": "^6.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
Loading…
Reference in New Issue