@eggjs/jsonp 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/commonjs/app/extend/application.d.ts +10 -0
  4. package/dist/commonjs/app/extend/application.js +115 -0
  5. package/dist/commonjs/app/extend/context.d.ts +15 -0
  6. package/dist/commonjs/app/extend/context.js +35 -0
  7. package/dist/commonjs/config/config.default.d.ts +5 -0
  8. package/dist/commonjs/config/config.default.js +11 -0
  9. package/dist/commonjs/error/JSONPForbiddenReferrerError.d.ts +5 -0
  10. package/dist/commonjs/error/JSONPForbiddenReferrerError.js +16 -0
  11. package/dist/commonjs/index.d.ts +1 -0
  12. package/dist/commonjs/index.js +4 -0
  13. package/dist/commonjs/lib/private_key.d.ts +1 -0
  14. package/dist/commonjs/lib/private_key.js +5 -0
  15. package/dist/commonjs/package.json +3 -0
  16. package/dist/commonjs/types.d.ts +50 -0
  17. package/dist/commonjs/types.js +3 -0
  18. package/dist/esm/app/extend/application.d.ts +10 -0
  19. package/dist/esm/app/extend/application.js +112 -0
  20. package/dist/esm/app/extend/context.d.ts +15 -0
  21. package/dist/esm/app/extend/context.js +32 -0
  22. package/dist/esm/config/config.default.d.ts +5 -0
  23. package/dist/esm/config/config.default.js +9 -0
  24. package/dist/esm/error/JSONPForbiddenReferrerError.d.ts +5 -0
  25. package/dist/esm/error/JSONPForbiddenReferrerError.js +12 -0
  26. package/dist/esm/index.d.ts +1 -0
  27. package/dist/esm/index.js +2 -0
  28. package/dist/esm/lib/private_key.d.ts +1 -0
  29. package/dist/esm/lib/private_key.js +2 -0
  30. package/dist/esm/package.json +3 -0
  31. package/dist/esm/types.d.ts +50 -0
  32. package/dist/esm/types.js +2 -0
  33. package/dist/package.json +4 -0
  34. package/package.json +94 -0
  35. package/src/app/extend/application.ts +131 -0
  36. package/src/app/extend/context.ts +34 -0
  37. package/src/config/config.default.ts +10 -0
  38. package/src/error/JSONPForbiddenReferrerError.ts +12 -0
  39. package/src/index.ts +1 -0
  40. package/src/lib/private_key.ts +1 -0
  41. package/src/types.ts +55 -0
  42. package/src/typings/index.d.ts +4 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Alibaba Group Holding Limited and other contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @eggjs/jsonp
