Add server-side validation for snippet names

This commit is contained in:
Andrew Kingston 2024-03-13 11:48:17 +00:00
parent 74a2b54880
commit 2d12a1a8fa
3 changed files with 26 additions and 13 deletions

View File

@ -13,13 +13,13 @@
import { snippets } from "stores/builder" import { snippets } from "stores/builder"
import { getSequentialName } from "helpers/duplicate" import { getSequentialName } from "helpers/duplicate"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { ValidSnippetNameRegex } from "@budibase/shared-core"
export let snippet export let snippet
export const show = () => drawer.show() export const show = () => drawer.show()
export const hide = () => drawer.hide() export const hide = () => drawer.hide()
const roughValidNameRegex = /^[_$A-Z\xA0-\uFFFF][_$A-Z0-9\xA0-\uFFFF]*$/i
const firstCharNumberRegex = /^[0-9].*$/ const firstCharNumberRegex = /^[0-9].*$/
let drawer let drawer
@ -43,7 +43,7 @@
drawer.hide() drawer.hide()
notifications.success(`Snippet ${newSnippet.name} saved`) notifications.success(`Snippet ${newSnippet.name} saved`)
} catch (error) { } catch (error) {
notifications.error("Error saving snippet") notifications.error(error.message || "Error saving snippet")
} }
loading = false loading = false
} }
@ -69,21 +69,16 @@
if (!name?.length) { if (!name?.length) {
return "Name is required" return "Name is required"
} }
if (firstCharNumberRegex.test(name)) {
return "Can't start with a number"
}
if (!roughValidNameRegex.test(name)) {
return "No special characters or spaces"
}
if (snippets.some(snippet => snippet.name === name)) { if (snippets.some(snippet => snippet.name === name)) {
return "That name is already in use" return "That name is already in use"
} }
const js = `(function ${name}(){return true})()` if (firstCharNumberRegex.test(name)) {
try { return "Can't start with a number"
return eval(js) === true ? null : "Invalid name"
} catch (error) {
return "Invalid name"
} }
if (!ValidSnippetNameRegex.test(name)) {
return "No special characters or spaces"
}
return null
} }
</script> </script>

View File

@ -2,6 +2,7 @@ import { auth, permissions } from "@budibase/backend-core"
import { DataSourceOperation } from "../../../constants" import { DataSourceOperation } from "../../../constants"
import { WebhookActionType } from "@budibase/types" import { WebhookActionType } from "@budibase/types"
import Joi from "joi" import Joi from "joi"
import { ValidSnippetNameRegex } from "@budibase/shared-core"
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("") const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
const OPTIONAL_NUMBER = Joi.number().optional().allow(null) const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
@ -226,6 +227,21 @@ export function applicationValidator(opts = { isCreate: true }) {
base.name = appNameValidator.optional() base.name = appNameValidator.optional()
} }
const snippetValidator = Joi.array()
.optional()
.items(
Joi.object({
name: Joi.string()
.pattern(new RegExp(ValidSnippetNameRegex))
.error(
new Error(
"Snippet name cannot include spaces or special characters, and cannot start with a number"
)
),
code: OPTIONAL_STRING,
})
)
return auth.joiValidator.body( return auth.joiValidator.body(
Joi.object({ Joi.object({
_id: OPTIONAL_STRING, _id: OPTIONAL_STRING,
@ -235,6 +251,7 @@ export function applicationValidator(opts = { isCreate: true }) {
template: Joi.object({ template: Joi.object({
templateString: OPTIONAL_STRING, templateString: OPTIONAL_STRING,
}).unknown(true), }).unknown(true),
snippets: snippetValidator,
}).unknown(true) }).unknown(true)
) )
} }

View File

@ -98,6 +98,7 @@ export enum BuilderSocketEvent {
export const SocketSessionTTL = 60 export const SocketSessionTTL = 60
export const ValidQueryNameRegex = /^[^()]*$/ export const ValidQueryNameRegex = /^[^()]*$/
export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g
export const ValidSnippetNameRegex = /^[a-z-_][a-z0-9-_]*$/i
export const REBOOT_CRON = "@reboot" export const REBOOT_CRON = "@reboot"