index.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. 'use strict';
  2. const vary = require('vary');
  3. /**
  4. * CORS middleware
  5. *
  6. * @param {Object} [options]
  7. * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
  8. * - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
  9. * - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
  10. * - {String|Array} allowHeaders `Access-Control-Allow-Headers`
  11. * - {String|Number} maxAge `Access-Control-Max-Age` in seconds
  12. * - {Boolean} credentials `Access-Control-Allow-Credentials`
  13. * - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
  14. * @return {Function} cors middleware
  15. * @api public
  16. */
  17. module.exports = function(options) {
  18. const defaults = {
  19. allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
  20. };
  21. options = {
  22. ...defaults,
  23. ...options,
  24. };
  25. if (Array.isArray(options.exposeHeaders)) {
  26. options.exposeHeaders = options.exposeHeaders.join(',');
  27. }
  28. if (Array.isArray(options.allowMethods)) {
  29. options.allowMethods = options.allowMethods.join(',');
  30. }
  31. if (Array.isArray(options.allowHeaders)) {
  32. options.allowHeaders = options.allowHeaders.join(',');
  33. }
  34. if (options.maxAge) {
  35. options.maxAge = String(options.maxAge);
  36. }
  37. options.keepHeadersOnError = options.keepHeadersOnError === undefined || !!options.keepHeadersOnError;
  38. return async function cors(ctx, next) {
  39. // If the Origin header is not present terminate this set of steps.
  40. // The request is outside the scope of this specification.
  41. const requestOrigin = ctx.get('Origin');
  42. // Always set Vary header
  43. // https://github.com/rs/cors/issues/10
  44. ctx.vary('Origin');
  45. if (!requestOrigin) return await next();
  46. let origin;
  47. if (typeof options.origin === 'function') {
  48. origin = options.origin(ctx);
  49. if (origin instanceof Promise) origin = await origin;
  50. if (!origin) return await next();
  51. } else {
  52. origin = options.origin || requestOrigin;
  53. }
  54. let credentials;
  55. if (typeof options.credentials === 'function') {
  56. credentials = options.credentials(ctx);
  57. if (credentials instanceof Promise) credentials = await credentials;
  58. } else {
  59. credentials = !!options.credentials;
  60. }
  61. const headersSet = {};
  62. function set(key, value) {
  63. ctx.set(key, value);
  64. headersSet[key] = value;
  65. }
  66. if (ctx.method !== 'OPTIONS') {
  67. // Simple Cross-Origin Request, Actual Request, and Redirects
  68. set('Access-Control-Allow-Origin', origin);
  69. if (credentials === true) {
  70. set('Access-Control-Allow-Credentials', 'true');
  71. }
  72. if (options.exposeHeaders) {
  73. set('Access-Control-Expose-Headers', options.exposeHeaders);
  74. }
  75. if (!options.keepHeadersOnError) {
  76. return await next();
  77. }
  78. try {
  79. return await next();
  80. } catch (err) {
  81. const errHeadersSet = err.headers || {};
  82. const varyWithOrigin = vary.append(errHeadersSet.vary || errHeadersSet.Vary || '', 'Origin');
  83. delete errHeadersSet.Vary;
  84. err.headers = {
  85. ...errHeadersSet,
  86. ...headersSet,
  87. ...{ vary: varyWithOrigin },
  88. };
  89. throw err;
  90. }
  91. } else {
  92. // Preflight Request
  93. // If there is no Access-Control-Request-Method header or if parsing failed,
  94. // do not set any additional headers and terminate this set of steps.
  95. // The request is outside the scope of this specification.
  96. if (!ctx.get('Access-Control-Request-Method')) {
  97. // this not preflight request, ignore it
  98. return await next();
  99. }
  100. ctx.set('Access-Control-Allow-Origin', origin);
  101. if (credentials === true) {
  102. ctx.set('Access-Control-Allow-Credentials', 'true');
  103. }
  104. if (options.maxAge) {
  105. ctx.set('Access-Control-Max-Age', options.maxAge);
  106. }
  107. if (options.allowMethods) {
  108. ctx.set('Access-Control-Allow-Methods', options.allowMethods);
  109. }
  110. let allowHeaders = options.allowHeaders;
  111. if (!allowHeaders) {
  112. allowHeaders = ctx.get('Access-Control-Request-Headers');
  113. }
  114. if (allowHeaders) {
  115. ctx.set('Access-Control-Allow-Headers', allowHeaders);
  116. }
  117. ctx.status = 204;
  118. }
  119. };
  120. };