├── .eslintrc ├── .github └── workflows │ ├── codeql-analysis.yml │ └── nodejs.yml ├── .gitignore ├── History.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── cors.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master", 1.x ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '40 12 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - master 11 | pull_request: 12 | branches: 13 | - main 14 | - master 15 | 16 | jobs: 17 | build: 18 | runs-on: ${{ matrix.os }} 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | node-version: [14, 16, 18, 20] 24 | os: [ubuntu-latest] 25 | 26 | steps: 27 | - name: Checkout Git Source 28 | uses: actions/checkout@v2 29 | 30 | - name: Use Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v1 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | 35 | - name: Install Dependencies 36 | run: npm i 37 | 38 | - name: Continuous Integration 39 | run: npm run ci 40 | 41 | - name: Code Coverage 42 | uses: codecov/codecov-action@v1 43 | with: 44 | token: ${{ secrets.CODECOV_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | .idea 9 | .DS_Store 10 | 11 | pids 12 | logs 13 | results 14 | 15 | node_modules 16 | npm-debug.log 17 | coverage/ 18 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 5.0.0 / 2023-12-11 3 | ================== 4 | 5 | **others** 6 | * [[`f31dac9`](http://github.com/koajs/cors/commit/f31dac99f5355c41e7d4dd3c4a80c5f154941a11)] - Merge pull request from GHSA-qxrj-hx23-xp82 (fengmk2 <>) 7 | 8 | 4.0.0 / 2022-10-08 9 | ================== 10 | 11 | **fixes** 12 | * [[`7358ab3`](http://github.com/koajs/cors/commit/7358ab381af6413013938f49c56ac79a7453d35c)] - fix: Calling all options even if origin header is not present (#87) (Cleber Rossi <>) 13 | 14 | **others** 15 | * [[`d19090f`](http://github.com/koajs/cors/commit/d19090fc8591059895fa9c606967d3a67fd3c5b8)] - refactor: [BREAKING] drop node 8, 10, 12 support (#88) (fengmk2 <>) 16 | 17 | 3.4.3 / 2022-10-08 18 | ================== 19 | 20 | **others** 21 | * [[`208b86c`](http://github.com/koajs/cors/commit/208b86c893013d65e4479219aae0763b807bc8a6)] - Revert "fix: Calling all options even if origin header is not present (#87)" (fengmk2 <>) 22 | 23 | 3.4.2 / 2022-10-06 24 | ================== 25 | 26 | **fixes** 27 | * [[`2e8da5b`](http://github.com/koajs/cors/commit/2e8da5bd2acbc9c1adfabdea459982b3d5bdd31f)] - fix: Calling all options even if origin header is not present (#87) (Cleber Rossi <>) 28 | 29 | 3.4.1 / 2022-08-19 30 | ================== 31 | 32 | **fixes** 33 | * [[`1205356`](http://github.com/koajs/cors/commit/12053567ef2caa8f4191298bc9d010017bb0f233)] - fix: must specify an origin value instead of "*" wildcard (#85) (Tyreal Hu <>) 34 | 35 | 3.4.0 / 2022-08-19 36 | ================== 37 | 38 | **others** 39 | * [[`2cd4789`](http://github.com/koajs/cors/commit/2cd4789f66a64cd13228e7305cce9069bd2d1283)] - 🤖 TEST: Run test on Node.js 18 (#86) (fengmk2 <>) 40 | * [[`ae56e05`](http://github.com/koajs/cors/commit/ae56e054cb669c73784f8a12ab6413abca6eff57)] - Create codeql-analysis.yml (fengmk2 <>) 41 | * [[`c4b5d21`](http://github.com/koajs/cors/commit/c4b5d21e0cf5ab76109be65f4b7267d0ccacce81)] - refactor: use friendlier promise checking (#84) (Swain Molster <>) 42 | * [[`fbe33bc`](http://github.com/koajs/cors/commit/fbe33bca26373965429356f02144507c31326cfc)] - 📖 DOC: Add privateNetworkAccess js to README (fengmk2 <>) 43 | 44 | 3.3.0 / 2022-03-29 45 | ================== 46 | 47 | **features** 48 | * [[`c279fc3`](http://github.com/koajs/cors/commit/c279fc36e60f3b2835395d15c4604fa1b284fc5f)] - feat: Add support for "Private Network Access" (#83) (Chi Ma <<55783048+cma-skedulo@users.noreply.github.com>>) 49 | 50 | **others** 51 | * [[`97d9220`](http://github.com/koajs/cors/commit/97d92207ae33aa2dbdd21d218ef836183194c257)] - chore: credentials jsdoc (#80) (Jing Yi Wang <>) 52 | 53 | 3.2.0 / 2022-03-12 54 | ================== 55 | 56 | **features** 57 | * [[`134ec9b`](http://github.com/koajs/cors/commit/134ec9b54b18565cf8bba8c5e6b6639d7d7e43a3)] - feat: support secure context headers (Levi Tomes <>) 58 | 59 | **others** 60 | * [[`bcadb55`](http://github.com/koajs/cors/commit/bcadb5599905c28934ed3c28f866f6cdb3f77aee)] - test: run test on github action (fengmk2 <>) 61 | 62 | 3.1.0 / 2020-05-17 63 | ================== 64 | 65 | **features** 66 | * [[`013662a`](http://github.com/koajs/cors/commit/013662ae1ab65c4af230c17dfa1044609502b15b)] - feat: add support for using a function to determine whether or not to allow credentials. (#68) (mcohen75 <>) 67 | 68 | **others** 69 | * [[`da84dec`](http://github.com/koajs/cors/commit/da84dec7fa16af95d157a549bd473e7bfa4aa152)] - docs: update install script (dead-horse <>) 70 | * [[`eba3b44`](http://github.com/koajs/cors/commit/eba3b446055bd14b86d19dfc81d8ed5f83a8a534)] - chore: ES6 Object spread (#66) (Alpha <>) 71 | 72 | 3.0.0 / 2019-03-11 73 | ================== 74 | 75 | **others** 76 | * [[`369d31d`](http://github.com/koajs/cors/commit/369d31db7835ed344011706f9506d45a44638017)] - refactor: use async function, support options.origin return promise (#59) (Yiyu He <>) 77 | 78 | 2.2.3 / 2018-12-19 79 | ================== 80 | 81 | **fixes** 82 | * [[`12ae730`](http://github.com/koajs/cors/commit/12ae7306e8055322e6c5d29319330da52ba0e126)] - fix: set `Vary: Origin` header on error responses (#55) (Erik Fried <>) 83 | 84 | 2.2.2 / 2018-07-11 85 | ================== 86 | 87 | **others** 88 | * [[`019ec40`](http://github.com/koajs/cors/commit/019ec403be573177e8ed6ad3ef4077b82b5ea934)] - travis: test node@10 and drop test node@4 (#51) (fengmk2 <>) 89 | * [[`6e22833`](http://github.com/koajs/cors/commit/6e22833ce125ca334b68980372065867eda892b0)] - doc: update outdated options doc (Xingan Wang <>) 90 | * [[`c982530`](http://github.com/koajs/cors/commit/c9825308ce1c76810468bdf5a404b838206fba22)] - travis: test node@8 (jongleberry <>) 91 | * [[`b4f65b3`](http://github.com/koajs/cors/commit/b4f65b39b558b870521e6613aee58898e88196f9)] - npm: remove tag (jongleberry <>) 92 | * [[`878ae9b`](http://github.com/koajs/cors/commit/878ae9b0c99fb6da8d3840e502d4968a65089e28)] - package: rename to @koa/cors (jongleberry <>) 93 | 94 | 2.2.1 / 2017-02-12 95 | ================== 96 | 97 | * fix: always set "Vary: Origin" header (#31) 98 | 99 | 2.2.0 / 2016-09-26 100 | ================== 101 | 102 | * feat: add PATCH to default methods 103 | 104 | 2.1.1 / 2016-05-14 105 | ================== 106 | 107 | * fix: keepHeadersOnError won't affect OPTIONS request (#17) 108 | 109 | 2.1.0 / 2016-04-29 110 | ================== 111 | 112 | * feat: Keep headers after an error (#13) 113 | * chore: use eslint instead of jshint (#10) 114 | * test: use codecov instead of coveralls 115 | 116 | 2.0.0 / 2016-02-20 117 | ================== 118 | 119 | * chore: make node engines >= 4.3.1 120 | * doc: update example 121 | * test: only test on node 4+ 122 | * refactor: src,test: update to (ctx, next) -> Promise middleware contract 123 | * chore: base on koa@2 124 | 125 | 1.0.1 / 2015-02-11 126 | ================== 127 | 128 | * fix: make more spec-compliant 129 | 130 | 1.0.0 / 2015-02-09 131 | ================== 132 | 133 | * first release 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright (c) 2015 - present koajs 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @koa/cors 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Node.js CI](https://github.com/koajs/cors/actions/workflows/nodejs.yml/badge.svg)](https://github.com/koajs/cors/actions/workflows/nodejs.yml) 5 | [![Test coverage][codecov-image]][codecov-url] 6 | [![npm download][download-image]][download-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/@koa/cors.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/@koa/cors 10 | [codecov-image]: https://codecov.io/github/koajs/cors/coverage.svg?branch=v2.x 11 | [codecov-url]: https://codecov.io/github/koajs/cors?branch=v2.x 12 | [download-image]: https://img.shields.io/npm/dm/@koa/cors.svg?style=flat-square 13 | [download-url]: https://npmjs.org/package/@koa/cors 14 | 15 | [Cross-Origin Resource Sharing(CORS)](https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS) for koa 16 | 17 | ## Installation 18 | 19 | ```bash 20 | $ npm install @koa/cors --save 21 | ``` 22 | 23 | ## Quick start 24 | 25 | Enable cors with default options: 26 | 27 | - origin: `*` (v4 and before: the request's Origin header). This means that **by default the requests from all origin webpages will be allowed**. 28 | If you're running a generic API server, this is what you want, but otherwise you should look into changing the default to something more 29 | suitable to your application. 30 | - allowMethods: GET,HEAD,PUT,POST,DELETE,PATCH 31 | 32 | ```js 33 | const Koa = require('koa'); 34 | const cors = require('@koa/cors'); 35 | 36 | const app = new Koa(); 37 | app.use(cors()); 38 | ``` 39 | 40 | ## cors(options) 41 | 42 | ```js 43 | /** 44 | * CORS middleware 45 | * 46 | * @param {Object} [options] 47 | * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is '*' 48 | * If `credentials` set and return `true, the `origin` default value will set to the request `Origin` header 49 | * - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH' 50 | * - {String|Array} exposeHeaders `Access-Control-Expose-Headers` 51 | * - {String|Array} allowHeaders `Access-Control-Allow-Headers` 52 | * - {String|Number} maxAge `Access-Control-Max-Age` in seconds 53 | * - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`, default is false. 54 | * - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown 55 | * - {Boolean} secureContext `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy` headers.', default is false 56 | * - {Boolean} privateNetworkAccess handle `Access-Control-Request-Private-Network` request by return `Access-Control-Allow-Private-Network`, default to false 57 | * @return {Function} cors middleware 58 | * @api public 59 | */ 60 | ``` 61 | 62 | ## Breaking change between 4.0 and 5.0 63 | 64 | The default `origin` is set to `*`, if you want to keep the 4.0 behavior, you can set the `origin` handler like this: 65 | 66 | ```js 67 | app.use(cors({ 68 | origin(ctx) { 69 | return ctx.get('Origin') || '*'; 70 | }, 71 | })); 72 | ``` 73 | 74 | ## License 75 | 76 | [MIT](./LICENSE) 77 | 78 | 79 | 80 | ## Contributors 81 | 82 | |[
fengmk2](https://github.com/fengmk2)
|[
dead-horse](https://github.com/dead-horse)
|[
omsmith](https://github.com/omsmith)
|[
jonathanong](https://github.com/jonathanong)
|[
AlphaWong](https://github.com/AlphaWong)
|[
cma-skedulo](https://github.com/cma-skedulo)
| 83 | | :---: | :---: | :---: | :---: | :---: | :---: | 84 | |[
CleberRossi](https://github.com/CleberRossi)
|[
erikfried](https://github.com/erikfried)
|[
j-waaang](https://github.com/j-waaang)
|[
ltomes](https://github.com/ltomes)
|[
lfreneda](https://github.com/lfreneda)
|[
matthewmueller](https://github.com/matthewmueller)
| 85 | [
PlasmaPower](https://github.com/PlasmaPower)
|[
swain](https://github.com/swain)
|[
TyrealHu](https://github.com/TyrealHu)
|[
xg-wang](https://github.com/xg-wang)
|[
lishengzxc](https://github.com/lishengzxc)
|[
mcohen75](https://github.com/mcohen75)
86 | 87 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Sat Oct 08 2022 21:35:10 GMT+0800`. 88 | 89 | 90 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const vary = require('vary'); 2 | 3 | /** 4 | * CORS middleware 5 | * 6 | * @param {Object} [options] 7 | * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is '*' 8 | * If `credentials` set and return `true, the `origin` default value will set to the request `Origin` header 9 | * - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH' 10 | * - {String|Array} exposeHeaders `Access-Control-Expose-Headers` 11 | * - {String|Array} allowHeaders `Access-Control-Allow-Headers` 12 | * - {String|Number} maxAge `Access-Control-Max-Age` in seconds 13 | * - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials` 14 | * - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown 15 | * - {Boolean} secureContext `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy` headers.', default is false 16 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/Planned_changes 17 | * - {Boolean} privateNetworkAccess handle `Access-Control-Request-Private-Network` request by return `Access-Control-Allow-Private-Network`, default to false 18 | * @see https://wicg.github.io/private-network-access/ 19 | * @return {Function} cors middleware 20 | * @public 21 | */ 22 | module.exports = function(options) { 23 | const defaults = { 24 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH', 25 | secureContext: false, 26 | }; 27 | 28 | options = { 29 | ...defaults, 30 | ...options, 31 | }; 32 | 33 | if (Array.isArray(options.exposeHeaders)) { 34 | options.exposeHeaders = options.exposeHeaders.join(','); 35 | } 36 | 37 | if (Array.isArray(options.allowMethods)) { 38 | options.allowMethods = options.allowMethods.join(','); 39 | } 40 | 41 | if (Array.isArray(options.allowHeaders)) { 42 | options.allowHeaders = options.allowHeaders.join(','); 43 | } 44 | 45 | if (options.maxAge) { 46 | options.maxAge = String(options.maxAge); 47 | } 48 | 49 | options.keepHeadersOnError = options.keepHeadersOnError === undefined || !!options.keepHeadersOnError; 50 | 51 | return async function cors(ctx, next) { 52 | // If the Origin header is not present terminate this set of steps. 53 | // The request is outside the scope of this specification. 54 | const requestOrigin = ctx.get('Origin'); 55 | 56 | // Always set Vary header 57 | // https://github.com/rs/cors/issues/10 58 | ctx.vary('Origin'); 59 | 60 | let origin; 61 | if (typeof options.origin === 'function') { 62 | origin = await options.origin(ctx); 63 | if (!origin) { 64 | return await next(); 65 | } 66 | } else { 67 | origin = options.origin || '*'; 68 | } 69 | 70 | let credentials; 71 | if (typeof options.credentials === 'function') { 72 | credentials = await options.credentials(ctx); 73 | } else { 74 | credentials = !!options.credentials; 75 | } 76 | 77 | if (credentials && origin === '*') { 78 | origin = requestOrigin; 79 | } 80 | 81 | const headersSet = {}; 82 | 83 | function set(key, value) { 84 | ctx.set(key, value); 85 | headersSet[key] = value; 86 | } 87 | 88 | if (ctx.method !== 'OPTIONS') { 89 | // Simple Cross-Origin Request, Actual Request, and Redirects 90 | set('Access-Control-Allow-Origin', origin); 91 | 92 | if (credentials === true) { 93 | set('Access-Control-Allow-Credentials', 'true'); 94 | } 95 | 96 | if (options.exposeHeaders) { 97 | set('Access-Control-Expose-Headers', options.exposeHeaders); 98 | } 99 | 100 | if (options.secureContext) { 101 | set('Cross-Origin-Opener-Policy', 'same-origin'); 102 | set('Cross-Origin-Embedder-Policy', 'require-corp'); 103 | } 104 | 105 | if (!options.keepHeadersOnError) { 106 | return await next(); 107 | } 108 | try { 109 | return await next(); 110 | } catch (err) { 111 | const errHeadersSet = err.headers || {}; 112 | const varyWithOrigin = vary.append(errHeadersSet.vary || errHeadersSet.Vary || '', 'Origin'); 113 | delete errHeadersSet.Vary; 114 | 115 | err.headers = { 116 | ...errHeadersSet, 117 | ...headersSet, 118 | ...{ vary: varyWithOrigin }, 119 | }; 120 | throw err; 121 | } 122 | } else { 123 | // Preflight Request 124 | 125 | // If there is no Access-Control-Request-Method header or if parsing failed, 126 | // do not set any additional headers and terminate this set of steps. 127 | // The request is outside the scope of this specification. 128 | if (!ctx.get('Access-Control-Request-Method')) { 129 | // this not preflight request, ignore it 130 | return await next(); 131 | } 132 | 133 | ctx.set('Access-Control-Allow-Origin', origin); 134 | 135 | if (credentials === true) { 136 | ctx.set('Access-Control-Allow-Credentials', 'true'); 137 | } 138 | 139 | if (options.maxAge) { 140 | ctx.set('Access-Control-Max-Age', options.maxAge); 141 | } 142 | 143 | if (options.privateNetworkAccess && ctx.get('Access-Control-Request-Private-Network')) { 144 | ctx.set('Access-Control-Allow-Private-Network', 'true'); 145 | } 146 | 147 | if (options.allowMethods) { 148 | ctx.set('Access-Control-Allow-Methods', options.allowMethods); 149 | } 150 | 151 | if (options.secureContext) { 152 | set('Cross-Origin-Opener-Policy', 'same-origin'); 153 | set('Cross-Origin-Embedder-Policy', 'require-corp'); 154 | } 155 | 156 | let allowHeaders = options.allowHeaders; 157 | if (!allowHeaders) { 158 | allowHeaders = ctx.get('Access-Control-Request-Headers'); 159 | } 160 | if (allowHeaders) { 161 | ctx.set('Access-Control-Allow-Headers', allowHeaders); 162 | } 163 | 164 | ctx.status = 204; 165 | } 166 | }; 167 | }; 168 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@koa/cors", 3 | "version": "5.0.0", 4 | "description": "Cross-Origin Resource Sharing(CORS) for koa", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "scripts": { 10 | "contributor": "git-contributor", 11 | "test": "NODE_ENV=test mocha --check-leaks -R spec -t 5000 test/*.test.js", 12 | "test-cov": "NODE_ENV=test istanbul cover _mocha -- --check-leaks -t 5000 test/*.test.js", 13 | "ci": "npm run lint && npm run test-cov", 14 | "lint": "eslint index.js test" 15 | }, 16 | "dependencies": { 17 | "vary": "^1.1.2" 18 | }, 19 | "devDependencies": { 20 | "egg-ci": "^2.1.0", 21 | "eslint": "^8.25.0", 22 | "eslint-config-egg": "^12.0.0", 23 | "git-contributor": "^1.0.10", 24 | "istanbul": "*", 25 | "koa": "^2.5.1", 26 | "mocha": "^3.5.3", 27 | "supertest": "^3.1.0" 28 | }, 29 | "homepage": "https://github.com/koajs/cors", 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/koajs/cors.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/koajs/cors/issues" 36 | }, 37 | "keywords": [ 38 | "cors", 39 | "koa-cors", 40 | "Cross-Origin Resource Sharing", 41 | "@koa/cors", 42 | "koa", 43 | "koajs" 44 | ], 45 | "engines": { 46 | "node": ">= 14.0.0" 47 | }, 48 | "ci": { 49 | "version": "14, 16, 18, 20", 50 | "os": "linux" 51 | }, 52 | "author": "fengmk2 (http://github.com/fengmk2)", 53 | "license": "MIT" 54 | } 55 | -------------------------------------------------------------------------------- /test/cors.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const Koa = require('koa'); 3 | const request = require('supertest'); 4 | const cors = require('..'); 5 | 6 | describe('cors.test.js', function() { 7 | describe('default options', function() { 8 | const app = new Koa(); 9 | app.use(cors()); 10 | app.use(function(ctx) { 11 | ctx.body = { foo: 'bar' }; 12 | }); 13 | 14 | it('should set `Access-Control-Allow-Origin` to `*` when request Origin header missing', function(done) { 15 | request(app.listen()) 16 | .get('/') 17 | .expect({ foo: 'bar' }) 18 | .expect('access-control-allow-origin', '*') 19 | .expect(200, done); 20 | }); 21 | 22 | it('should set `Access-Control-Allow-Origin` to `*`', function(done) { 23 | request(app.listen()) 24 | .get('/') 25 | .set('Origin', 'http://koajs.com') 26 | .expect('Access-Control-Allow-Origin', '*') 27 | .expect({ foo: 'bar' }) 28 | .expect(200, done); 29 | }); 30 | 31 | it('should 204 on Preflight Request', function(done) { 32 | request(app.listen()) 33 | .options('/') 34 | .set('Origin', 'http://koajs.com') 35 | .set('Access-Control-Request-Method', 'PUT') 36 | .expect('Access-Control-Allow-Origin', '*') 37 | .expect('Access-Control-Allow-Methods', 'GET,HEAD,PUT,POST,DELETE,PATCH') 38 | .expect(204, done); 39 | }); 40 | 41 | it('should not Preflight Request if request missing Access-Control-Request-Method', function(done) { 42 | request(app.listen()) 43 | .options('/') 44 | .set('Origin', 'http://koajs.com') 45 | .expect(200, done); 46 | }); 47 | 48 | it('should always set `Vary` to Origin', function(done) { 49 | request(app.listen()) 50 | .get('/') 51 | .set('Origin', 'http://koajs.com') 52 | .expect('Vary', 'Origin') 53 | .expect({ foo: 'bar' }) 54 | .expect(200, done); 55 | }); 56 | }); 57 | 58 | describe('options.origin=*', function() { 59 | const app = new Koa(); 60 | app.use(cors({ 61 | origin: '*', 62 | })); 63 | app.use(function(ctx) { 64 | ctx.body = { foo: 'bar' }; 65 | }); 66 | 67 | it('should always set `Access-Control-Allow-Origin` to *', function(done) { 68 | request(app.listen()) 69 | .get('/') 70 | .set('Origin', 'http://koajs.com') 71 | .expect('Access-Control-Allow-Origin', '*') 72 | .expect({ foo: 'bar' }) 73 | .expect(200, done); 74 | }); 75 | 76 | it('should always set `Access-Control-Allow-Origin` to *, even if no Origin is passed on request', function(done) { 77 | request(app.listen()) 78 | .get('/') 79 | .expect('Access-Control-Allow-Origin', '*') 80 | .expect({ foo: 'bar' }) 81 | .expect(200, done); 82 | }); 83 | }); 84 | 85 | describe('options.origin set the request Origin header', function() { 86 | const app = new Koa(); 87 | app.use(cors({ 88 | origin(ctx) { 89 | return ctx.get('Origin') || '*'; 90 | }, 91 | })); 92 | app.use(function(ctx) { 93 | ctx.body = { foo: 'bar' }; 94 | }); 95 | 96 | it('should set `Access-Control-Allow-Origin` to request `Origin` header', function(done) { 97 | request(app.listen()) 98 | .get('/') 99 | .set('Origin', 'http://koajs.com') 100 | .expect('Access-Control-Allow-Origin', 'http://koajs.com') 101 | .expect({ foo: 'bar' }) 102 | .expect(200, done); 103 | }); 104 | 105 | it('should set `Access-Control-Allow-Origin` to request `origin` header', function(done) { 106 | request(app.listen()) 107 | .get('/') 108 | .set('origin', 'http://origin.koajs.com') 109 | .expect('Access-Control-Allow-Origin', 'http://origin.koajs.com') 110 | .expect({ foo: 'bar' }) 111 | .expect(200, done); 112 | }); 113 | 114 | it('should set `Access-Control-Allow-Origin` to `*`, even if no Origin is passed on request', function(done) { 115 | request(app.listen()) 116 | .get('/') 117 | .expect('Access-Control-Allow-Origin', '*') 118 | .expect({ foo: 'bar' }) 119 | .expect(200, done); 120 | }); 121 | }); 122 | 123 | describe('options.secureContext=true', function() { 124 | const app = new Koa(); 125 | app.use(cors({ 126 | secureContext: true, 127 | })); 128 | app.use(function(ctx) { 129 | ctx.body = { foo: 'bar' }; 130 | }); 131 | 132 | it('should always set `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy` on not OPTIONS', function(done) { 133 | request(app.listen()) 134 | .get('/') 135 | .set('Origin', 'http://koajs.com') 136 | .expect('Cross-Origin-Opener-Policy', 'same-origin') 137 | .expect('Cross-Origin-Embedder-Policy', 'require-corp') 138 | .expect({ foo: 'bar' }) 139 | .expect(200, done); 140 | }); 141 | 142 | it('should always set `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy` on OPTIONS', function(done) { 143 | request(app.listen()) 144 | .options('/') 145 | .set('Origin', 'http://koajs.com') 146 | .set('Access-Control-Request-Method', 'PUT') 147 | .expect('Cross-Origin-Opener-Policy', 'same-origin') 148 | .expect('Cross-Origin-Embedder-Policy', 'require-corp') 149 | .expect(204, done); 150 | }); 151 | }); 152 | 153 | describe('options.secureContext=false', function() { 154 | const app = new Koa(); 155 | app.use(cors({ 156 | secureContext: false, 157 | })); 158 | app.use(function(ctx) { 159 | ctx.body = { foo: 'bar' }; 160 | }); 161 | 162 | it('should not set `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy`', function(done) { 163 | request(app.listen()) 164 | .get('/') 165 | .set('Origin', 'http://koajs.com') 166 | .expect(res => { 167 | assert(!('Cross-Origin-Opener-Policy' in res.headers)); 168 | assert(!('Cross-Origin-Embedder-Policy' in res.headers)); 169 | }) 170 | .expect({ foo: 'bar' }) 171 | .expect(200, done); 172 | }); 173 | }); 174 | 175 | describe('options.origin=function', function() { 176 | const app = new Koa(); 177 | app.use(cors({ 178 | origin(ctx) { 179 | if (ctx.url === '/forbin') { 180 | return false; 181 | } 182 | return '*'; 183 | }, 184 | })); 185 | app.use(function(ctx) { 186 | ctx.body = { foo: 'bar' }; 187 | }); 188 | 189 | it('should disable cors', function(done) { 190 | request(app.listen()) 191 | .get('/forbin') 192 | .set('Origin', 'http://koajs.com') 193 | .expect({ foo: 'bar' }) 194 | .expect(200, function(err, res) { 195 | assert(!err); 196 | assert(!res.headers['access-control-allow-origin']); 197 | done(); 198 | }); 199 | }); 200 | 201 | it('should set access-control-allow-origin to *', function(done) { 202 | request(app.listen()) 203 | .get('/') 204 | .set('Origin', 'http://koajs.com') 205 | .expect({ foo: 'bar' }) 206 | .expect('Access-Control-Allow-Origin', '*') 207 | .expect(200, done); 208 | }); 209 | }); 210 | 211 | describe('options.origin=promise', function() { 212 | const app = new Koa(); 213 | app.use(cors({ 214 | origin(ctx) { 215 | return new Promise(resolve => { 216 | setTimeout(() => { 217 | if (ctx.url === '/forbin') { 218 | return resolve(false); 219 | } 220 | return resolve('*'); 221 | }, 100); 222 | }); 223 | }, 224 | })); 225 | app.use(function(ctx) { 226 | ctx.body = { foo: 'bar' }; 227 | }); 228 | 229 | it('should disable cors', function(done) { 230 | request(app.listen()) 231 | .get('/forbin') 232 | .set('Origin', 'http://koajs.com') 233 | .expect({ foo: 'bar' }) 234 | .expect(200, function(err, res) { 235 | assert(!err); 236 | assert(!res.headers['access-control-allow-origin']); 237 | done(); 238 | }); 239 | }); 240 | 241 | it('should set access-control-allow-origin to *', function(done) { 242 | request(app.listen()) 243 | .get('/') 244 | .set('Origin', 'http://koajs.com') 245 | .expect({ foo: 'bar' }) 246 | .expect('Access-Control-Allow-Origin', '*') 247 | .expect(200, done); 248 | }); 249 | }); 250 | 251 | describe('options.origin=async function', function() { 252 | const app = new Koa(); 253 | app.use(cors({ 254 | async origin(ctx) { 255 | if (ctx.url === '/forbin') { 256 | return false; 257 | } 258 | return '*'; 259 | }, 260 | })); 261 | app.use(function(ctx) { 262 | ctx.body = { foo: 'bar' }; 263 | }); 264 | 265 | it('should disable cors', function(done) { 266 | request(app.listen()) 267 | .get('/forbin') 268 | .set('Origin', 'http://koajs.com') 269 | .expect({ foo: 'bar' }) 270 | .expect(200, function(err, res) { 271 | assert(!err); 272 | assert(!res.headers['access-control-allow-origin']); 273 | done(); 274 | }); 275 | }); 276 | 277 | it('should set access-control-allow-origin to *', function(done) { 278 | request(app.listen()) 279 | .get('/') 280 | .set('Origin', 'http://koajs.com') 281 | .expect({ foo: 'bar' }) 282 | .expect('Access-Control-Allow-Origin', '*') 283 | .expect(200, done); 284 | }); 285 | 286 | it('behaves correctly when the return type is promise-like', function(done) { 287 | class WrappedPromise { 288 | constructor(...args) { 289 | this.internalPromise = new Promise(...args); 290 | } 291 | 292 | then(onFulfilled) { 293 | this.internalPromise.then(onFulfilled); 294 | } 295 | } 296 | 297 | const app = new Koa() 298 | .use(cors({ 299 | origin() { 300 | return new WrappedPromise(resolve => { 301 | return resolve('*'); 302 | }); 303 | }, 304 | })) 305 | .use(function(ctx) { 306 | ctx.body = { foo: 'bar' }; 307 | }); 308 | 309 | request(app.listen()) 310 | .get('/') 311 | .set('Origin', 'http://koajs.com') 312 | .expect({ foo: 'bar' }) 313 | .expect('Access-Control-Allow-Origin', '*') 314 | .expect(200, done); 315 | }); 316 | }); 317 | 318 | describe('options.exposeHeaders', function() { 319 | it('should Access-Control-Expose-Headers: `content-length`', function(done) { 320 | const app = new Koa(); 321 | app.use(cors({ 322 | exposeHeaders: 'content-length', 323 | })); 324 | app.use(function(ctx) { 325 | ctx.body = { foo: 'bar' }; 326 | }); 327 | 328 | request(app.listen()) 329 | .get('/') 330 | .set('Origin', 'http://koajs.com') 331 | .expect('Access-Control-Expose-Headers', 'content-length') 332 | .expect({ foo: 'bar' }) 333 | .expect(200, done); 334 | }); 335 | 336 | it('should work with array', function(done) { 337 | const app = new Koa(); 338 | app.use(cors({ 339 | exposeHeaders: [ 'content-length', 'x-header' ], 340 | })); 341 | app.use(function(ctx) { 342 | ctx.body = { foo: 'bar' }; 343 | }); 344 | 345 | request(app.listen()) 346 | .get('/') 347 | .set('Origin', 'http://koajs.com') 348 | .expect('Access-Control-Expose-Headers', 'content-length,x-header') 349 | .expect({ foo: 'bar' }) 350 | .expect(200, done); 351 | }); 352 | }); 353 | 354 | describe('options.maxAge', function() { 355 | it('should set maxAge with number', function(done) { 356 | const app = new Koa(); 357 | app.use(cors({ 358 | maxAge: 3600, 359 | })); 360 | app.use(function(ctx) { 361 | ctx.body = { foo: 'bar' }; 362 | }); 363 | 364 | request(app.listen()) 365 | .options('/') 366 | .set('Origin', 'http://koajs.com') 367 | .set('Access-Control-Request-Method', 'PUT') 368 | .expect('Access-Control-Max-Age', '3600') 369 | .expect(204, done); 370 | }); 371 | 372 | it('should set maxAge with string', function(done) { 373 | const app = new Koa(); 374 | app.use(cors({ 375 | maxAge: '3600', 376 | })); 377 | app.use(function(ctx) { 378 | ctx.body = { foo: 'bar' }; 379 | }); 380 | 381 | request(app.listen()) 382 | .options('/') 383 | .set('Origin', 'http://koajs.com') 384 | .set('Access-Control-Request-Method', 'PUT') 385 | .expect('Access-Control-Max-Age', '3600') 386 | .expect(204, done); 387 | }); 388 | 389 | it('should not set maxAge on simple request', function(done) { 390 | const app = new Koa(); 391 | app.use(cors({ 392 | maxAge: '3600', 393 | })); 394 | app.use(function(ctx) { 395 | ctx.body = { foo: 'bar' }; 396 | }); 397 | 398 | request(app.listen()) 399 | .get('/') 400 | .set('Origin', 'http://koajs.com') 401 | .expect({ foo: 'bar' }) 402 | .expect(200, function(err, res) { 403 | assert(!err); 404 | assert(!res.headers['access-control-max-age']); 405 | done(); 406 | }); 407 | }); 408 | }); 409 | 410 | describe('options.credentials', function() { 411 | const app = new Koa(); 412 | app.use(cors({ 413 | credentials: true, 414 | })); 415 | app.use(function(ctx) { 416 | ctx.body = { foo: 'bar' }; 417 | }); 418 | 419 | it('should enable Access-Control-Allow-Credentials on Simple request', function(done) { 420 | request(app.listen()) 421 | .get('/') 422 | .set('Origin', 'http://koajs.com') 423 | .expect('Access-Control-Allow-Credentials', 'true') 424 | .expect({ foo: 'bar' }) 425 | .expect(200, done); 426 | }); 427 | 428 | it('should enable Access-Control-Allow-Credentials on Preflight request', function(done) { 429 | request(app.listen()) 430 | .options('/') 431 | .set('Origin', 'http://koajs.com') 432 | .set('Access-Control-Request-Method', 'DELETE') 433 | .expect('Access-Control-Allow-Credentials', 'true') 434 | .expect(204, done); 435 | }); 436 | }); 437 | 438 | describe('options.credentials unset', function() { 439 | const app = new Koa(); 440 | app.use(cors()); 441 | app.use(function(ctx) { 442 | ctx.body = { foo: 'bar' }; 443 | }); 444 | 445 | it('should disable Access-Control-Allow-Credentials on Simple request', function(done) { 446 | request(app.listen()) 447 | .get('/') 448 | .set('Origin', 'http://koajs.com') 449 | .expect({ foo: 'bar' }) 450 | .expect(200) 451 | .end(function(error, response) { 452 | if (error) return done(error); 453 | 454 | const header = response.headers['access-control-allow-credentials']; 455 | assert.equal(header, undefined, 'Access-Control-Allow-Credentials must not be set.'); 456 | done(); 457 | }); 458 | }); 459 | 460 | it('should disable Access-Control-Allow-Credentials on Preflight request', function(done) { 461 | request(app.listen()) 462 | .options('/') 463 | .set('Origin', 'http://koajs.com') 464 | .set('Access-Control-Request-Method', 'DELETE') 465 | .expect(204) 466 | .end(function(error, response) { 467 | if (error) return done(error); 468 | 469 | const header = response.headers['access-control-allow-credentials']; 470 | assert.equal(header, undefined, 'Access-Control-Allow-Credentials must not be set.'); 471 | done(); 472 | }); 473 | }); 474 | }); 475 | 476 | describe('options.credentials=function', function() { 477 | const app = new Koa(); 478 | app.use(cors({ 479 | credentials(ctx) { 480 | return ctx.url !== '/forbin'; 481 | }, 482 | })); 483 | app.use(function(ctx) { 484 | ctx.body = { foo: 'bar' }; 485 | }); 486 | 487 | it('should enable Access-Control-Allow-Credentials on Simple request', function(done) { 488 | request(app.listen()) 489 | .get('/') 490 | .set('Origin', 'http://koajs.com') 491 | .expect('Access-Control-Allow-Credentials', 'true') 492 | .expect({ foo: 'bar' }) 493 | .expect(200, done); 494 | }); 495 | 496 | it('should enable Access-Control-Allow-Credentials on Preflight request', function(done) { 497 | request(app.listen()) 498 | .options('/') 499 | .set('Origin', 'http://koajs.com') 500 | .set('Access-Control-Request-Method', 'DELETE') 501 | .expect('Access-Control-Allow-Credentials', 'true') 502 | .expect(204, done); 503 | }); 504 | 505 | it('should disable Access-Control-Allow-Credentials on Simple request', function(done) { 506 | request(app.listen()) 507 | .get('/forbin') 508 | .set('Origin', 'http://koajs.com') 509 | .expect({ foo: 'bar' }) 510 | .expect(200) 511 | .end(function(error, response) { 512 | if (error) return done(error); 513 | 514 | const header = response.headers['access-control-allow-credentials']; 515 | assert.equal(header, undefined, 'Access-Control-Allow-Credentials must not be set.'); 516 | done(); 517 | }); 518 | }); 519 | 520 | it('should disable Access-Control-Allow-Credentials on Preflight request', function(done) { 521 | request(app.listen()) 522 | .options('/forbin') 523 | .set('Origin', 'http://koajs.com') 524 | .set('Access-Control-Request-Method', 'DELETE') 525 | .expect(204) 526 | .end(function(error, response) { 527 | if (error) return done(error); 528 | 529 | const header = response.headers['access-control-allow-credentials']; 530 | assert.equal(header, undefined, 'Access-Control-Allow-Credentials must not be set.'); 531 | done(); 532 | }); 533 | }); 534 | }); 535 | 536 | describe('options.credentials=async function', function() { 537 | const app = new Koa(); 538 | app.use(cors({ 539 | async credentials() { 540 | return true; 541 | }, 542 | })); 543 | app.use(function(ctx) { 544 | ctx.body = { foo: 'bar' }; 545 | }); 546 | 547 | it('should enable Access-Control-Allow-Credentials on Simple request', function(done) { 548 | request(app.listen()) 549 | .get('/') 550 | .set('Origin', 'http://koajs.com') 551 | .expect('Access-Control-Allow-Credentials', 'true') 552 | .expect({ foo: 'bar' }) 553 | .expect(200, done); 554 | }); 555 | 556 | it('should enable Access-Control-Allow-Credentials on Preflight request', function(done) { 557 | request(app.listen()) 558 | .options('/') 559 | .set('Origin', 'http://koajs.com') 560 | .set('Access-Control-Request-Method', 'DELETE') 561 | .expect('Access-Control-Allow-Credentials', 'true') 562 | .expect(204, done); 563 | }); 564 | 565 | it('behaves correctly when the return type is promise-like', function(done) { 566 | class WrappedPromise { 567 | constructor(...args) { 568 | this.internalPromise = new Promise(...args); 569 | } 570 | 571 | then(onFulfilled) { 572 | this.internalPromise.then(onFulfilled); 573 | } 574 | } 575 | 576 | const app = new Koa() 577 | .use(cors({ 578 | credentials() { 579 | return new WrappedPromise(resolve => { 580 | resolve(true); 581 | }); 582 | }, 583 | })) 584 | .use(function(ctx) { 585 | ctx.body = { foo: 'bar' }; 586 | }); 587 | 588 | request(app.listen()) 589 | .get('/') 590 | .set('Origin', 'http://koajs.com') 591 | .expect('Access-Control-Allow-Credentials', 'true') 592 | .expect({ foo: 'bar' }) 593 | .expect(200, done); 594 | }); 595 | }); 596 | 597 | describe('options.allowHeaders', function() { 598 | it('should work with allowHeaders is string', function(done) { 599 | const app = new Koa(); 600 | app.use(cors({ 601 | allowHeaders: 'X-PINGOTHER', 602 | })); 603 | app.use(function(ctx) { 604 | ctx.body = { foo: 'bar' }; 605 | }); 606 | 607 | request(app.listen()) 608 | .options('/') 609 | .set('Origin', 'http://koajs.com') 610 | .set('Access-Control-Request-Method', 'PUT') 611 | .expect('Access-Control-Allow-Headers', 'X-PINGOTHER') 612 | .expect(204, done); 613 | }); 614 | 615 | it('should work with allowHeaders is array', function(done) { 616 | const app = new Koa(); 617 | app.use(cors({ 618 | allowHeaders: [ 'X-PINGOTHER' ], 619 | })); 620 | app.use(function(ctx) { 621 | ctx.body = { foo: 'bar' }; 622 | }); 623 | 624 | request(app.listen()) 625 | .options('/') 626 | .set('Origin', 'http://koajs.com') 627 | .set('Access-Control-Request-Method', 'PUT') 628 | .expect('Access-Control-Allow-Headers', 'X-PINGOTHER') 629 | .expect(204, done); 630 | }); 631 | 632 | it('should set Access-Control-Allow-Headers to request access-control-request-headers header', function(done) { 633 | const app = new Koa(); 634 | app.use(cors()); 635 | app.use(function(ctx) { 636 | ctx.body = { foo: 'bar' }; 637 | }); 638 | 639 | request(app.listen()) 640 | .options('/') 641 | .set('Origin', 'http://koajs.com') 642 | .set('Access-Control-Request-Method', 'PUT') 643 | .set('access-control-request-headers', 'X-PINGOTHER') 644 | .expect('Access-Control-Allow-Headers', 'X-PINGOTHER') 645 | .expect(204, done); 646 | }); 647 | }); 648 | 649 | describe('options.allowMethods', function() { 650 | it('should work with allowMethods is array', function(done) { 651 | const app = new Koa(); 652 | app.use(cors({ 653 | allowMethods: [ 'GET', 'POST' ], 654 | })); 655 | app.use(function(ctx) { 656 | ctx.body = { foo: 'bar' }; 657 | }); 658 | 659 | request(app.listen()) 660 | .options('/') 661 | .set('Origin', 'http://koajs.com') 662 | .set('Access-Control-Request-Method', 'PUT') 663 | .expect('Access-Control-Allow-Methods', 'GET,POST') 664 | .expect(204, done); 665 | }); 666 | 667 | it('should skip allowMethods', function(done) { 668 | const app = new Koa(); 669 | app.use(cors({ 670 | allowMethods: null, 671 | })); 672 | app.use(function(ctx) { 673 | ctx.body = { foo: 'bar' }; 674 | }); 675 | 676 | request(app.listen()) 677 | .options('/') 678 | .set('Origin', 'http://koajs.com') 679 | .set('Access-Control-Request-Method', 'PUT') 680 | .expect(204, done); 681 | }); 682 | }); 683 | 684 | describe('options.headersKeptOnError', function() { 685 | it('should keep CORS headers after an error', function(done) { 686 | const app = new Koa(); 687 | app.use(cors({ 688 | origin(ctx) { 689 | return ctx.get('Origin') || '*'; 690 | }, 691 | })); 692 | app.use(function(ctx) { 693 | ctx.body = { foo: 'bar' }; 694 | throw new Error('Whoops!'); 695 | }); 696 | 697 | request(app.listen()) 698 | .get('/') 699 | .set('Origin', 'http://koajs.com') 700 | .expect('Access-Control-Allow-Origin', 'http://koajs.com') 701 | .expect('Vary', 'Origin') 702 | .expect(/Error/) 703 | .expect(500, done); 704 | }); 705 | 706 | it('should not affect OPTIONS requests', function(done) { 707 | const app = new Koa(); 708 | app.use(cors({ 709 | origin(ctx) { 710 | return ctx.get('Origin') || '*'; 711 | }, 712 | })); 713 | app.use(function(ctx) { 714 | ctx.body = { foo: 'bar' }; 715 | throw new Error('Whoops!'); 716 | }); 717 | 718 | request(app.listen()) 719 | .options('/') 720 | .set('Origin', 'http://koajs.com') 721 | .set('Access-Control-Request-Method', 'PUT') 722 | .expect('Access-Control-Allow-Origin', 'http://koajs.com') 723 | .expect(204, done); 724 | }); 725 | 726 | it('should not keep unrelated headers', function(done) { 727 | const app = new Koa(); 728 | app.use(cors({ 729 | origin(ctx) { 730 | return ctx.get('Origin') || '*'; 731 | }, 732 | })); 733 | app.use(function(ctx) { 734 | ctx.body = { foo: 'bar' }; 735 | ctx.set('X-Example', 'Value'); 736 | throw new Error('Whoops!'); 737 | }); 738 | 739 | request(app.listen()) 740 | .get('/') 741 | .set('Origin', 'http://koajs.com') 742 | .expect('Access-Control-Allow-Origin', 'http://koajs.com') 743 | .expect(/Error/) 744 | .expect(500, function(err, res) { 745 | if (err) { 746 | return done(err); 747 | } 748 | assert(!res.headers['x-example']); 749 | done(); 750 | }); 751 | }); 752 | 753 | it('should not keep CORS headers after an error if keepHeadersOnError is false', function(done) { 754 | const app = new Koa(); 755 | app.use(cors({ 756 | keepHeadersOnError: false, 757 | })); 758 | app.use(function(ctx) { 759 | ctx.body = { foo: 'bar' }; 760 | throw new Error('Whoops!'); 761 | }); 762 | 763 | request(app.listen()) 764 | .get('/') 765 | .set('Origin', 'http://koajs.com') 766 | .expect(/Error/) 767 | .expect(500, function(err, res) { 768 | if (err) { 769 | return done(err); 770 | } 771 | assert(!res.headers['access-control-allow-origin']); 772 | assert(!res.headers.vary); 773 | done(); 774 | }); 775 | }); 776 | }); 777 | 778 | describe('other middleware has been set `Vary` header to Accept-Encoding', function() { 779 | const app = new Koa(); 780 | app.use(function(ctx, next) { 781 | ctx.set('Vary', 'Accept-Encoding'); 782 | return next(); 783 | }); 784 | 785 | app.use(cors()); 786 | 787 | app.use(function(ctx) { 788 | ctx.body = { foo: 'bar' }; 789 | }); 790 | 791 | it('should append `Vary` header to Origin', function(done) { 792 | request(app.listen()) 793 | .get('/') 794 | .set('Origin', 'http://koajs.com') 795 | .expect('Vary', 'Accept-Encoding, Origin') 796 | .expect({ foo: 'bar' }) 797 | .expect(200, done); 798 | }); 799 | }); 800 | 801 | describe('other middleware has set vary header on Error', function() { 802 | it('should append `Origin to other `Vary` header', function(done) { 803 | const app = new Koa(); 804 | app.use(cors()); 805 | 806 | app.use(function(ctx) { 807 | ctx.body = { foo: 'bar' }; 808 | const error = new Error('Whoops!'); 809 | error.headers = { Vary: 'Accept-Encoding' }; 810 | throw error; 811 | }); 812 | 813 | request(app.listen()) 814 | .get('/') 815 | .set('Origin', 'http://koajs.com') 816 | .expect('Vary', 'Accept-Encoding, Origin') 817 | .expect(/Error/) 818 | .expect(500, done); 819 | }); 820 | it('should preserve `Vary: *`', function(done) { 821 | const app = new Koa(); 822 | app.use(cors()); 823 | 824 | app.use(function(ctx) { 825 | ctx.body = { foo: 'bar' }; 826 | const error = new Error('Whoops!'); 827 | error.headers = { Vary: '*' }; 828 | throw error; 829 | }); 830 | 831 | request(app.listen()) 832 | .get('/') 833 | .set('Origin', 'http://koajs.com') 834 | .expect('Vary', '*') 835 | .expect(/Error/) 836 | .expect(500, done); 837 | }); 838 | it('should not append Origin` if already present in `Vary`', function(done) { 839 | const app = new Koa(); 840 | app.use(cors()); 841 | 842 | app.use(function(ctx) { 843 | ctx.body = { foo: 'bar' }; 844 | const error = new Error('Whoops!'); 845 | error.headers = { Vary: 'Origin, Accept-Encoding' }; 846 | throw error; 847 | }); 848 | 849 | request(app.listen()) 850 | .get('/') 851 | .set('Origin', 'http://koajs.com') 852 | .expect('Vary', 'Origin, Accept-Encoding') 853 | .expect(/Error/) 854 | .expect(500, done); 855 | }); 856 | }); 857 | 858 | describe('options.privateNetworkAccess=false', function() { 859 | const app = new Koa(); 860 | app.use(cors({ 861 | privateNetworkAccess: false, 862 | })); 863 | 864 | app.use(function(ctx) { 865 | ctx.body = { foo: 'bar' }; 866 | }); 867 | 868 | it('should not set `Access-Control-Allow-Private-Network` on not OPTIONS', function(done) { 869 | request(app.listen()) 870 | .get('/') 871 | .set('Origin', 'http://koajs.com') 872 | .set('Access-Control-Request-Method', 'PUT') 873 | .expect(res => { 874 | assert(!('Access-Control-Allow-Private-Network' in res.headers)); 875 | }) 876 | .expect(200, done); 877 | }); 878 | 879 | it('should not set `Access-Control-Allow-Private-Network` if `Access-Control-Request-Private-Network` not exist on OPTIONS', function(done) { 880 | request(app.listen()) 881 | .options('/') 882 | .set('Origin', 'http://koajs.com') 883 | .set('Access-Control-Request-Method', 'PUT') 884 | .expect(res => { 885 | assert(!('Access-Control-Allow-Private-Network' in res.headers)); 886 | }) 887 | .expect(204, done); 888 | }); 889 | 890 | it('should not set `Access-Control-Allow-Private-Network` if `Access-Control-Request-Private-Network` exist on OPTIONS', function(done) { 891 | request(app.listen()) 892 | .options('/') 893 | .set('Origin', 'http://koajs.com') 894 | .set('Access-Control-Request-Method', 'PUT') 895 | .set('Access-Control-Request-Private-Network', 'true') 896 | .expect(res => { 897 | assert(!('Access-Control-Allow-Private-Network' in res.headers)); 898 | }) 899 | .expect(204, done); 900 | }); 901 | }); 902 | 903 | describe('options.privateNetworkAccess=true', function() { 904 | const app = new Koa(); 905 | app.use(cors({ 906 | privateNetworkAccess: true, 907 | })); 908 | 909 | app.use(function(ctx) { 910 | ctx.body = { foo: 'bar' }; 911 | }); 912 | 913 | it('should not set `Access-Control-Allow-Private-Network` on not OPTIONS', function(done) { 914 | request(app.listen()) 915 | .get('/') 916 | .set('Origin', 'http://koajs.com') 917 | .set('Access-Control-Request-Method', 'PUT') 918 | .expect(res => { 919 | assert(!('Access-Control-Allow-Private-Network' in res.headers)); 920 | }) 921 | .expect(200, done); 922 | }); 923 | 924 | it('should not set `Access-Control-Allow-Private-Network` if `Access-Control-Request-Private-Network` not exist on OPTIONS', function(done) { 925 | request(app.listen()) 926 | .options('/') 927 | .set('Origin', 'http://koajs.com') 928 | .set('Access-Control-Request-Method', 'PUT') 929 | .expect(res => { 930 | assert(!('Access-Control-Allow-Private-Network' in res.headers)); 931 | }) 932 | .expect(204, done); 933 | }); 934 | 935 | it('should always set `Access-Control-Allow-Private-Network` if `Access-Control-Request-Private-Network` exist on OPTIONS', function(done) { 936 | request(app.listen()) 937 | .options('/') 938 | .set('Origin', 'http://koajs.com') 939 | .set('Access-Control-Request-Method', 'PUT') 940 | .set('Access-Control-Request-Private-Network', 'true') 941 | .expect('Access-Control-Allow-Private-Network', 'true') 942 | .expect(204, done); 943 | }); 944 | }); 945 | 946 | describe('options.origin=*, and options.credentials=true', function() { 947 | const app = new Koa(); 948 | app.use(cors({ 949 | origin: '*', 950 | credentials: true, 951 | })); 952 | 953 | app.use(function(ctx) { 954 | ctx.body = { foo: 'bar' }; 955 | }); 956 | 957 | it('Access-Control-Allow-Origin should be request.origin, and Access-Control-Allow-Credentials should be true', function(done) { 958 | request(app.listen()) 959 | .get('/') 960 | .set('Origin', 'http://koajs.com') 961 | .expect('Access-Control-Allow-Credentials', 'true') 962 | .expect('Access-Control-Allow-Origin', 'http://koajs.com') 963 | .expect({ foo: 'bar' }) 964 | .expect(200, done); 965 | }); 966 | }); 967 | 968 | describe('options.origin=*, and options.credentials=false', function() { 969 | const app = new Koa(); 970 | app.use(cors({ 971 | origin: '*', 972 | credentials: false, 973 | })); 974 | 975 | app.use(function(ctx) { 976 | ctx.body = { foo: 'bar' }; 977 | }); 978 | 979 | it('Access-Control-Allow-Origin should be *', function(done) { 980 | request(app.listen()) 981 | .get('/') 982 | .set('Origin', 'http://koajs.com') 983 | .expect('Access-Control-Allow-Origin', '*') 984 | .expect({ foo: 'bar' }) 985 | .expect(200, done); 986 | }); 987 | }); 988 | }); 989 | --------------------------------------------------------------------------------