diff --git a/docs/zh-CN/development/guide/middleware.md b/docs/zh-CN/development/guide/middleware.md index cc8aad93c..e3bc66b71 100644 --- a/docs/zh-CN/development/guide/middleware.md +++ b/docs/zh-CN/development/guide/middleware.md @@ -145,5 +145,4 @@ app.resourcer.use(async (ctx, next) => { 待补充 -- samples/xxx -- samples/yyy \ No newline at end of file +- [samples/ratelimit](https://github.com/nocobase/nocobase/blob/main/packages/samples/ratelimit/) IP rate-limiting diff --git a/packages/samples/ratelimit/README.md b/packages/samples/ratelimit/README.md new file mode 100644 index 000000000..366b32716 --- /dev/null +++ b/packages/samples/ratelimit/README.md @@ -0,0 +1,29 @@ +# Rate limiter middleware sample + +## Register + +```ts +yarn pm add sample-ratelimit +``` + +## Activate + +```bash +yarn pm enable sample-ratelimit +``` + +## Launch the app + +```bash +# for development +yarn dev + +# for production +yarn build +yarn start +``` + +## Demo + +[gif] + diff --git a/packages/samples/ratelimit/client.d.ts b/packages/samples/ratelimit/client.d.ts new file mode 100755 index 000000000..765db9222 --- /dev/null +++ b/packages/samples/ratelimit/client.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +export * from './lib/client'; +export { default } from './lib/client'; + diff --git a/packages/samples/ratelimit/client.js b/packages/samples/ratelimit/client.js new file mode 100755 index 000000000..238820257 --- /dev/null +++ b/packages/samples/ratelimit/client.js @@ -0,0 +1,30 @@ +"use strict"; + +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +var _index = _interopRequireWildcard(require("./lib/client")); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = {}; +Object.defineProperty(exports, "default", { + enumerable: true, + get: function get() { + return _index.default; + } +}); + +Object.keys(_index).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _index[key]; + } + }); +}); diff --git a/packages/samples/ratelimit/package.json b/packages/samples/ratelimit/package.json new file mode 100644 index 000000000..95a6f31c6 --- /dev/null +++ b/packages/samples/ratelimit/package.json @@ -0,0 +1,13 @@ +{ + "name": "@nocobase/plugin-sample-ratelimit", + "version": "0.7.4-alpha.7", + "main": "lib/server/index.js", + "dependencies": { + "koa-ratelimit": "^5.0.1" + }, + "devDependencies": { + "@nocobase/client": "0.7.4-alpha.7", + "@nocobase/server": "0.7.4-alpha.7", + "@nocobase/test": "0.7.4-alpha.7" + } +} diff --git a/packages/samples/ratelimit/server.d.ts b/packages/samples/ratelimit/server.d.ts new file mode 100755 index 000000000..e70edb928 --- /dev/null +++ b/packages/samples/ratelimit/server.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +export * from './lib/server'; +export { default } from './lib/server'; + diff --git a/packages/samples/ratelimit/server.js b/packages/samples/ratelimit/server.js new file mode 100755 index 000000000..d06a7eb92 --- /dev/null +++ b/packages/samples/ratelimit/server.js @@ -0,0 +1,30 @@ +"use strict"; + +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +var _index = _interopRequireWildcard(require("./lib/server")); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = {}; +Object.defineProperty(exports, "default", { + enumerable: true, + get: function get() { + return _index.default; + } +}); + +Object.keys(_index).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function get() { + return _index[key]; + } + }); +}); diff --git a/packages/samples/ratelimit/src/client/index.tsx b/packages/samples/ratelimit/src/client/index.tsx new file mode 100644 index 000000000..db735f78d --- /dev/null +++ b/packages/samples/ratelimit/src/client/index.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default React.memo((props) => { + return <>{props.children}; +}); diff --git a/packages/samples/ratelimit/src/index.ts b/packages/samples/ratelimit/src/index.ts new file mode 100644 index 000000000..7ddad5814 --- /dev/null +++ b/packages/samples/ratelimit/src/index.ts @@ -0,0 +1 @@ +export { default } from './server'; diff --git a/packages/samples/ratelimit/src/server/index.ts b/packages/samples/ratelimit/src/server/index.ts new file mode 100644 index 000000000..5c679968c --- /dev/null +++ b/packages/samples/ratelimit/src/server/index.ts @@ -0,0 +1,42 @@ +import { InstallOptions, Plugin } from '@nocobase/server'; +import ratelimit from 'koa-ratelimit'; + +export class CustomPagePlugin extends Plugin { + getName(): string { + return this.getPackageName(__dirname); + } + + beforeLoad() { + const db = new Map(); + this.app.use( + ratelimit({ + driver: 'memory', + db: db, + duration: 60000, + errorMessage: 'Sometimes You Just Have to Slow Down.', + id: (ctx) => ctx.ip, + headers: { + remaining: 'Rate-Limit-Remaining', + reset: 'Rate-Limit-Reset', + total: 'Rate-Limit-Total', + }, + max: 20, + disableHeader: false, + whitelist: (ctx) => { + // some logic that returns a boolean + }, + blacklist: (ctx) => { + // some logic that returns a boolean + }, + }), + ); + } + + async load() {} + + async install(options: InstallOptions) { + // TODO + } +} + +export default CustomPagePlugin; diff --git a/yarn.lock b/yarn.lock index eeafcd555..8aacf87bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6687,6 +6687,11 @@ async-mutex@^0.3.2: dependencies: tslib "^2.3.1" +async-ratelimiter@^1.3.0: + version "1.3.3" + resolved "https://registry.npmjs.org/async-ratelimiter/-/async-ratelimiter-1.3.3.tgz#29e37eb2367ace9b824d47f1d46694599a1da87e" + integrity sha512-XzLQYuDRJzKXEXrDtKu/CmoWZ0/j39kGW3Mhc/wr1SMtap1QppxrALMrX63eQK7xfXglbfAdHtNrhgix9aOsIw== + async-validator@^4.0.2: version "4.0.7" resolved "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz#034a0fd2103a6b2ebf010da75183bec299247afe" @@ -14206,6 +14211,15 @@ koa-convert@^2.0.0: co "^4.6.0" koa-compose "^4.1.0" +koa-ratelimit@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/koa-ratelimit/-/koa-ratelimit-5.0.1.tgz#e4a42c9f43510351600ba052e788fae7d4bf559d" + integrity sha512-H7IEkNS/b18Uwtm3RIvAK3orJE8ew8wEBsnezlQWz7GTWqEnDtbTNfTedVXjj07gyh8gWTkEdODEXRquGCBqmg== + dependencies: + async-ratelimiter "^1.3.0" + debug "^4.1.1" + ms "^2.1.2" + koa-send@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79" @@ -15532,7 +15546,7 @@ mri@^1.1.5: resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== -ms@*, ms@^2.0.0, ms@^2.1.1: +ms@*, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==