'use strict';
|
|
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];
|
}
|
}
|