Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

fix: disable cors by default #3275

Merged
merged 10 commits into from
Sep 14, 2020
57 changes: 57 additions & 0 deletions docs/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ The js-ipfs config file is a JSON document located in the root directory of the
- [`Enabled`](#enabled)
- [`Swarm`](#swarm-1)
- [`ConnMgr`](#connmgr)
- [Example](#example)
- [`API`](#api-1)
- [`HTTPHeaders`](#httpheaders)
- [`Access-Control-Allow-Origin`](#access-control-allow-origin)
- [Example](#example-1)
- [`Access-Control-Allow-Credentials`](#access-control-allow-credentials)
- [Example](#example-2)

## Profiles

Expand Down Expand Up @@ -264,3 +271,53 @@ The "basic" connection manager tries to keep between `LowWater` and `HighWater`
}
}
```

## `API`

Settings applied to the HTTP RPC API server

### `HTTPHeaders`

HTTP header settings used by the HTTP RPC API server

#### `Access-Control-Allow-Origin`

The RPC API endpoints running on your local node are protected by the [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) mechanism.

When a request is made that sends an `Origin` header, that Origin must be present in the allowed origins configured for the node, otherwise the browser will disallow that request to proceed, unless `mode: 'no-cors'` is set on the request, in which case the response will be opaque.

To allow requests from web browsers, configure the `API.HTTPHeaders.Access-Control-Allow-Origin` setting. This is an array of URL strings and/or the wildcard `'*'` character.
achingbrain marked this conversation as resolved.
Show resolved Hide resolved

##### Example

```json
{
"API": {
"HTTPHeaders": {
"Access-Control-Allow-Origin": [
"http://127.0.0.1:8080"
]
}
}
}
```

Note that the origin must match exactly so `'http://127.0.0.1:8080'` is treated differently to `'http://127.0.0.1:8080/'`
achingbrain marked this conversation as resolved.
Show resolved Hide resolved

#### `Access-Control-Allow-Credentials`

The [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header allows client-side JavaScript running in the browser to send and receive credentials with requests - cookies, auth headers or TLS certificates.

For most applications this will not be necessary but if you require this to be set, see the examnple below for how to configure it.
achingbrain marked this conversation as resolved.
Show resolved Hide resolved

##### Example

```json
{
"API": {
"HTTPHeaders": {
"Access-Control-Allow-Credentials": true
}
}
}
```
2 changes: 2 additions & 0 deletions packages/ipfs-http-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ $ ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["http://exam
$ ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "POST", "GET"]'
```

If you are using `js-ipfs`, substitute `ipfs` for `jsipfs` in the commands above.

### Custom Headers

If you wish to send custom headers with each request made by this library, for example, the Authorization header. You can use the config to do so:
Expand Down
21 changes: 14 additions & 7 deletions packages/ipfs/src/http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ function hapiInfoToMultiaddr (info) {
return toMultiaddr(uri)
}

async function serverCreator (serverAddrs, createServer, ipfs) {
async function serverCreator (serverAddrs, createServer, ipfs, cors) {
serverAddrs = serverAddrs || []
// just in case the address is just string
serverAddrs = Array.isArray(serverAddrs) ? serverAddrs : [serverAddrs]

const servers = []
for (const address of serverAddrs) {
const addrParts = address.split('/')
const server = await createServer(addrParts[2], addrParts[4], ipfs)
const server = await createServer(addrParts[2], addrParts[4], ipfs, cors)
await server.start()
server.info.ma = hapiInfoToMultiaddr(server.info)
servers.push(server)
Expand All @@ -56,9 +56,14 @@ class HttpApi {

const config = await ipfs.config.getAll()
config.Addresses = config.Addresses || {}
config.API = config.API || {}
config.API.HTTPHeaders = config.API.HTTPHeaders || {}

const apiAddrs = config.Addresses.API
this._apiServers = await serverCreator(apiAddrs, this._createApiServer, ipfs)
this._apiServers = await serverCreator(apiAddrs, this._createApiServer, ipfs, {
origin: config.API.HTTPHeaders['Access-Control-Allow-Origin'] || [],
credentials: Boolean(config.API.HTTPHeaders['Access-Control-Allow-Credentials'])
})

const gatewayAddrs = config.Addresses.Gateway
this._gatewayServers = await serverCreator(gatewayAddrs, this._createGatewayServer, ipfs)
Expand All @@ -67,14 +72,16 @@ class HttpApi {
return this
}

async _createApiServer (host, port, ipfs) {
async _createApiServer (host, port, ipfs, cors) {
if (!cors || !cors.origin || !cors.origin.length) {
cors = false
}

const server = Hapi.server({
host,
port,
// CORS is enabled by default
// TODO: shouldn't, fix this
routes: {
cors: true,
cors,
response: {
emptyStatusCode: 200
}
Expand Down
123 changes: 123 additions & 0 deletions packages/ipfs/test/http-api/cors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'

const { expect } = require('aegir/utils/chai')
const http = require('../utils/http')
const sinon = require('sinon')

describe('cors', () => {
let ipfs

beforeEach(() => {
ipfs = {
id: sinon.stub().returns({
id: 'id',
publicKey: 'publicKey',
addresses: 'addresses',
agentVersion: 'agentVersion',
protocolVersion: 'protocolVersion'
})
}
})

describe('should allow configuring CORS', () => {
lidel marked this conversation as resolved.
Show resolved Hide resolved
it('does not return allowed origins when cors is not configured and origin is supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, { ipfs })

expect(res).to.not.have.nested.property('headers.access-control-allow-origin')
})

it('returns allowed origins when origin is supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, { ipfs, cors: { origin: [origin] } })

expect(res).to.have.nested.property('headers.access-control-allow-origin', origin)
})

it('does not allow credentials when omitted in config', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, { ipfs, cors: { origin: [origin] } })

expect(res).to.not.have.nested.property('headers.access-control-allow-credentials')
})

it('returns allowed credentials when origin is supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, { ipfs, cors: { origin: [origin], credentials: true } })

expect(res).to.have.nested.property('headers.access-control-allow-credentials', 'true')
})

it('does not return allowed origins when origin is not supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id'
}, { ipfs, cors: { origin: [origin] } })

expect(res).to.not.have.nested.property('headers.access-control-allow-origin')
})

it('does not return allowed credentials when origin is not supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id'
}, { ipfs, cors: { origin: [origin], credentials: true } })

expect(res).to.not.have.nested.property('headers.access-control-allow-credentials')
})

it('does not return allowed origins when different origin is supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin: origin + '/'
}
}, { ipfs, cors: { origin: [origin] } })

expect(res).to.not.have.nested.property('headers.access-control-allow-origin')
})

it('allows wildcard origins', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin: origin + '/'
}
}, { ipfs, cors: { origin: ['*'] } })

expect(res).to.have.nested.property('headers.access-control-allow-origin', origin + '/')
})
})
})
1 change: 1 addition & 0 deletions packages/ipfs/test/http-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ if (isNode) {
require('./routes')
}

require('./cors')
require('./interface')
4 changes: 2 additions & 2 deletions packages/ipfs/test/utils/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

const HttpApi = require('../../src/http')

module.exports = async (request, { ipfs } = {}) => {
module.exports = async (request, { ipfs, cors } = {}) => {
const api = new HttpApi(ipfs)
const server = await api._createApiServer('127.0.0.1', 8080, ipfs)
const server = await api._createApiServer('127.0.0.1', 8080, ipfs, cors)

return server.inject(request)
}