Merge pull request #10883 from Budibase/budi-7010/frontend-encrypt-app-exports
BUDI-7010 - Frontend encrypt app exports
This commit is contained in:
commit
2e2b200b45
|
@ -4,20 +4,60 @@
|
|||
Toggle,
|
||||
Body,
|
||||
InlineAlert,
|
||||
Input,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { createValidationStore } from "helpers/validation/yup"
|
||||
|
||||
export let app
|
||||
export let published
|
||||
let excludeRows = false
|
||||
let includeInternalTablesRows = true
|
||||
let encypt = true
|
||||
|
||||
$: title = published ? "Export published app" : "Export latest app"
|
||||
$: confirmText = published ? "Export published" : "Export latest"
|
||||
let password = null
|
||||
const validation = createValidationStore()
|
||||
validation.addValidatorType("password", "password", true)
|
||||
$: validation.observe("password", password)
|
||||
|
||||
const Step = { CONFIG: "config", SET_PASSWORD: "set_password" }
|
||||
let currentStep = Step.CONFIG
|
||||
|
||||
$: exportButtonText = published ? "Export published" : "Export latest"
|
||||
$: stepConfig = {
|
||||
[Step.CONFIG]: {
|
||||
title: published ? "Export published app" : "Export latest app",
|
||||
confirmText: encypt ? "Continue" : exportButtonText,
|
||||
onConfirm: () => {
|
||||
if (!encypt) {
|
||||
exportApp()
|
||||
} else {
|
||||
currentStep = Step.SET_PASSWORD
|
||||
return false
|
||||
}
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
[Step.SET_PASSWORD]: {
|
||||
title: "Add password to encrypt your export",
|
||||
confirmText: exportButtonText,
|
||||
onConfirm: async () => {
|
||||
await validation.check({ password })
|
||||
if (!$validation.valid) {
|
||||
return false
|
||||
}
|
||||
exportApp(password)
|
||||
},
|
||||
isValid: $validation.valid,
|
||||
},
|
||||
}
|
||||
|
||||
const exportApp = async () => {
|
||||
const id = published ? app.prodId : app.devId
|
||||
const url = `/api/backups/export?appId=${id}`
|
||||
await downloadFile(url, { excludeRows })
|
||||
await downloadFile(url, {
|
||||
excludeRows: !includeInternalTablesRows,
|
||||
encryptPassword: password,
|
||||
})
|
||||
}
|
||||
|
||||
async function downloadFile(url, body) {
|
||||
|
@ -56,13 +96,33 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ModalContent {title} {confirmText} onConfirm={exportApp}>
|
||||
<InlineAlert
|
||||
header="Do not share your budibase application exports publicly as they may contain sensitive information such as database credentials or secret keys."
|
||||
/>
|
||||
<Body
|
||||
>Apps can be exported with or without data that is within internal tables -
|
||||
select this below.</Body
|
||||
>
|
||||
<Toggle text="Exclude Rows" bind:value={excludeRows} />
|
||||
<ModalContent
|
||||
title={stepConfig[currentStep].title}
|
||||
confirmText={stepConfig[currentStep].confirmText}
|
||||
onConfirm={stepConfig[currentStep].onConfirm}
|
||||
disabled={!stepConfig[currentStep].isValid}
|
||||
>
|
||||
{#if currentStep === Step.CONFIG}
|
||||
<Body>
|
||||
<Toggle
|
||||
text="Export rows from internal tables"
|
||||
bind:value={includeInternalTablesRows}
|
||||
/>
|
||||
<Toggle text="Encrypt my export" bind:value={encypt} />
|
||||
</Body>
|
||||
{#if !encypt}
|
||||
<InlineAlert
|
||||
header="Do not share your budibase application exports publicly as they may contain sensitive information such as database credentials or secret keys."
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if currentStep === Step.SET_PASSWORD}
|
||||
<Input
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Type here..."
|
||||
bind:value={password}
|
||||
error={$validation.errors.password}
|
||||
/>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
|
|
|
@ -6,7 +6,6 @@ export function createValidationStore(initialValue, ...validators) {
|
|||
let touched = false
|
||||
|
||||
const value = writable(initialValue || "")
|
||||
const error = derived(value, $v => validate($v, validators))
|
||||
const touchedStore = derived(value, () => {
|
||||
if (!touched) {
|
||||
touched = true
|
||||
|
@ -14,6 +13,10 @@ export function createValidationStore(initialValue, ...validators) {
|
|||
}
|
||||
return touched
|
||||
})
|
||||
const error = derived(
|
||||
[value, touchedStore],
|
||||
([$v, $t]) => $t && validate($v, validators)
|
||||
)
|
||||
|
||||
return [value, error, touchedStore]
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { notifications } from "@budibase/bbui"
|
|||
|
||||
export const createValidationStore = () => {
|
||||
const DEFAULT = {
|
||||
values: {},
|
||||
errors: {},
|
||||
touched: {},
|
||||
valid: false,
|
||||
|
@ -33,6 +34,9 @@ export const createValidationStore = () => {
|
|||
case "email":
|
||||
propertyValidator = string().email().nullable()
|
||||
break
|
||||
case "password":
|
||||
propertyValidator = string().nullable()
|
||||
break
|
||||
default:
|
||||
propertyValidator = string().nullable()
|
||||
}
|
||||
|
@ -41,9 +45,68 @@ export const createValidationStore = () => {
|
|||
propertyValidator = propertyValidator.required()
|
||||
}
|
||||
|
||||
// We want to do this after the possible required validation, to prioritise the required error
|
||||
switch (type) {
|
||||
case "password":
|
||||
propertyValidator = propertyValidator.min(8)
|
||||
break
|
||||
}
|
||||
|
||||
validator[propertyName] = propertyValidator
|
||||
}
|
||||
|
||||
const observe = async (propertyName, value) => {
|
||||
const values = get(validation).values
|
||||
let fieldIsValid
|
||||
if (!values.hasOwnProperty(propertyName)) {
|
||||
// Initial setup
|
||||
values[propertyName] = value
|
||||
return
|
||||
}
|
||||
|
||||
if (value === values[propertyName]) {
|
||||
return
|
||||
}
|
||||
|
||||
const obj = object().shape(validator)
|
||||
try {
|
||||
validation.update(store => {
|
||||
store.errors[propertyName] = null
|
||||
return store
|
||||
})
|
||||
await obj.validateAt(propertyName, { [propertyName]: value })
|
||||
fieldIsValid = true
|
||||
} catch (error) {
|
||||
const [fieldError] = error.errors
|
||||
if (fieldError) {
|
||||
validation.update(store => {
|
||||
store.errors[propertyName] = capitalise(fieldError)
|
||||
store.valid = false
|
||||
return store
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldIsValid) {
|
||||
// Validate the rest of the fields
|
||||
try {
|
||||
await obj.validate(
|
||||
{ ...values, [propertyName]: value },
|
||||
{ abortEarly: false }
|
||||
)
|
||||
validation.update(store => {
|
||||
store.valid = true
|
||||
return store
|
||||
})
|
||||
} catch {
|
||||
validation.update(store => {
|
||||
store.valid = false
|
||||
return store
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const check = async values => {
|
||||
const obj = object().shape(validator)
|
||||
// clear the previous errors
|
||||
|
@ -87,5 +150,6 @@ export const createValidationStore = () => {
|
|||
check,
|
||||
addValidator,
|
||||
addValidatorType,
|
||||
observe,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue