egg-jsonp
Version:
jsonp support for egg
114 lines (94 loc) • 3.26 kB
JavaScript
;
const is = require('is-type-of');
const url = require('url');
const { JSONP_CONFIG } = require('../../lib/private_key');
module.exports = {
/**
* return a middleware to enable jsonp response.
* will do some security check inside.
* @param {Object} options jsonp options. can override `config.jsonp`.
* @return {Function} jsonp middleware
* @public
*/
jsonp(options) {
const defaultOptions = this.config.jsonp;
options = Object.assign({}, defaultOptions, options);
if (!Array.isArray(options.callback)) options.callback = [ options.callback ];
const csrfEnable = this.plugins.security && this.plugins.security.enable // security enable
&& this.config.security.csrf && this.config.security.csrf.enable !== false // csrf enable
&& options.csrf; // jsonp csrf enabled
const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);
if (!csrfEnable && !validateReferrer) {
this.coreLogger.warn('[egg-jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');
}
/**
* jsonp request security check, pass if
*
* 1. hit referrer white list
* 2. or pass csrf check
* 3. both check are disabled
*
* @param {Context} ctx request context
*/
function securityAssert(ctx) {
// all disabled. don't need check
if (!csrfEnable && !validateReferrer) return;
// pass referrer check
const referrer = ctx.get('referrer');
if (validateReferrer && validateReferrer(referrer)) return;
if (csrfEnable && validateCsrf(ctx)) return;
const err = new Error('jsonp request security validate failed');
err.referrer = referrer;
err.status = 403;
throw err;
}
return async function jsonp(ctx, next) {
const jsonpFunction = getJsonpFunction(ctx.query, options.callback);
ctx[JSONP_CONFIG] = {
jsonpFunction,
options,
};
// before handle request, must do some security checks
securityAssert(ctx);
await next();
// generate jsonp body
ctx.createJsonpBody(ctx.body);
};
},
};
function createValidateReferer(whiteList) {
if (!Array.isArray(whiteList)) whiteList = [ whiteList ];
return function(referrer) {
let parsed = null;
for (const item of whiteList) {
if (is.regExp(item) && item.test(referrer)) {
// regexp(/^https?:\/\/github.com\//): test the referrer with item
return true;
}
parsed = parsed || url.parse(referrer);
const hostname = parsed.hostname || '';
if (item[0] === '.' &&
(hostname.endsWith(item) || hostname === item.slice(1))) {
// string start with `.`(.github.com): referrer's hostname must ends with item
return true;
} else if (hostname === item) {
// string not start with `.`(github.com): referrer's hostname must strict equal to item
return true;
}
}
return false;
};
}
function validateCsrf(ctx) {
try {
ctx.assertCsrf();
return true;
} catch (_) {
return false;
}
}
function getJsonpFunction(query, callbacks) {
for (const callback of callbacks) {
if (query[callback]) return query[callback];
}
}