2
+
3
+ [![NPM version][npm-image]][npm-url]
4
+ [![Node.js CI](https://github.com/eggjs/jsonp/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/jsonp/actions/workflows/nodejs.yml)
5
+ [![Test coverage][codecov-image]][codecov-url]
6
+ [![Known Vulnerabilities][snyk-image]][snyk-url]
7
+ [![npm download][download-image]][download-url]
8
+ [![Node.js Version](https://img.shields.io/node/v/@eggjs/jsonp.svg?style=flat)](https://nodejs.org/en/download/)
9
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
10
+
11
+ [npm-image]: https://img.shields.io/npm/v/@eggjs/jsonp.svg?style=flat-square
12
+ [npm-url]: https://npmjs.org/package/@eggjs/jsonp
13
+ [codecov-image]: https://img.shields.io/codecov/c/github/eggjs/jsonp.svg?style=flat-square
14
+ [codecov-url]: https://codecov.io/github/eggjs/jsonp?branch=master
15
+ [snyk-image]: https://snyk.io/test/npm/@eggjs/jsonp/badge.svg?style=flat-square
16
+ [snyk-url]: https://snyk.io/test/npm/@eggjs/jsonp
17
+ [download-image]: https://img.shields.io/npm/dm/@eggjs/jsonp.svg?style=flat-square
18
+ [download-url]: https://npmjs.org/package/@eggjs/jsonp
19
+
20
+ An egg plugin for jsonp support.
21
+
22
+ ## Requirements
23
+
24
+ - egg >= 4.x
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ npm i @eggjs/jsonp
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```ts
35
+ // {app_root}/config/plugin.ts
36
+
37
+ export default {
38
+ jsonp: {
39
+ enable: true,
40
+ package: '@eggjs/jsonp',
41
+ },
42
+ };
43
+ ```
44
+
45
+ ## Configuration
46
+
47
+ - {String|Array} callback - jsonp callback method key, default to `[ '_callback', 'callback' ]`
48
+ - {Number} limit - callback method name's max length, default to `50`
49
+ - {Boolean} csrf - enable csrf check or not. default to false
50
+ - {String|RegExp|Array} whiteList - referrer white list
51
+
52
+ if whiteList's type is `RegExp`, referrer must match `whiteList`, pay attention to the first `^` and last `/`.
53
+
54
+ ```ts
55
+ export default {
56
+ jsonp: {
57
+ whiteList: /^https?:\/\/test.com\//,
58
+ },
59
+ };
60
+
61
+ // matchs referrer:
62
+ // https://test.com/hello
63
+ // http://test.com/
64
+ ```
65
+
66
+ if whiteList's type is `String` and starts with `.`:
67
+
68
+ ```ts
69
+ export default {
70
+ jsonp: {
71
+ whiteList: '.test.com',
72
+ },
73
+ };
74
+
75
+ // matchs domain test.com:
76
+ // https://test.com/hello
77
+ // http://test.com/
78
+
79
+ // matchs subdomain
80
+ // https://sub.test.com/hello
81
+ // http://sub.sub.test.com/
82
+ ```
83
+
84
+ if whiteList's type is `String` and not starts with `.`:
85
+
86
+ ```ts
87
+ export default {
88
+ jsonp: {
89
+ whiteList: 'sub.test.com',
90
+ },
91
+ };
92
+
93
+ // only matchs domain sub.test.com:
94
+ // https://sub.test.com/hello
95
+ // http://sub.test.com/
96
+ ```
97
+
98
+ whiteList also can be an array:
99
+
100
+ ```ts
101
+ export default {
102
+ jsonp: {
103
+ whiteList: [ '.foo.com', '.bar.com' ],
104
+ },
105
+ };
106
+ ```
107
+
108
+ see [config/config.default.ts](https://github.com/eggjs/jsonp/blob/master/src/config/config.default.ts) for more detail.
109
+
110
+ ## API
111
+
112
+ - ctx.acceptJSONP - detect if response should be jsonp, readonly
113
+
114
+ ## Example
115
+
116
+ In `app/router.ts`
117
+
118
+ ```ts
119
+ // Create once and use in any router you want to support jsonp.
120
+ const jsonp = app.jsonp();
121
+
122
+ app.get('/default', jsonp, 'jsonp.index');
123
+ app.get('/another', jsonp, 'jsonp.another');
124
+
125
+ // Customize by create another jsonp middleware with specific configurations.
126
+ app.get('/customize', app.jsonp({ callback: 'fn' }), 'jsonp.customize');
127
+ ```
128
+
129
+ ## Questions & Suggestions
130
+
131
+ Please open an issue [here](https://github.com/eggjs/egg/issues).
132
+
133
+ ## License
134
+
135
+ [MIT](LICENSE)
136
+
137
+ ## Contributors
138
+
139
+ [![Contributors](https://contrib.rocks/image?repo=eggjs/jsonp)](https://github.com/eggjs/jsonp/graphs/contributors)
140
+
141
+ Made with [contributors-img](https://contrib.rocks).
@@ -0,0 +1,10 @@
1
+ import { EggCore, type MiddlewareFunc } from '@eggjs/core';
2
+ import { JSONPConfig } from '../../types.js';
3
+ export default class JSONPApplication extends EggCore {
4
+ /**
5
+ * return a middleware to enable jsonp response.
6
+ * will do some security check inside.
7
+ * @public
8
+ */
9
+ jsonp(initOptions?: Partial<JSONPConfig>): MiddlewareFunc;
10
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_util_1 = require("node:util");
4
+ const node_url_1 = require("node:url");
5
+ const core_1 = require("@eggjs/core");
6
+ const private_key_js_1 = require("../../lib/private_key.js");
7
+ const JSONPForbiddenReferrerError_js_1 = require("../../error/JSONPForbiddenReferrerError.js");
8
+ const debug = (0, node_util_1.debuglog)('@egg/jsonp/app/extend/application');
9
+ class JSONPApplication extends core_1.EggCore {
10
+ /**
11
+ * return a middleware to enable jsonp response.
12
+ * will do some security check inside.
13
+ * @public
14
+ */
15
+ jsonp(initOptions = {}) {
16
+ const options = {
17
+ ...this.config.jsonp,
18
+ ...initOptions,
19
+ };
20
+ if (!Array.isArray(options.callback)) {
21
+ options.callback = [options.callback];
22
+ }
23
+ const csrfEnable = this.plugins.security && this.plugins.security.enable // security enable
24
+ && this.config.security.csrf && this.config.security.csrf.enable !== false // csrf enable
25
+ && options.csrf; // jsonp csrf enabled
26
+ const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);
27
+ if (!csrfEnable && !validateReferrer) {
28
+ this.coreLogger.warn('[@eggjs/jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');
29
+ }
30
+ /**
31
+ * jsonp request security check, pass if
32
+ *
33
+ * 1. hit referrer white list
34
+ * 2. or pass csrf check
35
+ * 3. both check are disabled
36
+ *
37
+ * @param {Context} ctx request context
38
+ */
39
+ function securityAssert(ctx) {
40
+ // all disabled. don't need check
41
+ if (!csrfEnable && !validateReferrer)
42
+ return;
43
+ // pass referrer check
44
+ const referrer = ctx.get('referrer');
45
+ if (validateReferrer && validateReferrer(referrer))
46
+ return;
47
+ if (csrfEnable && validateCsrf(ctx))
48
+ return;
49
+ throw new JSONPForbiddenReferrerError_js_1.JSONPForbiddenReferrerError('jsonp request security validate failed', referrer, 403);
50
+ }
51
+ return async function jsonp(ctx, next) {
52
+ const jsonpFunction = getJsonpFunction(ctx.query, options.callback);
53
+ ctx[private_key_js_1.JSONP_CONFIG] = {
54
+ jsonpFunction,
55
+ options,
56
+ };
57
+ // before handle request, must do some security checks
58
+ securityAssert(ctx);
59
+ await next();
60
+ // generate jsonp body
61
+ ctx.createJsonpBody(ctx.body);
62
+ };
63
+ }
64
+ }
65
+ exports.default = JSONPApplication;
66
+ function createValidateReferer(whiteList) {
67
+ if (!Array.isArray(whiteList)) {
68
+ whiteList = [whiteList];
69
+ }
70
+ return (referrer) => {
71
+ let parsed;
72
+ for (const rule of whiteList) {
73
+ if (rule instanceof RegExp) {
74
+ if (rule.test(referrer)) {
75
+ // regexp(/^https?:\/\/github.com\//): test the referrer with rule
76
+ return true;
77
+ }
78
+ continue;
79
+ }
80
+ parsed = parsed ?? (0, node_url_1.parse)(referrer);
81
+ const hostname = parsed.hostname || '';
82
+ // check if referrer's hostname match the string rule
83
+ if (rule[0] === '.' &&
84
+ (hostname.endsWith(rule) || hostname === rule.slice(1))) {
85
+ // string start with `.`(.github.com): referrer's hostname must ends with rule
86
+ return true;
87
+ }
88
+ else if (hostname === rule) {
89
+ // string not start with `.`(github.com): referrer's hostname must strict equal to rule
90
+ return true;
91
+ }
92
+ }
93
+ // no rule matched
94
+ return false;
95
+ };
96
+ }
97
+ function validateCsrf(ctx) {
98
+ try {
99
+ // TODO(fengmk2): remove this when @eggjs/security support ctx.assertCsrf type define
100
+ ctx.assertCsrf();
101
+ return true;
102
+ }
103
+ catch (err) {
104
+ debug('validate csrf failed: %s', err);
105
+ return false;
106
+ }
107
+ }
108
+ function getJsonpFunction(query, callbacks) {
109
+ for (const callback of callbacks) {
110
+ if (query[callback]) {
111
+ return query[callback];
112
+ }
113
+ }
114
+ }
115
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbGljYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBwL2V4dGVuZC9hcHBsaWNhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlDQUFxQztBQUNyQyx1Q0FBc0U7QUFFdEUsc0NBQTJEO0FBQzNELDZEQUF3RDtBQUV4RCwrRkFBeUY7QUFHekYsTUFBTSxLQUFLLEdBQUcsSUFBQSxvQkFBUSxFQUFDLG1DQUFtQyxDQUFDLENBQUM7QUFFNUQsTUFBcUIsZ0JBQWlCLFNBQVEsY0FBTztJQUNuRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLGNBQW9DLEVBQUU7UUFDMUMsTUFBTSxPQUFPLEdBQUc7WUFDZCxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSztZQUNwQixHQUFHLFdBQVc7U0FDeUIsQ0FBQztRQUMxQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztZQUNyQyxPQUFPLENBQUMsUUFBUSxHQUFHLENBQUUsT0FBTyxDQUFDLFFBQVEsQ0FBRSxDQUFDO1FBQzFDLENBQUM7UUFFRCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsa0JBQWtCO2VBQ3RGLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLEtBQUssQ0FBQyxjQUFjO2VBQ3RGLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxxQkFBcUI7UUFFeEMsTUFBTSxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUV2RixJQUFJLENBQUMsVUFBVSxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUNyQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxrRkFBa0YsQ0FBQyxDQUFDO1FBQzNHLENBQUM7UUFDRDs7Ozs7Ozs7V0FRRztRQUNILFNBQVMsY0FBYyxDQUFDLEdBQWlCO1lBQ3ZDLGlDQUFpQztZQUNqQyxJQUFJLENBQUMsVUFBVSxJQUFJLENBQUMsZ0JBQWdCO2dCQUFFLE9BQU87WUFFN0Msc0JBQXNCO1lBQ3RCLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQVMsVUFBVSxDQUFDLENBQUM7WUFDN0MsSUFBSSxnQkFBZ0IsSUFBSSxnQkFBZ0IsQ0FBQyxRQUFRLENBQUM7Z0JBQUUsT0FBTztZQUMzRCxJQUFJLFVBQVUsSUFBSSxZQUFZLENBQUMsR0FBRyxDQUFDO2dCQUFFLE9BQU87WUFFNUMsTUFBTSxJQUFJLDREQUEyQixDQUNuQyx3Q0FBd0MsRUFDeEMsUUFBUSxFQUNSLEdBQUcsQ0FBQyxDQUFDO1FBQ1QsQ0FBQztRQUVELE9BQU8sS0FBSyxVQUFVLEtBQUssQ0FBQyxHQUFpQixFQUFFLElBQUk7WUFDakQsTUFBTSxhQUFhLEdBQUcsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7WUFFcEUsR0FBRyxDQUFDLDZCQUFZLENBQUMsR0FBRztnQkFDbEIsYUFBYTtnQkFDYixPQUFPO2FBQ1IsQ0FBQztZQUVGLHNEQUFzRDtZQUN0RCxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7WUFFcEIsTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUViLHNCQUFzQjtZQUN0QixHQUFHLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoQyxDQUFDLENBQUM7SUFDSixDQUFDO0NBQ0Y7QUFqRUQsbUNBaUVDO0FBRUQsU0FBUyxxQkFBcUIsQ0FBQyxTQUE2QztJQUMxRSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1FBQzlCLFNBQVMsR0FBRyxDQUFFLFNBQVMsQ0FBRSxDQUFDO0lBQzVCLENBQUM7SUFFRCxPQUFPLENBQUMsUUFBZ0IsRUFBRSxFQUFFO1FBQzFCLElBQUksTUFBc0MsQ0FBQztRQUMzQyxLQUFLLE1BQU0sSUFBSSxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQzdCLElBQUksSUFBSSxZQUFZLE1BQU0sRUFBRSxDQUFDO2dCQUMzQixJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztvQkFDeEIsa0VBQWtFO29CQUNsRSxPQUFPLElBQUksQ0FBQztnQkFDZCxDQUFDO2dCQUNELFNBQVM7WUFDWCxDQUFDO1lBRUQsTUFBTSxHQUFHLE1BQU0sSUFBSSxJQUFBLGdCQUFRLEVBQUMsUUFBUSxDQUFDLENBQUM7WUFDdEMsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLFFBQVEsSUFBSSxFQUFFLENBQUM7WUFFdkMscURBQXFEO1lBQ3JELElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUc7Z0JBQ2pCLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxRQUFRLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7Z0JBQzFELDhFQUE4RTtnQkFDOUUsT0FBTyxJQUFJLENBQUM7WUFDZCxDQUFDO2lCQUFNLElBQUksUUFBUSxLQUFLLElBQUksRUFBRSxDQUFDO2dCQUM3Qix1RkFBdUY7Z0JBQ3ZGLE9BQU8sSUFBSSxDQUFDO1lBQ2QsQ0FBQztRQUNILENBQUM7UUFFRCxrQkFBa0I7UUFDbEIsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQsU0FBUyxZQUFZLENBQUMsR0FBUTtJQUM1QixJQUFJLENBQUM7UUFDSCxxRkFBcUY7UUFDckYsR0FBRyxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2pCLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFDYixLQUFLLENBQUMsMEJBQTBCLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDdkMsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsS0FBcUIsRUFBRSxTQUFtQjtJQUNsRSxLQUFLLE1BQU0sUUFBUSxJQUFJLFNBQVMsRUFBRSxDQUFDO1FBQ2pDLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7WUFDcEIsT0FBTyxLQUFLLENBQUMsUUFBUSxDQUFXLENBQUM7UUFDbkMsQ0FBQztJQUNILENBQUM7QUFDSCxDQUFDIn0=
@@ -0,0 +1,15 @@
1
+ import { Context } from '@eggjs/core';
2
+ export default class JSONPContext extends Context {
3
+ /**
4
+ * detect if response should be jsonp
5
+ */
6
+ get acceptJSONP(): boolean;
7
+ /**
8
+ * JSONP wrap body function
9
+ * Set jsonp response wrap function, other plugin can use it.
10
+ * If not necessary, please don't use this method in your application code.
11
+ * @param {Object} body response body
12
+ * @private
13
+ */
14
+ createJsonpBody(body: any): void;
15
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsonp_body_1 = require("jsonp-body");
4
+ const core_1 = require("@eggjs/core");
5
+ const private_key_js_1 = require("../../lib/private_key.js");
6
+ class JSONPContext extends core_1.Context {
7
+ /**
8
+ * detect if response should be jsonp
9
+ */
10
+ get acceptJSONP() {
11
+ const jsonpConfig = Reflect.get(this, private_key_js_1.JSONP_CONFIG);
12
+ return !!(jsonpConfig?.jsonpFunction);
13
+ }
14
+ /**
15
+ * JSONP wrap body function
16
+ * Set jsonp response wrap function, other plugin can use it.
17
+ * If not necessary, please don't use this method in your application code.
18
+ * @param {Object} body response body
19
+ * @private
20
+ */
21
+ createJsonpBody(body) {
22
+ const jsonpConfig = Reflect.get(this, private_key_js_1.JSONP_CONFIG);
23
+ if (!jsonpConfig?.jsonpFunction) {
24
+ this.body = body;
25
+ return;
26
+ }
27
+ this.set('x-content-type-options', 'nosniff');
28
+ this.type = 'js';
29
+ body = body === undefined ? null : body;
30
+ // protect from jsonp xss
31
+ this.body = (0, jsonp_body_1.jsonp)(body, jsonpConfig.jsonpFunction, jsonpConfig.options);
32
+ }
33
+ }
34
+ exports.default = JSONPContext;
35
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcHAvZXh0ZW5kL2NvbnRleHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSwyQ0FBZ0Q7QUFDaEQsc0NBQXNDO0FBQ3RDLDZEQUF3RDtBQUV4RCxNQUFxQixZQUFhLFNBQVEsY0FBTztJQUMvQzs7T0FFRztJQUNILElBQUksV0FBVztRQUNiLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLDZCQUFZLENBQVEsQ0FBQztRQUMzRCxPQUFPLENBQUMsQ0FBQyxDQUFDLFdBQVcsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUN4QyxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsZUFBZSxDQUFDLElBQVM7UUFDdkIsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsNkJBQVksQ0FBUSxDQUFDO1FBQzNELElBQUksQ0FBQyxXQUFXLEVBQUUsYUFBYSxFQUFFLENBQUM7WUFDaEMsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7WUFDakIsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLENBQUMsR0FBRyxDQUFDLHdCQUF3QixFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBQzlDLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ2pCLElBQUksR0FBRyxJQUFJLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUN4Qyx5QkFBeUI7UUFDekIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFBLGtCQUFTLEVBQUMsSUFBSSxFQUFFLFdBQVcsQ0FBQyxhQUFhLEVBQUUsV0FBVyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzlFLENBQUM7Q0FDRjtBQTdCRCwrQkE2QkMifQ==
@@ -0,0 +1,5 @@
1
+ import type { JSONPConfig } from '../types.js';
2
+ declare const _default: {
3
+ jsonp: JSONPConfig;
4
+ };
5
+ export default _default;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ jsonp: {
5
+ limit: 50,
6
+ callback: ['_callback', 'callback'],
7
+ csrf: false,
8
+ whiteList: undefined,
9
+ },
10
+ };
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmRlZmF1bHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29uZmlnL2NvbmZpZy5kZWZhdWx0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBRUEsa0JBQWU7SUFDYixLQUFLLEVBQUU7UUFDTCxLQUFLLEVBQUUsRUFBRTtRQUNULFFBQVEsRUFBRSxDQUFFLFdBQVcsRUFBRSxVQUFVLENBQUU7UUFDckMsSUFBSSxFQUFFLEtBQUs7UUFDWCxTQUFTLEVBQUUsU0FBUztLQUNOO0NBQ2pCLENBQUMifQ==
@@ -0,0 +1,5 @@
1
+ export declare class JSONPForbiddenReferrerError extends Error {
2
+ referrer: string;
3
+ status: number;
4
+ constructor(message: string, referrer: string, status: number);
5
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JSONPForbiddenReferrerError = void 0;
4
+ class JSONPForbiddenReferrerError extends Error {
5
+ referrer;
6
+ status;
7
+ constructor(message, referrer, status) {
8
+ super(message);
9
+ this.name = this.constructor.name;
10
+ this.referrer = referrer;
11
+ this.status = status;
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ exports.JSONPForbiddenReferrerError = JSONPForbiddenReferrerError;
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSlNPTlBGb3JiaWRkZW5SZWZlcnJlckVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2Vycm9yL0pTT05QRm9yYmlkZGVuUmVmZXJyZXJFcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxNQUFhLDJCQUE0QixTQUFRLEtBQUs7SUFDcEQsUUFBUSxDQUFTO0lBQ2pCLE1BQU0sQ0FBUztJQUVmLFlBQVksT0FBZSxFQUFFLFFBQWdCLEVBQUUsTUFBYztRQUMzRCxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDO1FBQ2xDLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQ2xELENBQUM7Q0FDRjtBQVhELGtFQVdDIn0=
@@ -0,0 +1 @@
1
+ import './types.js';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./types.js");
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxzQkFBb0IifQ==
@@ -0,0 +1 @@
1
+ export declare const JSONP_CONFIG: unique symbol;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JSONP_CONFIG = void 0;
4
+ exports.JSONP_CONFIG = Symbol('jsonp#config');
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpdmF0ZV9rZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL3ByaXZhdGVfa2V5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFhLFFBQUEsWUFBWSxHQUFHLE1BQU0sQ0FBQyxjQUFjLENBQUMsQ0FBQyJ9
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,50 @@
1
+ import type { MiddlewareFunc } from '@eggjs/core';
2
+ /**
3
+ * jsonp options
4
+ * @member Config#jsonp
5
+ */
6
+ export interface JSONPConfig {
7
+ /**
8
+ * jsonp callback methods key, default to `['_callback', 'callback' ]`
9
+ */
10
+ callback: string[] | string;
11
+ /**
12
+ * callback method name's max length, default to `50`
13
+ */
14
+ limit: number;
15
+ /**
16
+ * enable csrf check or not, default to `false`
17
+ */
18
+ csrf: boolean;
19
+ /**
20
+ * referrer white list, default to `undefined`
21
+ */
22
+ whiteList?: string | RegExp | (string | RegExp)[];
23
+ }
24
+ declare module '@eggjs/core' {
25
+ interface EggAppConfig {
26
+ jsonp: JSONPConfig;
27
+ }
28
+ interface Context {
29
+ /**
30
+ * detect if response should be jsonp
31
+ */
32
+ acceptJSONP: boolean;
33
+ /**
34
+ * JSONP wrap body function
35
+ * Set jsonp response wrap function, other plugin can use it.
36
+ * If not necessary, please don't use this method in your application code.
37
+ * @param {Object} body response body
38
+ * @private
39
+ */
40
+ createJsonpBody(body: any): void;
41
+ }
42
+ interface EggCore {
43
+ /**
44
+ * return a middleware to enable jsonp response.
45
+ * will do some security check inside.
46
+ * @public
47
+ */
48
+ jsonp(initOptions?: Partial<JSONPConfig>): MiddlewareFunc;
49
+ }
50
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -0,0 +1,10 @@
1
+ import { EggCore, type MiddlewareFunc } from '@eggjs/core';
2
+ import { JSONPConfig } from '../../types.js';
3
+ export default class JSONPApplication extends EggCore {
4
+ /**
5
+ * return a middleware to enable jsonp response.
6
+ * will do some security check inside.
7
+ * @public
8
+ */
9
+ jsonp(initOptions?: Partial<JSONPConfig>): MiddlewareFunc;
10
+ }
@@ -0,0 +1,112 @@
1
+ import { debuglog } from 'node:util';
2
+ import { parse as urlParse } from 'node:url';
3
+ import { EggCore } from '@eggjs/core';
4
+ import { JSONP_CONFIG } from '../../lib/private_key.js';
5
+ import { JSONPForbiddenReferrerError } from '../../error/JSONPForbiddenReferrerError.js';
6
+ const debug = debuglog('@egg/jsonp/app/extend/application');
7
+ export default class JSONPApplication extends EggCore {
8
+ /**
9
+ * return a middleware to enable jsonp response.
10
+ * will do some security check inside.
11
+ * @public
12
+ */
13
+ jsonp(initOptions = {}) {
14
+ const options = {
15
+ ...this.config.jsonp,
16
+ ...initOptions,
17
+ };
18
+ if (!Array.isArray(options.callback)) {
19
+ options.callback = [options.callback];
20
+ }
21
+ const csrfEnable = this.plugins.security && this.plugins.security.enable // security enable
22
+ && this.config.security.csrf && this.config.security.csrf.enable !== false // csrf enable
23
+ && options.csrf; // jsonp csrf enabled
24
+ const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);
25
+ if (!csrfEnable && !validateReferrer) {
26
+ this.coreLogger.warn('[@eggjs/jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');
27
+ }
28
+ /**
29
+ * jsonp request security check, pass if
30
+ *
31
+ * 1. hit referrer white list
32
+ * 2. or pass csrf check
33
+ * 3. both check are disabled
34
+ *
35
+ * @param {Context} ctx request context
36
+ */
37
+ function securityAssert(ctx) {
38
+ // all disabled. don't need check
39
+ if (!csrfEnable && !validateReferrer)
40
+ return;
41
+ // pass referrer check
42
+ const referrer = ctx.get('referrer');
43
+ if (validateReferrer && validateReferrer(referrer))
44
+ return;
45
+ if (csrfEnable && validateCsrf(ctx))
46
+ return;
47
+ throw new JSONPForbiddenReferrerError('jsonp request security validate failed', referrer, 403);
48
+ }
49
+ return async function jsonp(ctx, next) {
50
+ const jsonpFunction = getJsonpFunction(ctx.query, options.callback);
51
+ ctx[JSONP_CONFIG] = {
52
+ jsonpFunction,
53
+ options,
54
+ };
55
+ // before handle request, must do some security checks
56
+ securityAssert(ctx);
57
+ await next();
58
+ // generate jsonp body
59
+ ctx.createJsonpBody(ctx.body);
60
+ };
61
+ }
62
+ }
63
+ function createValidateReferer(whiteList) {
64
+ if (!Array.isArray(whiteList)) {
65
+ whiteList = [whiteList];
66
+ }
67
+ return (referrer) => {
68
+ let parsed;
69
+ for (const rule of whiteList) {
70
+ if (rule instanceof RegExp) {
71
+ if (rule.test(referrer)) {
72
+ // regexp(/^https?:\/\/github.com\//): test the referrer with rule
73
+ return true;
74
+ }
75
+ continue;
76
+ }
77
+ parsed = parsed ?? urlParse(referrer);
78
+ const hostname = parsed.hostname || '';
79
+ // check if referrer's hostname match the string rule
80
+ if (rule[0] === '.' &&
81
+ (hostname.endsWith(rule) || hostname === rule.slice(1))) {
82
+ // string start with `.`(.github.com): referrer's hostname must ends with rule
83
+ return true;
84
+ }
85
+ else if (hostname === rule) {
86
+ // string not start with `.`(github.com): referrer's hostname must strict equal to rule
87
+ return true;
88
+ }
89
+ }
90
+ // no rule matched
91
+ return false;
92
+ };
93
+ }
94
+ function validateCsrf(ctx) {
95
+ try {
96
+ // TODO(fengmk2): remove this when @eggjs/security support ctx.assertCsrf type define
97
+ ctx.assertCsrf();
98
+ return true;
99
+ }
100
+ catch (err) {
101
+ debug('validate csrf failed: %s', err);
102
+ return false;
103
+ }
104
+ }
105
+ function getJsonpFunction(query, callbacks) {
106
+ for (const callback of callbacks) {
107
+ if (query[callback]) {
108
+ return query[callback];
109
+ }
110
+ }
111
+ }
112
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbGljYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBwL2V4dGVuZC9hcHBsaWNhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxLQUFLLElBQUksUUFBUSxFQUEyQixNQUFNLFVBQVUsQ0FBQztBQUV0RSxPQUFPLEVBQUUsT0FBTyxFQUF1QixNQUFNLGFBQWEsQ0FBQztBQUMzRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFeEQsT0FBTyxFQUFFLDJCQUEyQixFQUFFLE1BQU0sNENBQTRDLENBQUM7QUFHekYsTUFBTSxLQUFLLEdBQUcsUUFBUSxDQUFDLG1DQUFtQyxDQUFDLENBQUM7QUFFNUQsTUFBTSxDQUFDLE9BQU8sT0FBTyxnQkFBaUIsU0FBUSxPQUFPO0lBQ25EOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsY0FBb0MsRUFBRTtRQUMxQyxNQUFNLE9BQU8sR0FBRztZQUNkLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLO1lBQ3BCLEdBQUcsV0FBVztTQUN5QixDQUFDO1FBQzFDLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ3JDLE9BQU8sQ0FBQyxRQUFRLEdBQUcsQ0FBRSxPQUFPLENBQUMsUUFBUSxDQUFFLENBQUM7UUFDMUMsQ0FBQztRQUVELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxrQkFBa0I7ZUFDdEYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLEtBQUssS0FBSyxD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
@@ -0,0 +1,15 @@
1
+ import { Context } from '@eggjs/core';
2
+ export default class JSONPContext extends Context {
3
+ /**
4
+ * detect if response should be jsonp
5
+ */
6
+ get acceptJSONP(): boolean;
7
+ /**
8
+ * JSONP wrap body function
9
+ * Set jsonp response wrap function, other plugin can use it.
10
+ * If not necessary, please don't use this method in your application code.
11
+ * @param {Object} body response body
12
+ * @private
13
+ */
14
+ createJsonpBody(body: any): void;
15
+ }
@@ -0,0 +1,32 @@
1
+ import { jsonp as jsonpBody } from 'jsonp-body';
2
+ import { Context } from '@eggjs/core';
3
+ import { JSONP_CONFIG } from '../../lib/private_key.js';
4
+ export default class JSONPContext extends Context {
5
+ /**
6
+ * detect if response should be jsonp
7
+ */
8
+ get acceptJSONP() {
9
+ const jsonpConfig = Reflect.get(this, JSONP_CONFIG);
10
+ return !!(jsonpConfig?.jsonpFunction);
11
+ }
12
+ /**
13
+ * JSONP wrap body function
14
+ * Set jsonp response wrap function, other plugin can use it.
15
+ * If not necessary, please don't use this method in your application code.
16
+ * @param {Object} body response body
17
+ * @private
18
+ */
19
+ createJsonpBody(body) {
20
+ const jsonpConfig = Reflect.get(this, JSONP_CONFIG);
21
+ if (!jsonpConfig?.jsonpFunction) {
22
+ this.body = body;
23
+ return;
24
+ }
25
+ this.set('x-content-type-options', 'nosniff');
26
+ this.type = 'js';
27
+ body = body === undefined ? null : body;
28
+ // protect from jsonp xss
29
+ this.body = jsonpBody(body, jsonpConfig.jsonpFunction, jsonpConfig.options);
30
+ }
31
+ }
32
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcHAvZXh0ZW5kL2NvbnRleHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssSUFBSSxTQUFTLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDaEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFeEQsTUFBTSxDQUFDLE9BQU8sT0FBTyxZQUFhLFNBQVEsT0FBTztJQUMvQzs7T0FFRztJQUNILElBQUksV0FBVztRQUNiLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLFlBQVksQ0FBUSxDQUFDO1FBQzNELE9BQU8sQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ3hDLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxlQUFlLENBQUMsSUFBUztRQUN2QixNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxZQUFZLENBQVEsQ0FBQztRQUMzRCxJQUFJLENBQUMsV0FBVyxFQUFFLGFBQWEsRUFBRSxDQUFDO1lBQ2hDLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1lBQ2pCLE9BQU87UUFDVCxDQUFDO1FBRUQsSUFBSSxDQUFDLEdBQUcsQ0FBQyx3QkFBd0IsRUFBRSxTQUFTLENBQUMsQ0FBQztRQUM5QyxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLEdBQUcsSUFBSSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDeEMseUJBQXlCO1FBQ3pCLElBQUksQ0FBQyxJQUFJLEdBQUcsU0FBUyxDQUFDLElBQUksRUFBRSxXQUFXLENBQUMsYUFBYSxFQUFFLFdBQVcsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUM5RSxDQUFDO0NBQ0YifQ==
@@ -0,0 +1,5 @@
1
+ import type { JSONPConfig } from '../types.js';
2
+ declare const _default: {
3
+ jsonp: JSONPConfig;
4
+ };
5
+ export default _default;
@@ -0,0 +1,9 @@
1
+ export default {
2
+ jsonp: {
3
+ limit: 50,
4
+ callback: ['_callback', 'callback'],
5
+ csrf: false,
6
+ whiteList: undefined,
7
+ },
8
+ };
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmRlZmF1bHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29uZmlnL2NvbmZpZy5kZWZhdWx0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLGVBQWU7SUFDYixLQUFLLEVBQUU7UUFDTCxLQUFLLEVBQUUsRUFBRTtRQUNULFFBQVEsRUFBRSxDQUFFLFdBQVcsRUFBRSxVQUFVLENBQUU7UUFDckMsSUFBSSxFQUFFLEtBQUs7UUFDWCxTQUFTLEVBQUUsU0FBUztLQUNOO0NBQ2pCLENBQUMifQ==
@@ -0,0 +1,5 @@
1
+ export declare class JSONPForbiddenReferrerError extends Error {
2
+ referrer: string;
3
+ status: number;
4
+ constructor(message: string, referrer: string, status: number);
5
+ }
@@ -0,0 +1,12 @@
1
+ export class JSONPForbiddenReferrerError extends Error {
2
+ referrer;
3
+ status;
4
+ constructor(message, referrer, status) {
5
+ super(message);
6
+ this.name = this.constructor.name;
7
+ this.referrer = referrer;
8
+ this.status = status;
9
+ Error.captureStackTrace(this, this.constructor);
10
+ }
11
+ }
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSlNPTlBGb3JiaWRkZW5SZWZlcnJlckVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2Vycm9yL0pTT05QRm9yYmlkZGVuUmVmZXJyZXJFcnJvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxNQUFNLE9BQU8sMkJBQTRCLFNBQVEsS0FBSztJQUNwRCxRQUFRLENBQVM7SUFDakIsTUFBTSxDQUFTO0lBRWYsWUFBWSxPQUFlLEVBQUUsUUFBZ0IsRUFBRSxNQUFjO1FBQzNELEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNmLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUM7UUFDbEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7UUFDekIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsS0FBSyxDQUFDLGlCQUFpQixDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDbEQsQ0FBQztDQUNGIn0=
@@ -0,0 +1 @@
1
+ import './types.js';
@@ -0,0 +1,2 @@
1
+ import './types.js';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxZQUFZLENBQUMifQ==
@@ -0,0 +1 @@
1
+ export declare const JSONP_CONFIG: unique symbol;
@@ -0,0 +1,2 @@
1
+ export const JSONP_CONFIG = Symbol('jsonp#config');
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpdmF0ZV9rZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL3ByaXZhdGVfa2V5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUMifQ==
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,50 @@
1
+ import type { MiddlewareFunc } from '@eggjs/core';
2
+ /**
3
+ * jsonp options
4
+ * @member Config#jsonp
5
+ */
6
+ export interface JSONPConfig {
7
+ /**
8
+ * jsonp callback methods key, default to `['_callback', 'callback' ]`
9
+ */
10
+ callback: string[] | string;
11
+ /**
12
+ * callback method name's max length, default to `50`
13
+ */
14
+ limit: number;
15
+ /**
16
+ * enable csrf check or not, default to `false`
17
+ */
18
+ csrf: boolean;
19
+ /**
20
+ * referrer white list, default to `undefined`
21
+ */
22
+ whiteList?: string | RegExp | (string | RegExp)[];
23
+ }
24
+ declare module '@eggjs/core' {
25
+ interface EggAppConfig {
26
+ jsonp: JSONPConfig;
27
+ }
28
+ interface Context {
29
+ /**
30
+ * detect if response should be jsonp
31
+ */
32
+ acceptJSONP: boolean;
33
+ /**
34
+ * JSONP wrap body function
35
+ * Set jsonp response wrap function, other plugin can use it.
36
+ * If not necessary, please don't use this method in your application code.
37
+ * @param {Object} body response body
38
+ * @private
39
+ */
40
+ createJsonpBody(body: any): void;
41
+ }
42
+ interface EggCore {
43
+ /**
44
+ * return a middleware to enable jsonp response.
45
+ * will do some security check inside.
46
+ * @public
47
+ */
48
+ jsonp(initOptions?: Partial<JSONPConfig>): MiddlewareFunc;
49
+ }
50
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "@eggjs/jsonp",
3
+ "version": "3.0.0"
4
+ }
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@eggjs/jsonp",
3
+ "version": "3.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "jsonp support for egg",
8
+ "eggPlugin": {
9
+ "name": "jsonp",
10
+ "optionalDependencies": [
11
+ "security"
12
+ ],
13
+ "exports": {
14
+ "import": "./dist/esm",
15
+ "require": "./dist/commonjs",
16
+ "typescript": "./src"
17
+ }
18
+ },
19
+ "keywords": [
20
+ "egg",
21
+ "egg-plugin",
22
+ "jsonp",
23
+ "security"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/eggjs/jsonp.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/eggjs/egg/issues"
31
+ },
32
+ "homepage": "https://github.com/eggjs/jsonp#readme",
33
+ "author": "dead-horse",
34
+ "license": "MIT",
35
+ "engines": {
36
+ "node": ">= 18.19.0"
37
+ },
38
+ "dependencies": {
39
+ "@eggjs/core": "^6.2.13",
40
+ "jsonp-body": "^2.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@arethetypeswrong/cli": "^0.17.1",
44
+ "@eggjs/bin": "7",
45
+ "@eggjs/mock": "6",
46
+ "@eggjs/tsconfig": "1",
47
+ "@types/mocha": "10",
48
+ "@types/node": "22",
49
+ "egg": "4",
50
+ "eslint": "8",
51
+ "eslint-config-egg": "14",
52
+ "rimraf": "6",
53
+ "tshy": "3",
54
+ "tshy-after": "1",
55
+ "typescript": "5"
56
+ },
57
+ "scripts": {
58
+ "lint": "eslint --cache src test --ext .ts",
59
+ "pretest": "npm run clean && npm run lint -- --fix",
60
+ "test": "egg-bin test",
61
+ "preci": "npm run clean && npm run lint",
62
+ "ci": "egg-bin cov",
63
+ "postci": "npm run prepublishOnly && npm run clean",
64
+ "clean": "rimraf dist",
65
+ "prepublishOnly": "tshy && tshy-after && attw --pack"
66
+ },
67
+ "type": "module",
68
+ "tshy": {
69
+ "exports": {
70
+ ".": "./src/index.ts",
71
+ "./package.json": "./package.json"
72
+ }
73
+ },
74
+ "exports": {
75
+ ".": {
76
+ "import": {
77
+ "types": "./dist/esm/index.d.ts",
78
+ "default": "./dist/esm/index.js"
79
+ },
80
+ "require": {
81
+ "types": "./dist/commonjs/index.d.ts",
82
+ "default": "./dist/commonjs/index.js"
83
+ }
84
+ },
85
+ "./package.json": "./package.json"
86
+ },
87
+ "files": [
88
+ "dist",
89
+ "src"
90
+ ],
91
+ "types": "./dist/commonjs/index.d.ts",
92
+ "main": "./dist/commonjs/index.js",
93
+ "module": "./dist/esm/index.js"
94
+ }
@@ -0,0 +1,131 @@
1
+ import { debuglog } from 'node:util';
2
+ import { parse as urlParse, type UrlWithStringQuery } from 'node:url';
3
+ import type { ParsedUrlQuery } from 'node:querystring';
4
+ import { EggCore, type MiddlewareFunc } from '@eggjs/core';
5
+ import { JSONP_CONFIG } from '../../lib/private_key.js';
6
+ import { JSONPConfig } from '../../types.js';
7
+ import { JSONPForbiddenReferrerError } from '../../error/JSONPForbiddenReferrerError.js';
8
+ import JSONPContext from './context.js';
9
+
10
+ const debug = debuglog('@egg/jsonp/app/extend/application');
11
+
12
+ export default class JSONPApplication extends EggCore {
13
+ /**
14
+ * return a middleware to enable jsonp response.
15
+ * will do some security check inside.
16
+ * @public
17
+ */
18
+ jsonp(initOptions: Partial<JSONPConfig> = {}): MiddlewareFunc {
19
+ const options = {
20
+ ...this.config.jsonp,
21
+ ...initOptions,
22
+ } as JSONPConfig & { callback: string[] };
23
+ if (!Array.isArray(options.callback)) {
24
+ options.callback = [ options.callback ];
25
+ }
26
+
27
+ const csrfEnable = this.plugins.security && this.plugins.security.enable // security enable
28
+ && this.config.security.csrf && this.config.security.csrf.enable !== false // csrf enable
29
+ && options.csrf; // jsonp csrf enabled
30
+
31
+ const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);
32
+
33
+ if (!csrfEnable && !validateReferrer) {
34
+ this.coreLogger.warn('[@eggjs/jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');
35
+ }
36
+ /**
37
+ * jsonp request security check, pass if
38
+ *
39
+ * 1. hit referrer white list
40
+ * 2. or pass csrf check
41
+ * 3. both check are disabled
42
+ *
43
+ * @param {Context} ctx request context
44
+ */
45
+ function securityAssert(ctx: JSONPContext) {
46
+ // all disabled. don't need check
47
+ if (!csrfEnable && !validateReferrer) return;
48
+
49
+ // pass referrer check
50
+ const referrer = ctx.get<string>('referrer');
51
+ if (validateReferrer && validateReferrer(referrer)) return;
52
+ if (csrfEnable && validateCsrf(ctx)) return;
53
+
54
+ throw new JSONPForbiddenReferrerError(
55
+ 'jsonp request security validate failed',
56
+ referrer,
57
+ 403);
58
+ }
59
+
60
+ return async function jsonp(ctx: JSONPContext, next) {
61
+ const jsonpFunction = getJsonpFunction(ctx.query, options.callback);
62
+
63
+ ctx[JSONP_CONFIG] = {
64
+ jsonpFunction,
65
+ options,
66
+ };
67
+
68
+ // before handle request, must do some security checks
69
+ securityAssert(ctx);
70
+
71
+ await next();
72
+
73
+ // generate jsonp body
74
+ ctx.createJsonpBody(ctx.body);
75
+ };
76
+ }
77
+ }
78
+
79
+ function createValidateReferer(whiteList: Required<JSONPConfig>['whiteList']) {
80
+ if (!Array.isArray(whiteList)) {
81
+ whiteList = [ whiteList ];
82
+ }
83
+
84
+ return (referrer: string) => {
85
+ let parsed: UrlWithStringQuery | undefined;
86
+ for (const rule of whiteList) {
87
+ if (rule instanceof RegExp) {
88
+ if (rule.test(referrer)) {
89
+ // regexp(/^https?:\/\/github.com\//): test the referrer with rule
90
+ return true;
91
+ }
92
+ continue;
93
+ }
94
+
95
+ parsed = parsed ?? urlParse(referrer);
96
+ const hostname = parsed.hostname || '';
97
+
98
+ // check if referrer's hostname match the string rule
99
+ if (rule[0] === '.' &&
100
+ (hostname.endsWith(rule) || hostname === rule.slice(1))) {
101
+ // string start with `.`(.github.com): referrer's hostname must ends with rule
102
+ return true;
103
+ } else if (hostname === rule) {
104
+ // string not start with `.`(github.com): referrer's hostname must strict equal to rule
105
+ return true;
106
+ }
107
+ }
108
+
109
+ // no rule matched
110
+ return false;
111
+ };
112
+ }
113
+
114
+ function validateCsrf(ctx: any) {
115
+ try {
116
+ // TODO(fengmk2): remove this when @eggjs/security support ctx.assertCsrf type define
117
+ ctx.assertCsrf();
118
+ return true;
119
+ } catch (err) {
120
+ debug('validate csrf failed: %s', err);
121
+ return false;
122
+ }
123
+ }
124
+
125
+ function getJsonpFunction(query: ParsedUrlQuery, callbacks: string[]) {
126
+ for (const callback of callbacks) {
127
+ if (query[callback]) {
128
+ return query[callback] as string;
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,34 @@
1
+ import { jsonp as jsonpBody } from 'jsonp-body';
2
+ import { Context } from '@eggjs/core';
3
+ import { JSONP_CONFIG } from '../../lib/private_key.js';
4
+
5
+ export default class JSONPContext extends Context {
6
+ /**
7
+ * detect if response should be jsonp
8
+ */
9
+ get acceptJSONP() {
10
+ const jsonpConfig = Reflect.get(this, JSONP_CONFIG) as any;
11
+ return !!(jsonpConfig?.jsonpFunction);
12
+ }
13
+
14
+ /**
15
+ * JSONP wrap body function
16
+ * Set jsonp response wrap function, other plugin can use it.
17
+ * If not necessary, please don't use this method in your application code.
18
+ * @param {Object} body response body
19
+ * @private
20
+ */
21
+ createJsonpBody(body: any) {
22
+ const jsonpConfig = Reflect.get(this, JSONP_CONFIG) as any;
23
+ if (!jsonpConfig?.jsonpFunction) {
24
+ this.body = body;
25
+ return;
26
+ }
27
+
28
+ this.set('x-content-type-options', 'nosniff');
29
+ this.type = 'js';
30
+ body = body === undefined ? null : body;
31
+ // protect from jsonp xss
32
+ this.body = jsonpBody(body, jsonpConfig.jsonpFunction, jsonpConfig.options);
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ import type { JSONPConfig } from '../types.js';
2
+
3
+ export default {
4
+ jsonp: {
5
+ limit: 50,
6
+ callback: [ '_callback', 'callback' ],
7
+ csrf: false,
8
+ whiteList: undefined,
9
+ } as JSONPConfig,
10
+ };
@@ -0,0 +1,12 @@
1
+ export class JSONPForbiddenReferrerError extends Error {
2
+ referrer: string;
3
+ status: number;
4
+
5
+ constructor(message: string, referrer: string, status: number) {
6
+ super(message);
7
+ this.name = this.constructor.name;
8
+ this.referrer = referrer;
9
+ this.status = status;
10
+ Error.captureStackTrace(this, this.constructor);
11
+ }
12
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ import './types.js';
@@ -0,0 +1 @@
1
+ export const JSONP_CONFIG = Symbol('jsonp#config');
package/src/types.ts ADDED
@@ -0,0 +1,55 @@
1
+ import type { MiddlewareFunc } from '@eggjs/core';
2
+
3
+ /**
4
+ * jsonp options
5
+ * @member Config#jsonp
6
+ */
7
+ export interface JSONPConfig {
8
+ /**
9
+ * jsonp callback methods key, default to `['_callback', 'callback' ]`
10
+ */
11
+ callback: string[] | string;
12
+ /**
13
+ * callback method name's max length, default to `50`
14
+ */
15
+ limit: number;
16
+ /**
17
+ * enable csrf check or not, default to `false`
18
+ */
19
+ csrf: boolean;
20
+ /**
21
+ * referrer white list, default to `undefined`
22
+ */
23
+ whiteList?: string | RegExp | (string | RegExp)[];
24
+ }
25
+
26
+ declare module '@eggjs/core' {
27
+ // add EggAppConfig overrides types
28
+ interface EggAppConfig {
29
+ jsonp: JSONPConfig;
30
+ }
31
+
32
+ interface Context {
33
+ /**
34
+ * detect if response should be jsonp
35
+ */
36
+ acceptJSONP: boolean;
37
+ /**
38
+ * JSONP wrap body function
39
+ * Set jsonp response wrap function, other plugin can use it.
40
+ * If not necessary, please don't use this method in your application code.
41
+ * @param {Object} body response body
42
+ * @private
43
+ */
44
+ createJsonpBody(body: any): void;
45
+ }
46
+
47
+ interface EggCore {
48
+ /**
49
+ * return a middleware to enable jsonp response.
50
+ * will do some security check inside.
51
+ * @public
52
+ */
53
+ jsonp(initOptions?: Partial<JSONPConfig>): MiddlewareFunc;
54
+ }
55
+ }
@@ -0,0 +1,4 @@
1
+ // make sure to import egg typings and let typescript know about it
2
+ // @see https://github.com/whxaxes/blog/issues/11
3
+ // and https://www.typescriptlang.org/docs/handbook/declaration-merging.html
4
+ import 'egg';