Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/plugin-validator/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
/src
1 change: 1 addition & 0 deletions packages/plugin-validator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @tachybase/plugin-validator
2 changes: 2 additions & 0 deletions packages/plugin-validator/client.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dist/client';
export { default } from './dist/client';
1 change: 1 addition & 0 deletions packages/plugin-validator/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/client/index.js');
22 changes: 22 additions & 0 deletions packages/plugin-validator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"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/actions": "workspace:*",
"@tachybase/client": "workspace:*",
"@tachybase/data-source": "workspace:*",
"@tachybase/database": "workspace:*",
"@tachybase/server": "workspace:*",
"@tachybase/test": "workspace:*",
"@tachybase/utils": "workspace:*"
},
"description.zh-CN": "在正式数据操作之前验证数据类型合法性和完整性",
"displayName.zh-CN": "数据验证器"
}
2 changes: 2 additions & 0 deletions packages/plugin-validator/server.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dist/server';
export { default } from './dist/server';
1 change: 1 addition & 0 deletions packages/plugin-validator/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/server/index.js');
1 change: 1 addition & 0 deletions packages/plugin-validator/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './plugin';
21 changes: 21 additions & 0 deletions packages/plugin-validator/src/client/plugin.tsx
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions packages/plugin-validator/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './server';
export { default } from './server';
Empty file.
1 change: 1 addition & 0 deletions packages/plugin-validator/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './plugin';
142 changes: 142 additions & 0 deletions packages/plugin-validator/src/server/middlewares/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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;
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;
}
25 changes: 25 additions & 0 deletions packages/plugin-validator/src/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions packages/preset-tachybase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
1 change: 1 addition & 0 deletions packages/preset-tachybase/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
27 changes: 27 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading