diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..0cdf54a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master", 1.x ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '40 12 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # 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 + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d443c80..9471deb 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,8 +12,6 @@ on: branches: - main - master - schedule: - - cron: '0 2 * * *' jobs: build: @@ -22,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [8, 10, 12, 14, 16] + node-version: [14, 16, 18, 20] os: [ubuntu-latest] steps: @@ -35,7 +33,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Dependencies - run: npm i -g npminstall && npminstall + run: npm i - name: Continuous Integration run: npm run ci diff --git a/.gitignore b/.gitignore index 8280239..c67c953 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.out *.pid *.gz +.idea +.DS_Store pids logs diff --git a/History.md b/History.md index d30e42b..ade6b6c 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,46 @@ +5.0.0 / 2023-12-11 +================== + +**others** + * [[`f31dac9`](http://github.com/koajs/cors/commit/f31dac99f5355c41e7d4dd3c4a80c5f154941a11)] - Merge pull request from GHSA-qxrj-hx23-xp82 (fengmk2 <>) + +4.0.0 / 2022-10-08 +================== + +**fixes** + * [[`7358ab3`](http://github.com/koajs/cors/commit/7358ab381af6413013938f49c56ac79a7453d35c)] - fix: Calling all options even if origin header is not present (#87) (Cleber Rossi <>) + +**others** + * [[`d19090f`](http://github.com/koajs/cors/commit/d19090fc8591059895fa9c606967d3a67fd3c5b8)] - refactor: [BREAKING] drop node 8, 10, 12 support (#88) (fengmk2 <>) + +3.4.3 / 2022-10-08 +================== + +**others** + * [[`208b86c`](http://github.com/koajs/cors/commit/208b86c893013d65e4479219aae0763b807bc8a6)] - Revert "fix: Calling all options even if origin header is not present (#87)" (fengmk2 <>) + +3.4.2 / 2022-10-06 +================== + +**fixes** + * [[`2e8da5b`](http://github.com/koajs/cors/commit/2e8da5bd2acbc9c1adfabdea459982b3d5bdd31f)] - fix: Calling all options even if origin header is not present (#87) (Cleber Rossi <>) + +3.4.1 / 2022-08-19 +================== + +**fixes** + * [[`1205356`](http://github.com/koajs/cors/commit/12053567ef2caa8f4191298bc9d010017bb0f233)] - fix: must specify an origin value instead of "*" wildcard (#85) (Tyreal Hu <>) + +3.4.0 / 2022-08-19 +================== + +**others** + * [[`2cd4789`](http://github.com/koajs/cors/commit/2cd4789f66a64cd13228e7305cce9069bd2d1283)] - 🤖 TEST: Run test on Node.js 18 (#86) (fengmk2 <>) + * [[`ae56e05`](http://github.com/koajs/cors/commit/ae56e054cb669c73784f8a12ab6413abca6eff57)] - Create codeql-analysis.yml (fengmk2 <>) + * [[`c4b5d21`](http://github.com/koajs/cors/commit/c4b5d21e0cf5ab76109be65f4b7267d0ccacce81)] - refactor: use friendlier promise checking (#84) (Swain Molster <>) + * [[`fbe33bc`](http://github.com/koajs/cors/commit/fbe33bca26373965429356f02144507c31326cfc)] - 📖 DOC: Add privateNetworkAccess js to README (fengmk2 <>) + 3.3.0 / 2022-03-29 ================== diff --git a/LICENSE b/LICENSE index a2efa34..e97544e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ This software is licensed under the MIT License. -Copyright (c) 2015 - 2018 koajs and other contributors +Copyright (c) 2015 - present koajs and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b4c83d7..8eb3bdb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -@koa/cors -======= +# @koa/cors [![NPM version][npm-image]][npm-url] [![Node.js CI](https://github.com/koajs/cors/actions/workflows/nodejs.yml/badge.svg)](https://github.com/koajs/cors/actions/workflows/nodejs.yml) @@ -43,7 +42,8 @@ app.use(cors()); * CORS middleware * * @param {Object} [options] - * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header + * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is '*' + * If `credentials` set and return `true, the `origin` default value will set to the request `Origin` header * - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH' * - {String|Array} exposeHeaders `Access-Control-Expose-Headers` * - {String|Array} allowHeaders `Access-Control-Allow-Headers` @@ -51,11 +51,24 @@ app.use(cors()); * - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`, default is false. * - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown * - {Boolean} secureContext `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy` headers.', default is false + * - {Boolean} privateNetworkAccess handle `Access-Control-Request-Private-Network` request by return `Access-Control-Allow-Private-Network`, default to false * @return {Function} cors middleware * @api public */ ``` +## Breaking change between 5.0 and 4.0 + +The default `origin` is set to `*`, if you want to keep the 4.0 behavior, you can set the `origin` handler like this: + +```js +app.use(cors({ + origin(ctx) { + return ctx.get('Origin') || '*'; + }, +})); +``` + ## License [MIT](./LICENSE) @@ -66,9 +79,9 @@ app.use(cors()); |[
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)
| | :---: | :---: | :---: | :---: | :---: | :---: | -|[
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)
|[
PlasmaPower](https://github.com/PlasmaPower)
| -[
xg-wang](https://github.com/xg-wang)
|[
lishengzxc](https://github.com/lishengzxc)
|[
mcohen75](https://github.com/mcohen75)
+|[
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)
| +[
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)
-This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Mar 29 2022 18:21:55 GMT+0800`. +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`. diff --git a/index.js b/index.js index 5b1d8e4..5be3702 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,11 @@ -'use strict'; - const vary = require('vary'); /** * CORS middleware * * @param {Object} [options] - * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header + * - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is '*' + * If `credentials` set and return `true, the `origin` default value will set to the request `Origin` header * - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH' * - {String|Array} exposeHeaders `Access-Control-Expose-Headers` * - {String|Array} allowHeaders `Access-Control-Allow-Headers` @@ -14,8 +13,9 @@ const vary = require('vary'); * - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials` * - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown * - {Boolean} secureContext `Cross-Origin-Opener-Policy` & `Cross-Origin-Embedder-Policy` headers.', default is false - * - {Boolean} privateNetworkAccess handle `Access-Control-Request-Private-Network` request by return `Access-Control-Allow-Private-Network`, default to false * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/Planned_changes + * - {Boolean} privateNetworkAccess handle `Access-Control-Request-Private-Network` request by return `Access-Control-Allow-Private-Network`, default to false + * @see https://wicg.github.io/private-network-access/ * @return {Function} cors middleware * @public */ @@ -57,25 +57,27 @@ module.exports = function(options) { // https://github.com/rs/cors/issues/10 ctx.vary('Origin'); - if (!requestOrigin) return await next(); - let origin; if (typeof options.origin === 'function') { - origin = options.origin(ctx); - if (origin instanceof Promise) origin = await origin; - if (!origin) return await next(); + origin = await options.origin(ctx); + if (!origin) { + return await next(); + } } else { - origin = options.origin || requestOrigin; + origin = options.origin || '*'; } let credentials; if (typeof options.credentials === 'function') { - credentials = options.credentials(ctx); - if (credentials instanceof Promise) credentials = await credentials; + credentials = await options.credentials(ctx); } else { credentials = !!options.credentials; } + if (credentials && origin === '*') { + origin = requestOrigin; + } + const headersSet = {}; function set(key, value) { diff --git a/package.json b/package.json index cf7e43b..a640909 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@koa/cors", - "version": "3.3.0", + "version": "5.0.0", "description": "Cross-Origin Resource Sharing(CORS) for koa", "main": "index.js", "files": [ @@ -17,13 +17,13 @@ "vary": "^1.1.2" }, "devDependencies": { - "egg-ci": "^1.19.0", - "eslint": "^5.15.1", - "eslint-config-egg": "^7.1.0", + "egg-ci": "^2.1.0", + "eslint": "^8.25.0", + "eslint-config-egg": "^12.0.0", "git-contributor": "^1.0.10", "istanbul": "*", "koa": "^2.5.1", - "mocha": "3", + "mocha": "^3.5.3", "supertest": "^3.1.0" }, "homepage": "https://github.com/koajs/cors", @@ -43,14 +43,11 @@ "koajs" ], "engines": { - "node": ">= 8.0.0" + "node": ">= 14.0.0" }, "ci": { - "version": "8, 10, 12, 14, 16", - "type": "github", - "os": { - "github": "linux" - } + "version": "14, 16, 18, 20", + "os": "linux" }, "author": "fengmk2 (http://github.com/fengmk2)", "license": "MIT" diff --git a/test/cors.test.js b/test/cors.test.js index 58e9a05..84a5679 100644 --- a/test/cors.test.js +++ b/test/cors.test.js @@ -1,9 +1,7 @@ -'use strict'; - const assert = require('assert'); const Koa = require('koa'); const request = require('supertest'); -const cors = require('../'); +const cors = require('..'); describe('cors.test.js', function() { describe('default options', function() { @@ -13,22 +11,19 @@ describe('cors.test.js', function() { ctx.body = { foo: 'bar' }; }); - it('should not set `Access-Control-Allow-Origin` when request Origin header missing', function(done) { + it('should set `Access-Control-Allow-Origin` to `*` when request Origin header missing', function(done) { request(app.listen()) .get('/') .expect({ foo: 'bar' }) - .expect(200, function(err, res) { - assert(!err); - assert(!res.headers['access-control-allow-origin']); - done(); - }); + .expect('access-control-allow-origin', '*') + .expect(200, done); }); - it('should set `Access-Control-Allow-Origin` to request origin header', function(done) { + it('should set `Access-Control-Allow-Origin` to `*`', function(done) { request(app.listen()) .get('/') .set('Origin', 'http://koajs.com') - .expect('Access-Control-Allow-Origin', 'http://koajs.com') + .expect('Access-Control-Allow-Origin', '*') .expect({ foo: 'bar' }) .expect(200, done); }); @@ -38,7 +33,7 @@ describe('cors.test.js', function() { .options('/') .set('Origin', 'http://koajs.com') .set('Access-Control-Request-Method', 'PUT') - .expect('Access-Control-Allow-Origin', 'http://koajs.com') + .expect('Access-Control-Allow-Origin', '*') .expect('Access-Control-Allow-Methods', 'GET,HEAD,PUT,POST,DELETE,PATCH') .expect(204, done); }); @@ -77,6 +72,52 @@ describe('cors.test.js', function() { .expect({ foo: 'bar' }) .expect(200, done); }); + + it('should always set `Access-Control-Allow-Origin` to *, even if no Origin is passed on request', function(done) { + request(app.listen()) + .get('/') + .expect('Access-Control-Allow-Origin', '*') + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); + + describe('options.origin set the request Origin header', function() { + const app = new Koa(); + app.use(cors({ + origin(ctx) { + return ctx.get('Origin') || '*'; + }, + })); + app.use(function(ctx) { + ctx.body = { foo: 'bar' }; + }); + + it('should set `Access-Control-Allow-Origin` to request `Origin` header', function(done) { + request(app.listen()) + .get('/') + .set('Origin', 'http://koajs.com') + .expect('Access-Control-Allow-Origin', 'http://koajs.com') + .expect({ foo: 'bar' }) + .expect(200, done); + }); + + it('should set `Access-Control-Allow-Origin` to request `origin` header', function(done) { + request(app.listen()) + .get('/') + .set('origin', 'http://origin.koajs.com') + .expect('Access-Control-Allow-Origin', 'http://origin.koajs.com') + .expect({ foo: 'bar' }) + .expect(200, done); + }); + + it('should set `Access-Control-Allow-Origin` to `*`, even if no Origin is passed on request', function(done) { + request(app.listen()) + .get('/') + .expect('Access-Control-Allow-Origin', '*') + .expect({ foo: 'bar' }) + .expect(200, done); + }); }); describe('options.secureContext=true', function() { @@ -167,6 +208,46 @@ describe('cors.test.js', function() { }); }); + describe('options.origin=promise', function() { + const app = new Koa(); + app.use(cors({ + origin(ctx) { + return new Promise(resolve => { + setTimeout(() => { + if (ctx.url === '/forbin') { + return resolve(false); + } + return resolve('*'); + }, 100); + }); + }, + })); + app.use(function(ctx) { + ctx.body = { foo: 'bar' }; + }); + + it('should disable cors', function(done) { + request(app.listen()) + .get('/forbin') + .set('Origin', 'http://koajs.com') + .expect({ foo: 'bar' }) + .expect(200, function(err, res) { + assert(!err); + assert(!res.headers['access-control-allow-origin']); + done(); + }); + }); + + it('should set access-control-allow-origin to *', function(done) { + request(app.listen()) + .get('/') + .set('Origin', 'http://koajs.com') + .expect({ foo: 'bar' }) + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + }); + describe('options.origin=async function', function() { const app = new Koa(); app.use(cors({ @@ -201,6 +282,37 @@ describe('cors.test.js', function() { .expect('Access-Control-Allow-Origin', '*') .expect(200, done); }); + + it('behaves correctly when the return type is promise-like', function(done) { + class WrappedPromise { + constructor(...args) { + this.internalPromise = new Promise(...args); + } + + then(onFulfilled) { + this.internalPromise.then(onFulfilled); + } + } + + const app = new Koa() + .use(cors({ + origin() { + return new WrappedPromise(resolve => { + return resolve('*'); + }); + }, + })) + .use(function(ctx) { + ctx.body = { foo: 'bar' }; + }); + + request(app.listen()) + .get('/') + .set('Origin', 'http://koajs.com') + .expect({ foo: 'bar' }) + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); }); describe('options.exposeHeaders', function() { @@ -449,6 +561,37 @@ describe('cors.test.js', function() { .expect('Access-Control-Allow-Credentials', 'true') .expect(204, done); }); + + it('behaves correctly when the return type is promise-like', function(done) { + class WrappedPromise { + constructor(...args) { + this.internalPromise = new Promise(...args); + } + + then(onFulfilled) { + this.internalPromise.then(onFulfilled); + } + } + + const app = new Koa() + .use(cors({ + credentials() { + return new WrappedPromise(resolve => { + resolve(true); + }); + }, + })) + .use(function(ctx) { + ctx.body = { foo: 'bar' }; + }); + + request(app.listen()) + .get('/') + .set('Origin', 'http://koajs.com') + .expect('Access-Control-Allow-Credentials', 'true') + .expect({ foo: 'bar' }) + .expect(200, done); + }); }); describe('options.allowHeaders', function() { @@ -541,7 +684,11 @@ describe('cors.test.js', function() { describe('options.headersKeptOnError', function() { it('should keep CORS headers after an error', function(done) { const app = new Koa(); - app.use(cors()); + app.use(cors({ + origin(ctx) { + return ctx.get('Origin') || '*'; + }, + })); app.use(function(ctx) { ctx.body = { foo: 'bar' }; throw new Error('Whoops!'); @@ -558,7 +705,11 @@ describe('cors.test.js', function() { it('should not affect OPTIONS requests', function(done) { const app = new Koa(); - app.use(cors()); + app.use(cors({ + origin(ctx) { + return ctx.get('Origin') || '*'; + }, + })); app.use(function(ctx) { ctx.body = { foo: 'bar' }; throw new Error('Whoops!'); @@ -574,7 +725,11 @@ describe('cors.test.js', function() { it('should not keep unrelated headers', function(done) { const app = new Koa(); - app.use(cors()); + app.use(cors({ + origin(ctx) { + return ctx.get('Origin') || '*'; + }, + })); app.use(function(ctx) { ctx.body = { foo: 'bar' }; ctx.set('X-Example', 'Value'); @@ -642,6 +797,7 @@ describe('cors.test.js', function() { .expect(200, done); }); }); + describe('other middleware has set vary header on Error', function() { it('should append `Origin to other `Vary` header', function(done) { const app = new Koa(); @@ -787,4 +943,46 @@ describe('cors.test.js', function() { }); }); + describe('options.origin=*, and options.credentials=true', function() { + const app = new Koa(); + app.use(cors({ + origin: '*', + credentials: true, + })); + + app.use(function(ctx) { + ctx.body = { foo: 'bar' }; + }); + + it('Access-Control-Allow-Origin should be request.origin, and Access-Control-Allow-Credentials should be true', function(done) { + request(app.listen()) + .get('/') + .set('Origin', 'http://koajs.com') + .expect('Access-Control-Allow-Credentials', 'true') + .expect('Access-Control-Allow-Origin', 'http://koajs.com') + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); + + describe('options.origin=*, and options.credentials=false', function() { + const app = new Koa(); + app.use(cors({ + origin: '*', + credentials: false, + })); + + app.use(function(ctx) { + ctx.body = { foo: 'bar' }; + }); + + it('Access-Control-Allow-Origin should be *', function(done) { + request(app.listen()) + .get('/') + .set('Origin', 'http://koajs.com') + .expect('Access-Control-Allow-Origin', '*') + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); });