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 with safelisted Origins.

##### 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
26 changes: 0 additions & 26 deletions packages/ipfs/src/http/api/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
'use strict'

const Boom = require('@hapi/boom')

// RPC API requires POST, we block every other method
const BLOCKED_METHODS = [
'GET',
'PUT',
'PATCH',
'DELETE',
'OPTIONS'
]

const routes = [
require('./version'),
require('./shutdown'),
Expand Down Expand Up @@ -40,19 +29,4 @@ const routes = [
// webui is loaded from API port, but works over GET (not a part of RPC API)
const extraRoutes = [...require('./webui')]

const handler = () => {
throw Boom.methodNotAllowed()
}

// add routes that return HTTP 504 for non-POST requests to RPC API
BLOCKED_METHODS.forEach(method => {
routes.forEach(route => {
extraRoutes.push({
method,
handler,
path: route.path
})
})
})

module.exports = routes.concat(extraRoutes)
10 changes: 9 additions & 1 deletion packages/ipfs/src/http/error-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,18 @@ module.exports = server => {
server.logger.error(res)
}

return h.response({
const response = h.response({
Message: message,
Code: code,
Type: 'error'
}).code(statusCode)

const headers = res.output.headers || {}

Object.keys(headers).forEach(header => {
response.header(header, headers[header])
})

return response
})
}
68 changes: 43 additions & 25 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,22 @@ class HttpApi {
return this
}

async _createApiServer (host, port, ipfs) {
async _createApiServer (host, port, ipfs, cors) {
cors = {
...cors,
additionalHeaders: ['X-Stream-Output', 'X-Chunked-Output', 'X-Content-Length'],
additionalExposedHeaders: ['X-Stream-Output', 'X-Chunked-Output', 'X-Content-Length']
}

if (!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 All @@ -95,6 +108,29 @@ class HttpApi {
}
})

// block all non-post or non-options requests
server.ext({
type: 'onRequest',
method: function (request, h) {
if (request.method === 'post' || request.method === 'options') {
return h.continue
}

throw Boom.methodNotAllowed()
}
})

// https://tools.ietf.org/html/rfc7231#section-6.5.5
server.ext('onPreResponse', (request, h) => {
const { response } = request

if (response.isBoom && response.output && response.output.statusCode === 405) {
response.output.headers.Allow = 'OPTIONS, POST'
}

return h.continue
})

// https://github.com/ipfs/go-ipfs-cmds/pull/193/files
server.ext({
type: 'onRequest',
Expand Down Expand Up @@ -144,24 +180,6 @@ class HttpApi {
}
})

const setHeader = (key, value) => {
server.ext('onPreResponse', (request, h) => {
const { response } = request
if (response.isBoom) {
response.output.headers[key] = value
} else {
response.header(key, value)
}
return h.continue
})
}

// Set default headers
setHeader('Access-Control-Allow-Headers',
'X-Stream-Output, X-Chunked-Output, X-Content-Length')
setHeader('Access-Control-Expose-Headers',
'X-Stream-Output, X-Chunked-Output, X-Content-Length')

server.route(require('./api/routes'))

errorHandler(server)
Expand Down
Loading