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

Commit

Permalink
fix: disable cors by default (#3275)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcin Rataj <[email protected]>

Brings js-ipfs into line with go-ipfs by not having CORS on by default, instead requiring the user to explicitly configure it.

BREAKING CHANGE:

- CORS origins will need to be [configured manually](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs-http-client/README.md#cors) before use with ipfs-http-client
  • Loading branch information
achingbrain authored Sep 14, 2020
1 parent 494949a commit 3ff833d
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 55 deletions.
59 changes: 59 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,55 @@ 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

If you are running a webapp locally that you access via the URL `http://127.0.0.1:3000`, you must add it to the list of allowed origins in order to make API requests from that webapp in the browser:

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

Note that the origin must match exactly so `'http://127.0.0.1:3000'` is treated differently to `'http://127.0.0.1:3000/'`

#### `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 example below for how to configure it.

##### 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
})
}
73 changes: 48 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,34 @@ 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
}

if (request.method === 'get' && (request.path.startsWith('/ipfs') || request.path.startsWith('/webui'))) {
// allow requests to the webui
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 +185,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

0 comments on commit 3ff833d

Please sign in to comment.