diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index e7dd5c7a22..3badf0d8c3 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -13,13 +13,13 @@ import { snippets } from "stores/builder" import { getSequentialName } from "helpers/duplicate" import ConfirmDialog from "components/common/ConfirmDialog.svelte" + import { ValidSnippetNameRegex } from "@budibase/shared-core" export let snippet export const show = () => drawer.show() export const hide = () => drawer.hide() - const roughValidNameRegex = /^[_$A-Z\xA0-\uFFFF][_$A-Z0-9\xA0-\uFFFF]*$/i const firstCharNumberRegex = /^[0-9].*$/ let drawer @@ -43,7 +43,7 @@ drawer.hide() notifications.success(`Snippet ${newSnippet.name} saved`) } catch (error) { - notifications.error("Error saving snippet") + notifications.error(error.message || "Error saving snippet") } loading = false } @@ -69,21 +69,16 @@ if (!name?.length) { 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)) { return "That name is already in use" } - const js = `(function ${name}(){return true})()` - try { - return eval(js) === true ? null : "Invalid name" - } catch (error) { - return "Invalid name" + if (firstCharNumberRegex.test(name)) { + return "Can't start with a number" } + if (!ValidSnippetNameRegex.test(name)) { + return "No special characters or spaces" + } + return null } diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts index 55766cd120..424d0d6c79 100644 --- a/packages/server/src/api/routes/utils/validators.ts +++ b/packages/server/src/api/routes/utils/validators.ts @@ -2,6 +2,7 @@ import { auth, permissions } from "@budibase/backend-core" import { DataSourceOperation } from "../../../constants" import { WebhookActionType } from "@budibase/types" import Joi from "joi" +import { ValidSnippetNameRegex } from "@budibase/shared-core" const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("") const OPTIONAL_NUMBER = Joi.number().optional().allow(null) @@ -226,6 +227,21 @@ export function applicationValidator(opts = { isCreate: true }) { 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( Joi.object({ _id: OPTIONAL_STRING, @@ -235,6 +251,7 @@ export function applicationValidator(opts = { isCreate: true }) { template: Joi.object({ templateString: OPTIONAL_STRING, }).unknown(true), + snippets: snippetValidator, }).unknown(true) ) } diff --git a/packages/shared-core/src/constants/index.ts b/packages/shared-core/src/constants/index.ts index 99fb5c2a73..b5b651a3da 100644 --- a/packages/shared-core/src/constants/index.ts +++ b/packages/shared-core/src/constants/index.ts @@ -98,6 +98,7 @@ export enum BuilderSocketEvent { export const SocketSessionTTL = 60 export const ValidQueryNameRegex = /^[^()]*$/ export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g +export const ValidSnippetNameRegex = /^[a-z-_][a-z0-9-_]*$/i export const REBOOT_CRON = "@reboot"