Merge branch 'BUDI-8282/validate-configuration' into BUDI-8282/validate-configuration-for-hidden-views
This commit is contained in:
commit
44708a27ff
|
@ -3,7 +3,8 @@ import { Ctx } from "@budibase/types"
|
||||||
|
|
||||||
function validate(
|
function validate(
|
||||||
schema: Joi.ObjectSchema | Joi.ArraySchema,
|
schema: Joi.ObjectSchema | Joi.ArraySchema,
|
||||||
property: string
|
property: string,
|
||||||
|
opts: { errorPrefix: string } = { errorPrefix: `Invalid ${property}` }
|
||||||
) {
|
) {
|
||||||
// Return a Koa middleware function
|
// Return a Koa middleware function
|
||||||
return (ctx: Ctx, next: any) => {
|
return (ctx: Ctx, next: any) => {
|
||||||
|
@ -29,16 +30,26 @@ function validate(
|
||||||
|
|
||||||
const { error } = schema.validate(params)
|
const { error } = schema.validate(params)
|
||||||
if (error) {
|
if (error) {
|
||||||
ctx.throw(400, `Invalid ${property} - ${error.message}`)
|
let message = error.message
|
||||||
|
if (opts.errorPrefix) {
|
||||||
|
message = `Invalid ${property} - ${message}`
|
||||||
|
}
|
||||||
|
ctx.throw(400, message)
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function body(schema: Joi.ObjectSchema | Joi.ArraySchema) {
|
export function body(
|
||||||
return validate(schema, "body")
|
schema: Joi.ObjectSchema | Joi.ArraySchema,
|
||||||
|
opts?: { errorPrefix: string }
|
||||||
|
) {
|
||||||
|
return validate(schema, "body", opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function params(schema: Joi.ObjectSchema | Joi.ArraySchema) {
|
export function params(
|
||||||
return validate(schema, "params")
|
schema: Joi.ObjectSchema | Joi.ArraySchema,
|
||||||
|
opts?: { errorPrefix: string }
|
||||||
|
) {
|
||||||
|
return validate(schema, "params", opts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,7 +368,8 @@ describe.each([
|
||||||
await config.api.viewV2.create(newView, {
|
await config.api.viewV2.create(newView, {
|
||||||
status: 400,
|
status: 400,
|
||||||
body: {
|
body: {
|
||||||
message: 'You can\'t make read only the required field "name"',
|
message:
|
||||||
|
'You can\'t make field "name" readonly because it is a required field.',
|
||||||
status: 400,
|
status: 400,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1479,7 +1480,7 @@ describe.each([
|
||||||
body: {
|
body: {
|
||||||
status: 400,
|
status: 400,
|
||||||
message:
|
message:
|
||||||
'Invalid body - Required field "name" is missing in view "view a"',
|
'To make field "name" required, this field must be present and writable in views: view a.',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1510,7 +1511,7 @@ describe.each([
|
||||||
body: {
|
body: {
|
||||||
status: 400,
|
status: 400,
|
||||||
message:
|
message:
|
||||||
'Invalid body - Required field "name" is missing in view "view a"',
|
'To make field "name" required, this field must be present and writable in views: view a.',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,7 +30,7 @@ const validateViewSchemas: CustomValidator<Table> = (table, helpers) => {
|
||||||
)
|
)
|
||||||
if (missingField) {
|
if (missingField) {
|
||||||
return helpers.message({
|
return helpers.message({
|
||||||
custom: `Required field "${missingField}" is missing in view "${view.name}"`,
|
custom: `To make field "${missingField}" required, this field must be present and writable in views: ${view.name}.`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,42 +40,49 @@ const validateViewSchemas: CustomValidator<Table> = (table, helpers) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tableValidator() {
|
export function tableValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
_id: OPTIONAL_STRING,
|
_id: OPTIONAL_STRING,
|
||||||
_rev: OPTIONAL_STRING,
|
_rev: OPTIONAL_STRING,
|
||||||
type: OPTIONAL_STRING.valid("table", "internal", "external"),
|
type: OPTIONAL_STRING.valid("table", "internal", "external"),
|
||||||
primaryDisplay: OPTIONAL_STRING,
|
primaryDisplay: OPTIONAL_STRING,
|
||||||
schema: Joi.object().required(),
|
schema: Joi.object().required(),
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
views: Joi.object(),
|
views: Joi.object(),
|
||||||
rows: Joi.array(),
|
rows: Joi.array(),
|
||||||
}).custom(validateViewSchemas).unknown(true))
|
})
|
||||||
|
.custom(validateViewSchemas)
|
||||||
|
.unknown(true),
|
||||||
|
{ errorPrefix: "" }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nameValidator() {
|
export function nameValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
name: OPTIONAL_STRING,
|
name: OPTIONAL_STRING,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function datasourceValidator() {
|
export function datasourceValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
_id: Joi.string(),
|
_id: Joi.string(),
|
||||||
_rev: Joi.string(),
|
_rev: Joi.string(),
|
||||||
type: OPTIONAL_STRING.allow("datasource_plus"),
|
type: OPTIONAL_STRING.allow("datasource_plus"),
|
||||||
relationships: Joi.array().items(Joi.object({
|
relationships: Joi.array().items(
|
||||||
from: Joi.string().required(),
|
Joi.object({
|
||||||
to: Joi.string().required(),
|
from: Joi.string().required(),
|
||||||
cardinality: Joi.valid("1:N", "1:1", "N:N").required()
|
to: Joi.string().required(),
|
||||||
})),
|
cardinality: Joi.valid("1:N", "1:1", "N:N").required(),
|
||||||
}).unknown(true))
|
})
|
||||||
|
),
|
||||||
|
}).unknown(true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterObject() {
|
function filterObject() {
|
||||||
// prettier-ignore
|
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
string: Joi.object().optional(),
|
string: Joi.object().optional(),
|
||||||
fuzzy: Joi.object().optional(),
|
fuzzy: Joi.object().optional(),
|
||||||
|
@ -92,17 +99,20 @@ function filterObject() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function internalSearchValidator() {
|
export function internalSearchValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
tableId: OPTIONAL_STRING,
|
tableId: OPTIONAL_STRING,
|
||||||
query: filterObject(),
|
query: filterObject(),
|
||||||
limit: OPTIONAL_NUMBER,
|
limit: OPTIONAL_NUMBER,
|
||||||
sort: OPTIONAL_STRING,
|
sort: OPTIONAL_STRING,
|
||||||
sortOrder: OPTIONAL_STRING,
|
sortOrder: OPTIONAL_STRING,
|
||||||
sortType: OPTIONAL_STRING,
|
sortType: OPTIONAL_STRING,
|
||||||
paginate: Joi.boolean(),
|
paginate: Joi.boolean(),
|
||||||
bookmark: Joi.alternatives().try(OPTIONAL_STRING, OPTIONAL_NUMBER).optional(),
|
bookmark: Joi.alternatives()
|
||||||
}))
|
.try(OPTIONAL_STRING, OPTIONAL_NUMBER)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function externalSearchValidator() {
|
export function externalSearchValidator() {
|
||||||
|
@ -124,92 +134,110 @@ export function externalSearchValidator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function datasourceQueryValidator() {
|
export function datasourceQueryValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
endpoint: Joi.object({
|
endpoint: Joi.object({
|
||||||
datasourceId: Joi.string().required(),
|
datasourceId: Joi.string().required(),
|
||||||
operation: Joi.string().required().valid(...Object.values(DataSourceOperation)),
|
operation: Joi.string()
|
||||||
entityId: Joi.string().required(),
|
.required()
|
||||||
}).required(),
|
.valid(...Object.values(DataSourceOperation)),
|
||||||
resource: Joi.object({
|
entityId: Joi.string().required(),
|
||||||
fields: Joi.array().items(Joi.string()).optional(),
|
}).required(),
|
||||||
}).optional(),
|
resource: Joi.object({
|
||||||
body: Joi.object().optional(),
|
fields: Joi.array().items(Joi.string()).optional(),
|
||||||
sort: Joi.object().optional(),
|
}).optional(),
|
||||||
filters: filterObject().optional(),
|
body: Joi.object().optional(),
|
||||||
paginate: Joi.object({
|
sort: Joi.object().optional(),
|
||||||
page: Joi.string().alphanum().optional(),
|
filters: filterObject().optional(),
|
||||||
limit: Joi.number().optional(),
|
paginate: Joi.object({
|
||||||
}).optional(),
|
page: Joi.string().alphanum().optional(),
|
||||||
}))
|
limit: Joi.number().optional(),
|
||||||
|
}).optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function webhookValidator() {
|
export function webhookValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
live: Joi.bool(),
|
live: Joi.bool(),
|
||||||
_id: OPTIONAL_STRING,
|
_id: OPTIONAL_STRING,
|
||||||
_rev: OPTIONAL_STRING,
|
_rev: OPTIONAL_STRING,
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
bodySchema: Joi.object().optional(),
|
bodySchema: Joi.object().optional(),
|
||||||
action: Joi.object({
|
action: Joi.object({
|
||||||
type: Joi.string().required().valid(WebhookActionType.AUTOMATION),
|
type: Joi.string().required().valid(WebhookActionType.AUTOMATION),
|
||||||
target: Joi.string().required(),
|
target: Joi.string().required(),
|
||||||
}).required(),
|
}).required(),
|
||||||
}).unknown(true))
|
}).unknown(true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roleValidator() {
|
export function roleValidator() {
|
||||||
const permLevelArray = Object.values(permissions.PermissionLevel)
|
const permLevelArray = Object.values(permissions.PermissionLevel)
|
||||||
// prettier-ignore
|
|
||||||
return auth.joiValidator.body(Joi.object({
|
return auth.joiValidator.body(
|
||||||
_id: OPTIONAL_STRING,
|
Joi.object({
|
||||||
_rev: OPTIONAL_STRING,
|
_id: OPTIONAL_STRING,
|
||||||
name: Joi.string().regex(/^[a-zA-Z0-9_]*$/).required(),
|
_rev: OPTIONAL_STRING,
|
||||||
// this is the base permission ID (for now a built in)
|
name: Joi.string()
|
||||||
permissionId: Joi.string().valid(...Object.values(permissions.BuiltinPermissionID)).required(),
|
.regex(/^[a-zA-Z0-9_]*$/)
|
||||||
permissions: Joi.object()
|
.required(),
|
||||||
.pattern(/.*/, [Joi.string().valid(...permLevelArray)])
|
// this is the base permission ID (for now a built in)
|
||||||
.optional(),
|
permissionId: Joi.string()
|
||||||
inherits: OPTIONAL_STRING,
|
.valid(...Object.values(permissions.BuiltinPermissionID))
|
||||||
}).unknown(true))
|
.required(),
|
||||||
|
permissions: Joi.object()
|
||||||
|
.pattern(/.*/, [Joi.string().valid(...permLevelArray)])
|
||||||
|
.optional(),
|
||||||
|
inherits: OPTIONAL_STRING,
|
||||||
|
}).unknown(true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function permissionValidator() {
|
export function permissionValidator() {
|
||||||
const permLevelArray = Object.values(permissions.PermissionLevel)
|
const permLevelArray = Object.values(permissions.PermissionLevel)
|
||||||
// prettier-ignore
|
|
||||||
return auth.joiValidator.params(Joi.object({
|
return auth.joiValidator.params(
|
||||||
level: Joi.string().valid(...permLevelArray).required(),
|
Joi.object({
|
||||||
resourceId: Joi.string(),
|
level: Joi.string()
|
||||||
roleId: Joi.string(),
|
.valid(...permLevelArray)
|
||||||
}).unknown(true))
|
.required(),
|
||||||
|
resourceId: Joi.string(),
|
||||||
|
roleId: Joi.string(),
|
||||||
|
}).unknown(true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function screenValidator() {
|
export function screenValidator() {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
showNavigation: OPTIONAL_BOOLEAN,
|
showNavigation: OPTIONAL_BOOLEAN,
|
||||||
width: OPTIONAL_STRING,
|
width: OPTIONAL_STRING,
|
||||||
routing: Joi.object({
|
routing: Joi.object({
|
||||||
route: Joi.string().required(),
|
route: Joi.string().required(),
|
||||||
roleId: Joi.string().required().allow(""),
|
roleId: Joi.string().required().allow(""),
|
||||||
homeScreen: OPTIONAL_BOOLEAN,
|
homeScreen: OPTIONAL_BOOLEAN,
|
||||||
}).required().unknown(true),
|
})
|
||||||
props: Joi.object({
|
.required()
|
||||||
_id: Joi.string().required(),
|
.unknown(true),
|
||||||
_component: Joi.string().required(),
|
props: Joi.object({
|
||||||
_children: Joi.array().required(),
|
_id: Joi.string().required(),
|
||||||
_styles: Joi.object().required(),
|
_component: Joi.string().required(),
|
||||||
type: OPTIONAL_STRING,
|
_children: Joi.array().required(),
|
||||||
table: OPTIONAL_STRING,
|
_styles: Joi.object().required(),
|
||||||
layoutId: OPTIONAL_STRING,
|
type: OPTIONAL_STRING,
|
||||||
}).required().unknown(true),
|
table: OPTIONAL_STRING,
|
||||||
}).unknown(true))
|
layoutId: OPTIONAL_STRING,
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
.unknown(true),
|
||||||
|
}).unknown(true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateStepSchema(allowStepTypes: string[]) {
|
function generateStepSchema(allowStepTypes: string[]) {
|
||||||
// prettier-ignore
|
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
stepId: Joi.string().required(),
|
stepId: Joi.string().required(),
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
|
@ -219,33 +247,39 @@ function generateStepSchema(allowStepTypes: string[]) {
|
||||||
icon: Joi.string().required(),
|
icon: Joi.string().required(),
|
||||||
params: Joi.object(),
|
params: Joi.object(),
|
||||||
args: Joi.object(),
|
args: Joi.object(),
|
||||||
type: Joi.string().required().valid(...allowStepTypes),
|
type: Joi.string()
|
||||||
|
.required()
|
||||||
|
.valid(...allowStepTypes),
|
||||||
}).unknown(true)
|
}).unknown(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function automationValidator(existing = false) {
|
export function automationValidator(existing = false) {
|
||||||
// prettier-ignore
|
return auth.joiValidator.body(
|
||||||
return auth.joiValidator.body(Joi.object({
|
Joi.object({
|
||||||
_id: existing ? Joi.string().required() : OPTIONAL_STRING,
|
_id: existing ? Joi.string().required() : OPTIONAL_STRING,
|
||||||
_rev: existing ? Joi.string().required() : OPTIONAL_STRING,
|
_rev: existing ? Joi.string().required() : OPTIONAL_STRING,
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
type: Joi.string().valid("automation").required(),
|
type: Joi.string().valid("automation").required(),
|
||||||
definition: Joi.object({
|
definition: Joi.object({
|
||||||
steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])),
|
steps: Joi.array()
|
||||||
trigger: generateStepSchema(["TRIGGER"]).allow(null),
|
.required()
|
||||||
}).required().unknown(true),
|
.items(generateStepSchema(["ACTION", "LOGIC"])),
|
||||||
}).unknown(true))
|
trigger: generateStepSchema(["TRIGGER"]).allow(null),
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
.unknown(true),
|
||||||
|
}).unknown(true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applicationValidator(opts = { isCreate: true }) {
|
export function applicationValidator(opts = { isCreate: true }) {
|
||||||
// prettier-ignore
|
|
||||||
const base: any = {
|
const base: any = {
|
||||||
_id: OPTIONAL_STRING,
|
_id: OPTIONAL_STRING,
|
||||||
_rev: OPTIONAL_STRING,
|
_rev: OPTIONAL_STRING,
|
||||||
url: OPTIONAL_STRING,
|
url: OPTIONAL_STRING,
|
||||||
template: Joi.object({
|
template: Joi.object({
|
||||||
templateString: OPTIONAL_STRING,
|
templateString: OPTIONAL_STRING,
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const appNameValidator = Joi.string()
|
const appNameValidator = Joi.string()
|
||||||
|
|
|
@ -84,7 +84,7 @@ async function guardViewSchema(
|
||||||
|
|
||||||
if (viewSchemaField.readonly) {
|
if (viewSchemaField.readonly) {
|
||||||
throw new HTTPError(
|
throw new HTTPError(
|
||||||
`You can't make read only the required field "${field.name}"`,
|
`You can't make field "${field.name}" readonly because it is a required field.`,
|
||||||
400
|
400
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue