From ef5c55a490e825477476f26b2614425bc4536c3b Mon Sep 17 00:00:00 2001 From: Toby <2287769986@qq.com> Date: Wed, 26 Feb 2025 16:29:47 +0800 Subject: [PATCH 1/5] feat: data validator --- packages/plugin-validator/.npmignore | 2 + packages/plugin-validator/README.md | 1 + packages/plugin-validator/client.d.ts | 2 + packages/plugin-validator/client.js | 1 + packages/plugin-validator/package.json | 19 +++ packages/plugin-validator/server.d.ts | 2 + packages/plugin-validator/server.js | 1 + packages/plugin-validator/src/client/index.ts | 1 + .../plugin-validator/src/client/plugin.tsx | 21 +++ packages/plugin-validator/src/index.ts | 2 + .../src/server/collections/.gitkeep | 0 packages/plugin-validator/src/server/index.ts | 1 + .../src/server/middlewares/validator.ts | 143 ++++++++++++++++++ .../plugin-validator/src/server/plugin.ts | 25 +++ packages/preset-tachybase/src/server/index.ts | 1 + 15 files changed, 222 insertions(+) create mode 100644 packages/plugin-validator/.npmignore create mode 100644 packages/plugin-validator/README.md create mode 100644 packages/plugin-validator/client.d.ts create mode 100644 packages/plugin-validator/client.js create mode 100644 packages/plugin-validator/package.json create mode 100644 packages/plugin-validator/server.d.ts create mode 100644 packages/plugin-validator/server.js create mode 100644 packages/plugin-validator/src/client/index.ts create mode 100644 packages/plugin-validator/src/client/plugin.tsx create mode 100644 packages/plugin-validator/src/index.ts create mode 100644 packages/plugin-validator/src/server/collections/.gitkeep create mode 100644 packages/plugin-validator/src/server/index.ts create mode 100644 packages/plugin-validator/src/server/middlewares/validator.ts create mode 100644 packages/plugin-validator/src/server/plugin.ts diff --git a/packages/plugin-validator/.npmignore b/packages/plugin-validator/.npmignore new file mode 100644 index 0000000000..65f5e8779f --- /dev/null +++ b/packages/plugin-validator/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/packages/plugin-validator/README.md b/packages/plugin-validator/README.md new file mode 100644 index 0000000000..8fec43d489 --- /dev/null +++ b/packages/plugin-validator/README.md @@ -0,0 +1 @@ +# @tachybase/plugin-validator diff --git a/packages/plugin-validator/client.d.ts b/packages/plugin-validator/client.d.ts new file mode 100644 index 0000000000..6c459cbac4 --- /dev/null +++ b/packages/plugin-validator/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/packages/plugin-validator/client.js b/packages/plugin-validator/client.js new file mode 100644 index 0000000000..b6e3be70e6 --- /dev/null +++ b/packages/plugin-validator/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/packages/plugin-validator/package.json b/packages/plugin-validator/package.json new file mode 100644 index 0000000000..87af2ecee3 --- /dev/null +++ b/packages/plugin-validator/package.json @@ -0,0 +1,19 @@ +{ + "name": "@tachybase/plugin-validator", + "displayName": "Data validator", + "version": "0.23.49", + "description": "Before formal data operation, verify the legality and integrity of data types.", + "keywords": [ + "Security" + ], + "main": "dist/server/index.js", + "dependencies": {}, + "peerDependencies": { + "@tachybase/client": "workspace:*", + "@tachybase/server": "workspace:*", + "@tachybase/test": "workspace:*", + "@tachybase/utils": "workspace:*" + }, + "description.zh-CN": "在正式数据操作之前验证数据类型合法性和完整性", + "displayName.zh-CN": "数据验证器" +} diff --git a/packages/plugin-validator/server.d.ts b/packages/plugin-validator/server.d.ts new file mode 100644 index 0000000000..c41081ddc6 --- /dev/null +++ b/packages/plugin-validator/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/packages/plugin-validator/server.js b/packages/plugin-validator/server.js new file mode 100644 index 0000000000..972842039a --- /dev/null +++ b/packages/plugin-validator/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/packages/plugin-validator/src/client/index.ts b/packages/plugin-validator/src/client/index.ts new file mode 100644 index 0000000000..b68aea57f9 --- /dev/null +++ b/packages/plugin-validator/src/client/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugin-validator/src/client/plugin.tsx b/packages/plugin-validator/src/client/plugin.tsx new file mode 100644 index 0000000000..b0e0d79492 --- /dev/null +++ b/packages/plugin-validator/src/client/plugin.tsx @@ -0,0 +1,21 @@ +import { Plugin } from '@tachybase/client'; + +class PluginValidatorClient extends Plugin { + async afterAdd() { + // await this.app.pm.add() + } + + async beforeLoad() {} + + // You can get and modify the app instance here + async load() { + console.log(this.app); + // this.app.addComponents({}) + // this.app.addScopes({}) + // this.app.addProvider() + // this.app.addProviders() + // this.app.router.add() + } +} + +export default PluginValidatorClient; diff --git a/packages/plugin-validator/src/index.ts b/packages/plugin-validator/src/index.ts new file mode 100644 index 0000000000..7e74612df8 --- /dev/null +++ b/packages/plugin-validator/src/index.ts @@ -0,0 +1,2 @@ +export * from './server'; +export { default } from './server'; diff --git a/packages/plugin-validator/src/server/collections/.gitkeep b/packages/plugin-validator/src/server/collections/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/plugin-validator/src/server/index.ts b/packages/plugin-validator/src/server/index.ts new file mode 100644 index 0000000000..b68aea57f9 --- /dev/null +++ b/packages/plugin-validator/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugin-validator/src/server/middlewares/validator.ts b/packages/plugin-validator/src/server/middlewares/validator.ts new file mode 100644 index 0000000000..c51f2780e6 --- /dev/null +++ b/packages/plugin-validator/src/server/middlewares/validator.ts @@ -0,0 +1,143 @@ +import { Context, Next } from '@tachybase/actions'; +import { ICollection, ICollectionManager } from '@tachybase/data-source'; +import { FieldOptions } from '@tachybase/database'; +import { Application } from '@tachybase/server'; +import { dayjs } from '@tachybase/utils'; + +export async function validator(ctx: Context, next: Next) { + const { resourceName, actionName } = ctx.action.params; + // TODO: 如果是非main数据库也跳过 + if (!resourceName || !actionName) { + return next(); + } + if (!['update', 'create', 'destroy'].includes(actionName)) { + return next(); + } + const app = ctx.app as Application; + const collection = app.mainDataSource.collectionManager.getCollection(resourceName); + if (!collection) { + return next(); + } + + // TODO: 需要考虑代码内置表接口,事件源接口, 比如api-keys自定义了create,destroy接口 + + // 首先判断删除,更新的时候主键是否存在 + if (actionName === 'destroy' || actionName === 'update') { + // 获取collection表主键类型 + // bigInt,double,string,boolean,date + let primaryOptions = { + type: 'bigInt', + }; + for (const field of collection.getFields()) { + if (field.options.primaryKey) { + primaryOptions = field.options; + break; + } + } + const { filterByTk } = ctx.action.params; + if (!filterByTk) { + ctx.throw(400, 'filterByTk is required'); + } + if (Array.isArray(filterByTk)) { + for (const item of filterByTk) { + if (!checkType(primaryOptions, item)) { + ctx.throw(400, 'filterByTk type error'); + } + } + } else { + if (!checkType(primaryOptions, filterByTk)) { + ctx.throw(400, 'filterByTk type error'); + } + } + } + + if (actionName === 'update' || actionName === 'create') { + const data = ctx.request.body as any; + const notMatchKey = checkTypeByCollection(app.mainDataSource.collectionManager, collection, data); + if (notMatchKey) { + ctx.throw(400, `field ${notMatchKey} type error`); + } + } + // TODO: 用户输入的key多余的部分忽略,这部分以后可以严格限制不能多出字段定义的部分 + return next(); +} + +function checkTypeByCollection( + collectionManager: ICollectionManager, + collection: ICollection, + values: any, + parentKey: string = null, +): string | null { + // 通过遍历data判断是否符合字段定义 + for (const key in values) { + const field = collection.getField(key); + if (!field) { + continue; + } + const fieldOptions = field.options; + // 虚拟字段不判断 + if (fieldOptions.type === 'virtual') { + continue; + } + const nextParentKey = parentKey ? `${parentKey}.${key}` : key; + if (['hasOne', 'belongsTo'].includes(fieldOptions.type)) { + const subCollection = collectionManager.getCollection(fieldOptions.target); + if (!subCollection) { + continue; + } + const subKey = checkTypeByCollection(collectionManager, subCollection, values[key], nextParentKey); + if (subKey) { + return subKey; + } + } + if (['hasMany', 'belongsToMany'].includes(fieldOptions.type)) { + for (const item of values[key]) { + const subCollection = collectionManager.getCollection(fieldOptions.target); + if (!subCollection) { + continue; + } + const subKey = checkTypeByCollection(collectionManager, subCollection, item, nextParentKey); + if (subKey) { + return subKey; + } + } + } + if (!checkType(fieldOptions, values[key], true)) { + return nextParentKey; + } + } + return null; +} + +// TODO: 更细致的判断,例如长度,是否符合正则等 +// 判断类型是否符合 +function checkType(fieldOptions: FieldOptions, value: any, inputChange = false): boolean { + const type = fieldOptions.type; + if (type === 'string') { + return typeof value === 'string'; + } else if (type === 'bigInt') { + if (typeof value === 'number') { + return Number.isInteger(value); + } + return Number.isInteger(+value); + } else if (type === 'double') { + if (typeof value === 'number') { + return true; + } + return !Number.isNaN(+value); + } else if (type === 'boolean') { + // TODO: 是否存在1,0这样的设计 + return typeof value === 'boolean'; + } else if (type === 'date') { + return dayjs(value).isValid(); + } else if (type === 'sequence') { + // 不可输入的情况下不允许修改, TODO: 需要验证前端是否自动有这样的行为 + // if (inputChange && !fieldOptions.inputable) { + // return false; + // } + } else if (type === 'array') { + return Array.isArray(value); + } + // TODO: 单选多选判断 + return true; +} diff --git a/packages/plugin-validator/src/server/plugin.ts b/packages/plugin-validator/src/server/plugin.ts new file mode 100644 index 0000000000..a96fd0c0be --- /dev/null +++ b/packages/plugin-validator/src/server/plugin.ts @@ -0,0 +1,25 @@ +import { Plugin } from '@tachybase/server'; + +import { validator } from './middlewares/validator'; + +export class PluginValidatorServer extends Plugin { + async afterAdd() {} + + async beforeLoad() {} + + async load() { + // 目前只有resourcer之后才能通过ctx.action获取到resourceName和actionName + // TODO: 在authorization之前执行的好处是不用用户验证先验证参数,坏处是容易被测试窥探数据结构 + this.app.resourcer.use(validator, { tag: 'validator', after: 'acl' }); + } + + async install() {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default PluginValidatorServer; diff --git a/packages/preset-tachybase/src/server/index.ts b/packages/preset-tachybase/src/server/index.ts index b2af5e88be..c1f63b766a 100644 --- a/packages/preset-tachybase/src/server/index.ts +++ b/packages/preset-tachybase/src/server/index.ts @@ -85,6 +85,7 @@ export class PresetTachyBase extends Plugin { ['department', '0.23.22', false], ['workflow-analysis', '0.23.41', false], ['api-logs', '0.23.49', false], + ['validator', '0.23.49', false], ]; get localPlugins() { From 79e6bb536bcba14a287efb3ad11d635aa8df48bd Mon Sep 17 00:00:00 2001 From: Toby <2287769986@qq.com> Date: Wed, 26 Feb 2025 16:35:43 +0800 Subject: [PATCH 2/5] fix: build error --- pnpm-lock.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d11c789d4..8ccff55d1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4362,6 +4362,21 @@ importers: specifier: ^0.17.10 version: 0.17.10 + packages/plugin-validator: + dependencies: + '@tachybase/client': + specifier: workspace:* + version: link:../client + '@tachybase/server': + specifier: workspace:* + version: link:../server + '@tachybase/test': + specifier: workspace:* + version: link:../test + '@tachybase/utils': + specifier: workspace:* + version: link:../utils + packages/plugin-workflow-analysis: dependencies: '@tachybase/actions': From c055265c068c26b7e1cb5fc4f6e67de4a5e0fba3 Mon Sep 17 00:00:00 2001 From: Toby <2287769986@qq.com> Date: Wed, 26 Feb 2025 16:41:35 +0800 Subject: [PATCH 3/5] fix: delete TODO --- packages/plugin-validator/src/server/middlewares/validator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/plugin-validator/src/server/middlewares/validator.ts b/packages/plugin-validator/src/server/middlewares/validator.ts index c51f2780e6..b436112d86 100644 --- a/packages/plugin-validator/src/server/middlewares/validator.ts +++ b/packages/plugin-validator/src/server/middlewares/validator.ts @@ -6,7 +6,6 @@ import { dayjs } from '@tachybase/utils'; export async function validator(ctx: Context, next: Next) { const { resourceName, actionName } = ctx.action.params; - // TODO: 如果是非main数据库也跳过 if (!resourceName || !actionName) { return next(); } From 48ec3be09121eb41144a1e63aa30f3a5d935fb0a Mon Sep 17 00:00:00 2001 From: Toby <2287769986@qq.com> Date: Wed, 26 Feb 2025 16:50:04 +0800 Subject: [PATCH 4/5] fix: build error --- packages/plugin-validator/package.json | 3 +++ packages/preset-tachybase/package.json | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/plugin-validator/package.json b/packages/plugin-validator/package.json index 87af2ecee3..bf3c197ca2 100644 --- a/packages/plugin-validator/package.json +++ b/packages/plugin-validator/package.json @@ -9,7 +9,10 @@ "main": "dist/server/index.js", "dependencies": {}, "peerDependencies": { + "@tachybase/actions": "workspace:*", "@tachybase/client": "workspace:*", + "@tachybase/data-source": "workspace:*", + "@tachybase/database": "workspace:*", "@tachybase/server": "workspace:*", "@tachybase/test": "workspace:*", "@tachybase/utils": "workspace:*" diff --git a/packages/preset-tachybase/package.json b/packages/preset-tachybase/package.json index 0cb4a09b28..1941445bc9 100644 --- a/packages/preset-tachybase/package.json +++ b/packages/preset-tachybase/package.json @@ -80,6 +80,7 @@ "@tachybase/plugin-simple-cms": "workspace:*", "@tachybase/plugin-sub-accounts": "workspace:*", "@tachybase/plugin-theme-editor": "workspace:*", + "@tachybase/plugin-validator": "workspace:*", "@tachybase/plugin-workflow-analysis": "workspace:*", "@tachybase/plugin-workflow-approval": "workspace:*", "@tachybase/schema": "workspace:*", From 159c9ff92ede932a4a7f58277f50d238cdf2b04b Mon Sep 17 00:00:00 2001 From: Toby <2287769986@qq.com> Date: Wed, 26 Feb 2025 16:57:39 +0800 Subject: [PATCH 5/5] fix: build error --- pnpm-lock.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ccff55d1c..afcd5bc3e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4364,9 +4364,18 @@ importers: packages/plugin-validator: dependencies: + '@tachybase/actions': + specifier: workspace:* + version: link:../actions '@tachybase/client': specifier: workspace:* version: link:../client + '@tachybase/data-source': + specifier: workspace:* + version: link:../data-source + '@tachybase/database': + specifier: workspace:* + version: link:../database '@tachybase/server': specifier: workspace:* version: link:../server @@ -4739,6 +4748,9 @@ importers: '@tachybase/plugin-theme-editor': specifier: workspace:* version: link:../plugin-theme-editor + '@tachybase/plugin-validator': + specifier: workspace:* + version: link:../plugin-validator '@tachybase/plugin-workflow-analysis': specifier: workspace:* version: link:../plugin-workflow-analysis