UNPKG

database-proxy

Version:

Through a set of access control rules configuration database access to realize the client directly access the database via HTTP.

263 lines (262 loc) 11.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Constraint = exports.ConstraintTypes = void 0; const console_1 = require("console"); const $ = __importStar(require("validator")); const script_1 = require("./script"); var ConstraintTypes; (function (ConstraintTypes) { ConstraintTypes["REQUIRED"] = "required"; ConstraintTypes["IN"] = "in"; ConstraintTypes["DEFAULT"] = "default"; ConstraintTypes["LENGTH"] = "length"; ConstraintTypes["NUMBER"] = "number"; ConstraintTypes["UNIQUE"] = "unique"; ConstraintTypes["MATCH"] = "match"; ConstraintTypes["EXISTS"] = "exists"; ConstraintTypes["CONDITION"] = "condition"; ConstraintTypes["COND"] = "cond"; ConstraintTypes["NOT_EXISTS"] = "notExists"; })(ConstraintTypes || (exports.ConstraintTypes = ConstraintTypes = {})); class Constraint { constructor(context, data, ignoreConstraints) { (0, console_1.assert)(context, 'context cannot be empty'); (0, console_1.assert)(data, 'data to be constrainted cannot be empty'); this.context = context; this.data = data; this.ignoreConstraints = ignoreConstraints !== null && ignoreConstraints !== void 0 ? ignoreConstraints : []; } /** * 验证请求 query 中的一个字段 * 1. 如果请求 query 中缺省了该字段,除进行 required 和 default 检查外,则不进行额外约束,直接通过 * 2. 如果请求 query 中存在该字段,则对该字段进行所有约束检查,任意一条检查未通过则返回检查失败 * @param field 要验证的字段名 * @param constrains 此字段的约束配置 * @param context 请求上下文 * @returns */ async constraintField(field, constrains) { (0, console_1.assert)(this.context, 'context cannot be empty'); (0, console_1.assert)(this.data, 'data to be constrainted cannot be empty'); const data = this.data; if (typeof constrains === 'string') { constrains = { condition: constrains }; } if (typeof constrains !== 'object') { return `config error: [${field}]'s constraint config must be an object`; } /** * 当此字段缺省时,进行 required 和 default 约束检查 * 1. 如果存在 default 设置,则将缺省字段设置为默认值,跳过 required 以及后续约束检查,直接通过 * 2. 如果不存在 default,则进行 requied 约束检查,并跳过后续约束检查 */ if (!this.ignored('default') && data[field] === undefined) { // if default if (constrains['default'] !== undefined) { data[field] = data[field] = constrains['default']; return null; } else if (!this.ignored('required')) { const isRequired = constrains['required'] == true; return isRequired ? `${field} is required` : null; } } // 排除掉 required & default 约束 const constraintNames = Object.keys(constrains).filter((name) => [ConstraintTypes.REQUIRED, ConstraintTypes.DEFAULT].includes(name) == false); // 对每个约束进行检查 for (const constraintName of constraintNames) { if (this.ignored(constraintName)) { continue; } const options = constrains[constraintName]; const error = await this.checkConstraint(constraintName, options, field); if (error) return error; } return null; } /** * 对字段进行约束性检查 * @param constraintName 约束名 * @param constraintOption 约束配置 * @param fieldKey query 字段名 * @param context 请求上下文 * @returns error or null */ async checkConstraint(constraintName, constraintOption, fieldKey) { (0, console_1.assert)(this.context, 'context cannot be empty'); (0, console_1.assert)(this.data, 'data to be constrainted cannot be empty'); const data = this.data; if (!Constraint.ALLOWED_CONSTRAINTS.includes(constraintName)) { return `config error: unknown rule [${constraintName}]`; } const value = data[fieldKey]; const option = constraintOption; if (constraintName === ConstraintTypes.CONDITION) { return await this.performCondition(option, fieldKey, value); } if (constraintName === ConstraintTypes.COND) { return await this.performCondition(option, fieldKey, value); } if (constraintName === ConstraintTypes.IN) { return this.performIn(option, fieldKey, value); } if (constraintName === ConstraintTypes.LENGTH) { return this.performLength(option, fieldKey, value); } if (constraintName === ConstraintTypes.NUMBER) { return this.performNumber(option, fieldKey, value); } if (constraintName === ConstraintTypes.MATCH) { return this.performMatch(option, fieldKey, value); } // {"exists": "/users/id"}, if (constraintName === ConstraintTypes.EXISTS) { return await this.performExists(option, fieldKey, value); } // {"notExists": "/users/id"}, if (constraintName === ConstraintTypes.NOT_EXISTS) { return await this.performNotExists(option, fieldKey, value); } if (constraintName === ConstraintTypes.UNIQUE && constraintOption) { return await this.performUnique(option, fieldKey, value); } return null; } /** * 约束未被忽略 * @param constraintName 约束名 * @returns */ ignored(constraintName) { return this.ignoreConstraints.includes(constraintName); } async performCondition(constraintOption, fieldKey, value) { const { injections } = this.context; const global = Object.assign(Object.assign({}, injections), { $value: value, $v: value }); const { result, error } = await (0, script_1.executeScript)(constraintOption, global, this.context, fieldKey); if (error) return error; // 如果 result 为真,则通过验证 if (result) return null; return `condition evaluted to false`; } performIn(constraintOption, fieldKey, value) { if (!(constraintOption instanceof Array)) { return `config error: ${fieldKey}#in must be an array`; } if (!constraintOption.includes(value)) { const str = constraintOption.join(','); return `${fieldKey} should equal to one of [${str}]`; } } performLength(constraintOption, fieldKey, value) { if (!(constraintOption instanceof Array && constraintOption.length)) { return `config error: ${fieldKey}#length must be an array with 1-2 integer element, ex. [3, 10]`; } const min = constraintOption[0]; const max = constraintOption.length >= 2 ? constraintOption[1] : undefined; const ok = $.isLength(value, min, max); if (!ok) { let error = `length of ${fieldKey} should >= ${min}`; if (max !== undefined) error += ` and <= ${max}`; return error; } } performNumber(constraintOption, fieldKey, value) { if (!(constraintOption instanceof Array && constraintOption.length)) { return `config error: ${fieldKey}#number must be an array with 1-2 integer element, ex. [3, 10]`; } const min = constraintOption[0]; const max = constraintOption.length >= 2 ? constraintOption[1] : Infinity; const ok = value >= min && value <= max; if (!ok) { let error = `${fieldKey} should >= ${min}`; if (max !== Infinity) error += ` and <= ${max}`; return error; } } performMatch(constraintOption, fieldKey, value) { if (!(typeof constraintOption === 'string' && constraintOption.length)) { return `config error: ${fieldKey}#match must be a string`; } try { const partten = new RegExp(constraintOption); const ok = partten.test(value); if (!ok) { return `${fieldKey} had invalid format`; } } catch (error) { return error; } } async performExists(constraintOption, fieldKey, value) { if (!(typeof constraintOption === 'string' && constraintOption.length)) { return `config error: ${fieldKey}#exists must be a string`; } const { collection, field } = (0, script_1.parseQueryURI)(constraintOption); const accessor = this.context.ruler.accessor; const ret = await accessor.get(collection, { [field]: value }); if (!ret) return `${fieldKey} not exists in ${collection}`; } async performNotExists(constraintOption, fieldKey, value) { if (!(typeof constraintOption === 'string' && constraintOption.length)) { return `config error: ${fieldKey}#notExists must be a string`; } const { collection, field } = (0, script_1.parseQueryURI)(constraintOption); const accessor = this.context.ruler.accessor; const ret = await accessor.get(collection, { [field]: value }); if (ret) return `${fieldKey} already exists in ${collection}`; } async performUnique(_constraintOption, fieldKey, value) { const accessor = this.context.ruler.accessor; const collection = this.context.params.collection; const ret = await accessor.get(collection, { [fieldKey]: value }); if (ret) return `${fieldKey} already exists`; } } exports.Constraint = Constraint; Constraint.ALLOWED_CONSTRAINTS = [ ConstraintTypes.REQUIRED, ConstraintTypes.IN, ConstraintTypes.DEFAULT, ConstraintTypes.LENGTH, ConstraintTypes.NUMBER, ConstraintTypes.UNIQUE, ConstraintTypes.MATCH, ConstraintTypes.EXISTS, ConstraintTypes.CONDITION, ConstraintTypes.COND, ConstraintTypes.NOT_EXISTS, ];