application.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 'use strict';
  2. const is = require('is-type-of');
  3. const url = require('url');
  4. const { JSONP_CONFIG } = require('../../lib/private_key');
  5. module.exports = {
  6. /**
  7. * return a middleware to enable jsonp response.
  8. * will do some security check inside.
  9. * @param {Object} options jsonp options. can override `config.jsonp`.
  10. * @return {Function} jsonp middleware
  11. * @public
  12. */
  13. jsonp(options) {
  14. const defaultOptions = this.config.jsonp;
  15. options = Object.assign({}, defaultOptions, options);
  16. if (!Array.isArray(options.callback)) options.callback = [ options.callback ];
  17. const csrfEnable = this.plugins.security && this.plugins.security.enable // security enable
  18. && this.config.security.csrf && this.config.security.csrf.enable !== false // csrf enable
  19. && options.csrf; // jsonp csrf enabled
  20. const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);
  21. if (!csrfEnable && !validateReferrer) {
  22. this.coreLogger.warn('[egg-jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');
  23. }
  24. /**
  25. * jsonp request security check, pass if
  26. *
  27. * 1. hit referrer white list
  28. * 2. or pass csrf check
  29. * 3. both check are disabled
  30. *
  31. * @param {Context} ctx request context
  32. */
  33. function securityAssert(ctx) {
  34. // all disabled. don't need check
  35. if (!csrfEnable && !validateReferrer) return;
  36. // pass referrer check
  37. const referrer = ctx.get('referrer');
  38. if (validateReferrer && validateReferrer(referrer)) return;
  39. if (csrfEnable && validateCsrf(ctx)) return;
  40. const err = new Error('jsonp request security validate failed');
  41. err.referrer = referrer;
  42. err.status = 403;
  43. throw err;
  44. }
  45. return async function jsonp(ctx, next) {
  46. const jsonpFunction = getJsonpFunction(ctx.query, options.callback);
  47. ctx[JSONP_CONFIG] = {
  48. jsonpFunction,
  49. options,
  50. };
  51. // before handle request, must do some security checks
  52. securityAssert(ctx);
  53. await next();
  54. // generate jsonp body
  55. ctx.createJsonpBody(ctx.body);
  56. };
  57. },
  58. };
  59. function createValidateReferer(whiteList) {
  60. if (!Array.isArray(whiteList)) whiteList = [ whiteList ];
  61. return function(referrer) {
  62. let parsed = null;
  63. for (const item of whiteList) {
  64. if (is.regExp(item) && item.test(referrer)) {
  65. // regexp(/^https?:\/\/github.com\//): test the referrer with item
  66. return true;
  67. }
  68. parsed = parsed || url.parse(referrer);
  69. const hostname = parsed.hostname || '';
  70. if (item[0] === '.' &&
  71. (hostname.endsWith(item) || hostname === item.slice(1))) {
  72. // string start with `.`(.github.com): referrer's hostname must ends with item
  73. return true;
  74. } else if (hostname === item) {
  75. // string not start with `.`(github.com): referrer's hostname must strict equal to item
  76. return true;
  77. }
  78. }
  79. return false;
  80. };
  81. }
  82. function validateCsrf(ctx) {
  83. try {
  84. ctx.assertCsrf();
  85. return true;
  86. } catch (_) {
  87. return false;
  88. }
  89. }
  90. function getJsonpFunction(query, callbacks) {
  91. for (const callback of callbacks) {
  92. if (query[callback]) return query[callback];
  93. }
  94. }