Merge pull request #14411 from Budibase/poc-authenticated-iframe

Poc authenticated iframe
This commit is contained in:
Martin McKeaveney 2024-09-02 09:08:56 +01:00 committed by GitHub
commit cbf6891c5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 169 additions and 101 deletions

View File

@ -57,6 +57,7 @@ export const getBindableProperties = (asset, componentId) => {
const stateBindings = getStateBindings() const stateBindings = getStateBindings()
const selectedRowsBindings = getSelectedRowsBindings(asset) const selectedRowsBindings = getSelectedRowsBindings(asset)
const roleBindings = getRoleBindings() const roleBindings = getRoleBindings()
const embedBindings = getEmbedBindings()
return [ return [
...contextBindings, ...contextBindings,
...urlBindings, ...urlBindings,
@ -65,6 +66,7 @@ export const getBindableProperties = (asset, componentId) => {
...deviceBindings, ...deviceBindings,
...selectedRowsBindings, ...selectedRowsBindings,
...roleBindings, ...roleBindings,
...embedBindings,
] ]
} }
@ -813,6 +815,25 @@ export const getActionBindings = (actions, actionId) => {
return bindings return bindings
} }
/**
* Gets all device bindings for embeds.
*/
const getEmbedBindings = () => {
let bindings = []
const safeEmbed = makePropSafe("embed")
bindings = [
{
type: "context",
runtimeBinding: `${safeEmbed}`,
readableBinding: `ParentWindow`,
category: "Embed",
icon: "DistributeVertically",
},
]
return bindings
}
/** /**
* Gets the schema for a certain datasource plus. * Gets the schema for a certain datasource plus.
* The options which can be passed in are: * The options which can be passed in are:

View File

@ -42,6 +42,7 @@
import FreeFooter from "components/FreeFooter.svelte" import FreeFooter from "components/FreeFooter.svelte"
import MaintenanceScreen from "components/MaintenanceScreen.svelte" import MaintenanceScreen from "components/MaintenanceScreen.svelte"
import SnippetsProvider from "./context/SnippetsProvider.svelte" import SnippetsProvider from "./context/SnippetsProvider.svelte"
import EmbedProvider from "./context/EmbedProvider.svelte"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -160,116 +161,119 @@
{#if $environmentStore.maintenance.length > 0} {#if $environmentStore.maintenance.length > 0}
<MaintenanceScreen maintenanceList={$environmentStore.maintenance} /> <MaintenanceScreen maintenanceList={$environmentStore.maintenance} />
{:else} {:else}
<DeviceBindingsProvider> <EmbedProvider>
<UserBindingsProvider> <DeviceBindingsProvider>
<StateBindingsProvider> <UserBindingsProvider>
<RowSelectionProvider> <StateBindingsProvider>
<QueryParamsProvider> <RowSelectionProvider>
<SnippetsProvider> <QueryParamsProvider>
<!-- Settings bar can be rendered outside of device preview --> <SnippetsProvider>
<!-- Key block needs to be outside the if statement or it breaks --> <!-- Settings bar can be rendered outside of device preview -->
{#key $builderStore.selectedComponentId} <!-- Key block needs to be outside the if statement or it breaks -->
{#if $builderStore.inBuilder} {#key $builderStore.selectedComponentId}
<SettingsBar /> {#if $builderStore.inBuilder}
{/if} <SettingsBar />
{/key}
<!-- Clip boundary for selection indicators -->
<div
id="clip-root"
class:preview={$builderStore.inBuilder}
class:tablet-preview={$builderStore.previewDevice ===
"tablet"}
class:mobile-preview={$builderStore.previewDevice ===
"mobile"}
>
<!-- Actual app -->
<div id="app-root">
{#if showDevTools}
<DevToolsHeader />
{/if} {/if}
{/key}
<div id="app-body"> <!-- Clip boundary for selection indicators -->
{#if permissionError} <div
<div class="error"> id="clip-root"
<Layout justifyItems="center" gap="S"> class:preview={$builderStore.inBuilder}
<!-- eslint-disable-next-line svelte/no-at-html-tags --> class:tablet-preview={$builderStore.previewDevice ===
{@html ErrorSVG} "tablet"}
<Heading size="L"> class:mobile-preview={$builderStore.previewDevice ===
You don't have permission to use this app "mobile"}
</Heading> >
<Body size="S"> <!-- Actual app -->
Ask your administrator to grant you access <div id="app-root">
</Body> {#if showDevTools}
</Layout> <DevToolsHeader />
</div>
{:else if !$screenStore.activeLayout}
<div class="error">
<Layout justifyItems="center" gap="S">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html ErrorSVG}
<Heading size="L">
Something went wrong rendering your app
</Heading>
<Body size="S">
Get in touch with support if this issue persists
</Body>
</Layout>
</div>
{:else if embedNoScreens}
<div class="error">
<Layout justifyItems="center" gap="S">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html ErrorSVG}
<Heading size="L">
This Budibase app is not publicly accessible
</Heading>
</Layout>
</div>
{:else}
<CustomThemeWrapper>
{#key $screenStore.activeLayout._id}
<Component
isLayout
instance={$screenStore.activeLayout.props}
/>
{/key}
<!-- Layers on top of app -->
<NotificationDisplay />
<ConfirmationDisplay />
<PeekScreenDisplay />
</CustomThemeWrapper>
{/if} {/if}
{#if showDevTools} <div id="app-body">
<DevTools /> {#if permissionError}
<div class="error">
<Layout justifyItems="center" gap="S">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html ErrorSVG}
<Heading size="L">
You don't have permission to use this app
</Heading>
<Body size="S">
Ask your administrator to grant you access
</Body>
</Layout>
</div>
{:else if !$screenStore.activeLayout}
<div class="error">
<Layout justifyItems="center" gap="S">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html ErrorSVG}
<Heading size="L">
Something went wrong rendering your app
</Heading>
<Body size="S">
Get in touch with support if this issue
persists
</Body>
</Layout>
</div>
{:else if embedNoScreens}
<div class="error">
<Layout justifyItems="center" gap="S">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html ErrorSVG}
<Heading size="L">
This Budibase app is not publicly accessible
</Heading>
</Layout>
</div>
{:else}
<CustomThemeWrapper>
{#key $screenStore.activeLayout._id}
<Component
isLayout
instance={$screenStore.activeLayout.props}
/>
{/key}
<!-- Layers on top of app -->
<NotificationDisplay />
<ConfirmationDisplay />
<PeekScreenDisplay />
</CustomThemeWrapper>
{/if}
{#if showDevTools}
<DevTools />
{/if}
</div>
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled}
<FreeFooter />
{/if} {/if}
</div> </div>
{#if !$builderStore.inBuilder && $featuresStore.logoEnabled} <!-- Preview and dev tools utilities -->
<FreeFooter /> {#if $appStore.isDevApp}
<SelectionIndicator />
{/if}
{#if $builderStore.inBuilder || $devToolsStore.allowSelection}
<HoverIndicator />
{/if}
{#if $builderStore.inBuilder}
<DNDHandler />
<GridDNDHandler />
{/if} {/if}
</div> </div>
</SnippetsProvider>
<!-- Preview and dev tools utilities --> </QueryParamsProvider>
{#if $appStore.isDevApp} </RowSelectionProvider>
<SelectionIndicator /> </StateBindingsProvider>
{/if} </UserBindingsProvider>
{#if $builderStore.inBuilder || $devToolsStore.allowSelection} </DeviceBindingsProvider>
<HoverIndicator /> </EmbedProvider>
{/if}
{#if $builderStore.inBuilder}
<DNDHandler />
<GridDNDHandler />
{/if}
</div>
</SnippetsProvider>
</QueryParamsProvider>
</RowSelectionProvider>
</StateBindingsProvider>
</UserBindingsProvider>
</DeviceBindingsProvider>
{/if} {/if}
</div> </div>
<KeyboardManager /> <KeyboardManager />

View File

@ -0,0 +1,43 @@
<script>
import Provider from "./Provider.svelte"
import { onMount } from "svelte"
let data = {}
function extractDomainFromUrl(url) {
const { hostname } = new URL(url)
const parts = hostname.split(".")
const tld = parts.slice(-2).join(".")
return tld
}
function handleMessage(event) {
if (event.data?.type !== "bb-parent-window-event") {
return
}
// Validate the event origin to ensure it's coming from a trusted source
// Allow different subdomains but must match TLD
const appOrigin = extractDomainFromUrl(window.location.origin)
const eventOrigin = extractDomainFromUrl(event.origin)
if (appOrigin === eventOrigin) {
data = event.data
} else {
console.error(
`Embedded budibase app domain ${appOrigin} does not match origin of event ${eventOrigin}.
Top level domains must match`
)
}
}
onMount(() => {
window.addEventListener("message", handleMessage)
return () => window.removeEventListener("message", handleMessage)
})
</script>
<Provider key="embed" {data}>
<slot />
</Provider>