Merge pull request #10894 from Budibase/budi-7010/frontend-encrypt-app-imports
BUDI-7010 - Frontend decrypt app imports
This commit is contained in:
commit
a0bfebf53d
|
@ -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>
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue