Merge pull request #10894 from Budibase/budi-7010/frontend-encrypt-app-imports

BUDI-7010 - Frontend decrypt app imports
This commit is contained in:
Adria Navarro 2023-06-19 11:18:38 +01:00 committed by GitHub
commit a0bfebf53d
2 changed files with 144 additions and 59 deletions

View File

@ -1,6 +1,12 @@
<script> <script>
import { writable, get as svelteGet } from "svelte/store" import { writable, get as svelteGet } from "svelte/store"
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui" import {
notifications,
Input,
ModalContent,
Dropzone,
Modal,
} from "@budibase/bbui"
import { store, automationStore } from "builderStore" import { store, automationStore } from "builderStore"
import { API } from "api" import { API } from "api"
import { apps, admin, auth } from "stores/portal" import { apps, admin, auth } from "stores/portal"
@ -11,6 +17,7 @@
import TemplateCard from "components/common/TemplateCard.svelte" import TemplateCard from "components/common/TemplateCard.svelte"
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen" import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
import { Roles } from "constants/backend" import { Roles } from "constants/backend"
import { lowercase } from "helpers"
export let template export let template
@ -19,6 +26,7 @@
const values = writable({ name: "", url: null }) const values = writable({ name: "", url: null })
const validation = createValidationStore() const validation = createValidationStore()
const encryptionValidation = createValidationStore()
$: { $: {
const { url } = $values const { url } = $values
@ -27,8 +35,11 @@
...$values, ...$values,
url: url?.[0] === "/" ? url.substring(1, url.length) : url, url: url?.[0] === "/" ? url.substring(1, url.length) : url,
}) })
encryptionValidation.check({ ...$values })
} }
$: encryptedFile = $values.file?.name?.endsWith(".enc.tar.gz")
onMount(async () => { onMount(async () => {
const lastChar = $auth.user?.firstName const lastChar = $auth.user?.firstName
? $auth.user?.firstName[$auth.user?.firstName.length - 1] ? $auth.user?.firstName[$auth.user?.firstName.length - 1]
@ -87,6 +98,9 @@
appValidation.name(validation, { apps: applications }) appValidation.name(validation, { apps: applications })
appValidation.url(validation, { apps: applications }) appValidation.url(validation, { apps: applications })
appValidation.file(validation, { template }) appValidation.file(validation, { template })
encryptionValidation.addValidatorType("encryptionPassword", "text", true)
// init validation // init validation
const { url } = $values const { url } = $values
validation.check({ validation.check({
@ -110,6 +124,9 @@
data.append("templateName", template.name) data.append("templateName", template.name)
data.append("templateKey", template.key) data.append("templateKey", template.key)
data.append("templateFile", $values.file) data.append("templateFile", $values.file)
if ($values.encryptionPassword?.trim()) {
data.append("encryptionPassword", $values.encryptionPassword.trim())
}
} }
// Create App // Create App
@ -143,67 +160,119 @@
$goto(`/builder/app/${createdApp.instance._id}`) $goto(`/builder/app/${createdApp.instance._id}`)
} catch (error) { } catch (error) {
creating = false creating = false
console.error(error) throw error
notifications.error("Error creating app")
} }
} }
const Step = { CONFIG: "config", SET_PASSWORD: "set_password" }
let currentStep = Step.CONFIG
$: stepConfig = {
[Step.CONFIG]: {
title: "Create your app",
confirmText: template?.fromFile ? "Import app" : "Create app",
onConfirm: async () => {
if (encryptedFile) {
currentStep = Step.SET_PASSWORD
return false
} else {
try {
await createNewApp()
} catch (error) {
notifications.error("Error creating app")
}
}
},
isValid: $validation.valid,
},
[Step.SET_PASSWORD]: {
title: "Provide the export password",
confirmText: "Import app",
onConfirm: async () => {
try {
await createNewApp()
} catch (e) {
let message = "Error creating app"
if (e.message) {
message += `: ${lowercase(e.message)}`
}
notifications.error(message)
return false
}
},
isValid: $encryptionValidation.valid,
},
}
</script> </script>
<ModalContent <ModalContent
title={"Create your app"} title={stepConfig[currentStep].title}
confirmText={template?.fromFile ? "Import app" : "Create app"} confirmText={stepConfig[currentStep].confirmText}
onConfirm={createNewApp} onConfirm={stepConfig[currentStep].onConfirm}
disabled={!$validation.valid} disabled={!stepConfig[currentStep].isValid}
> >
{#if template && !template?.fromFile} {#if currentStep === Step.CONFIG}
<TemplateCard {#if template && !template?.fromFile}
name={template.name} <TemplateCard
imageSrc={template.image} name={template.name}
backgroundColour={template.background} imageSrc={template.image}
overlayEnabled={false} backgroundColour={template.background}
icon={template.icon} overlayEnabled={false}
/> icon={template.icon}
{/if} />
{#if template?.fromFile}
<Dropzone
error={$validation.touched.file && $validation.errors.file}
gallery={false}
label="File to import"
value={[$values.file]}
on:change={e => {
$values.file = e.detail?.[0]
$validation.touched.file = true
}}
/>
{/if}
<Input
autofocus={true}
bind:value={$values.name}
disabled={creating}
error={$validation.touched.name && $validation.errors.name}
on:blur={() => ($validation.touched.name = true)}
on:change={nameToUrl($values.name)}
label="Name"
placeholder={defaultAppName}
/>
<span>
<Input
bind:value={$values.url}
disabled={creating}
error={$validation.touched.url && $validation.errors.url}
on:blur={() => ($validation.touched.url = true)}
on:change={tidyUrl($values.url)}
label="URL"
placeholder={$values.url
? $values.url
: `/${resolveAppUrl(template, $values.name)}`}
/>
{#if $values.url && $values.url !== "" && !$validation.errors.url}
<div class="app-server" title={appUrl}>
{appUrl}
</div>
{/if} {/if}
</span> {#if template?.fromFile}
<Dropzone
error={$validation.touched.file && $validation.errors.file}
gallery={false}
label="File to import"
value={[$values.file]}
on:change={e => {
$values.file = e.detail?.[0]
$validation.touched.file = true
}}
/>
{/if}
<Input
autofocus={true}
bind:value={$values.name}
disabled={creating}
error={$validation.touched.name && $validation.errors.name}
on:blur={() => ($validation.touched.name = true)}
on:change={nameToUrl($values.name)}
label="Name"
placeholder={defaultAppName}
/>
<span>
<Input
bind:value={$values.url}
disabled={creating}
error={$validation.touched.url && $validation.errors.url}
on:blur={() => ($validation.touched.url = true)}
on:change={tidyUrl($values.url)}
label="URL"
placeholder={$values.url
? $values.url
: `/${resolveAppUrl(template, $values.name)}`}
/>
{#if $values.url && $values.url !== "" && !$validation.errors.url}
<div class="app-server" title={appUrl}>
{appUrl}
</div>
{/if}
</span>
{/if}
{#if currentStep === Step.SET_PASSWORD}
<Input
autofocus={true}
label="Imported file password"
type="password"
bind:value={$values.encryptionPassword}
disabled={creating}
on:blur={() => ($encryptionValidation.touched.encryptionPassword = true)}
error={$encryptionValidation.touched.encryptionPassword &&
$encryptionValidation.errors.encryptionPassword}
/>
{/if}
</ModalContent> </ModalContent>
<style> <style>

View File

@ -115,7 +115,18 @@ function checkAppName(
} }
} }
async function createInstance(appId: string, template: any) { interface AppTemplate {
templateString: string
useTemplate: string
file?: {
type: string
path: string
password?: string
}
key?: string
}
async function createInstance(appId: string, template: AppTemplate) {
const db = context.getAppDB() const db = context.getAppDB()
await db.put({ await db.put({
_id: "_design/database", _id: "_design/database",
@ -240,19 +251,24 @@ export async function fetchAppPackage(ctx: UserCtx) {
async function performAppCreate(ctx: UserCtx) { async function performAppCreate(ctx: UserCtx) {
const apps = (await dbCore.getAllApps({ dev: true })) as App[] const apps = (await dbCore.getAllApps({ dev: true })) as App[]
const name = ctx.request.body.name, const name = ctx.request.body.name,
possibleUrl = ctx.request.body.url possibleUrl = ctx.request.body.url,
encryptionPassword = ctx.request.body.encryptionPassword
checkAppName(ctx, apps, name) checkAppName(ctx, apps, name)
const url = sdk.applications.getAppUrl({ name, url: possibleUrl }) const url = sdk.applications.getAppUrl({ name, url: possibleUrl })
checkAppUrl(ctx, apps, url) checkAppUrl(ctx, apps, url)
const { useTemplate, templateKey, templateString } = ctx.request.body const { useTemplate, templateKey, templateString } = ctx.request.body
const instanceConfig: any = { const instanceConfig: AppTemplate = {
useTemplate, useTemplate,
key: templateKey, key: templateKey,
templateString, templateString,
} }
if (ctx.request.files && ctx.request.files.templateFile) { if (ctx.request.files && ctx.request.files.templateFile) {
instanceConfig.file = ctx.request.files.templateFile instanceConfig.file = {
...(ctx.request.files.templateFile as any),
password: encryptionPassword,
}
} }
const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null
const appId = generateDevAppID(generateAppID(tenantId)) const appId = generateDevAppID(generateAppID(tenantId))