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

Commit

Permalink
fix: make http api only accept POST requests (#2977)
Browse files Browse the repository at this point in the history
* fix: make http api only accept POST requests

This is to prevent people maliciously controlling your local node
by injecting images into webpages with URLs of API endpoints.

BREAKING CHANGE:

Where we used to accept all and any HTTP methods, now only POST is
accepted.  The API client will now only send POST requests too.

* test: add tests to make sure we are post-only

* chore: upgrade ipfs-utils

* fix: return 405 instead of 404 for bad methods

* fix: reject browsers that do not send an origin

Also fixes running interface tests over http in browsers against
js-ipfs
  • Loading branch information
achingbrain committed Apr 10, 2020
1 parent e54da1d commit 943d4a8
Show file tree
Hide file tree
Showing 84 changed files with 966 additions and 466 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -64,7 +64,7 @@ jobs:
- stage: check
script:
- npm run build -- $RUN_SINCE --scope={ipfs,ipfs-http-client} -- -- --bundlesize
- npm run dep-check -- $RUN_SINCE -- -- -- -i wrtc -i electron-webrtc
- npm run dep-check -- $RUN_SINCE -- -- -- -i electron-webrtc
- npm run lint -- $RUN_SINCE --concurrency 1

- stage: test
Expand Down
2 changes: 1 addition & 1 deletion packages/interface-ipfs-core/package.json
Expand Up @@ -38,7 +38,7 @@
"dirty-chai": "^2.0.1",
"ipfs-block": "^0.8.1",
"ipfs-unixfs": "^1.0.1",
"ipfs-utils": "^1.2.4",
"ipfs-utils": "ipfs/js-ipfs-utils#fix/add-iterator-method-to-response",
"ipld-dag-cbor": "^0.15.1",
"ipld-dag-pb": "^0.18.3",
"is-ipfs": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/ipfs-core-utils/package.json
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"buffer": "^5.4.2",
"err-code": "^2.0.0",
"ipfs-utils": "^1.2.4"
"ipfs-utils": "ipfs/js-ipfs-utils#fix/add-iterator-method-to-response"
},
"devDependencies": {
"aegir": "21.4.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/ipfs-http-client/package.json
Expand Up @@ -47,7 +47,7 @@
"form-data": "^3.0.0",
"ipfs-block": "^0.8.1",
"ipfs-core-utils": "^0.1.1",
"ipfs-utils": "^1.2.4",
"ipfs-utils": "ipfs/js-ipfs-utils#fix/add-iterator-method-to-response",
"ipld-dag-cbor": "^0.15.1",
"ipld-dag-pb": "^0.18.3",
"ipld-raw": "^4.0.1",
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/add.js
Expand Up @@ -10,8 +10,7 @@ module.exports = configure((api) => {
return async function * add (input, options = {}) {
const progressFn = options.progress

const res = await api.ndjson('add', {
method: 'POST',
const res = await api.post('add', {
searchParams: toUrlSearchParams(null, {
...options,
'stream-channels': true,
Expand All @@ -24,7 +23,7 @@ module.exports = configure((api) => {
)
})

for await (let file of res) {
for await (let file of res.ndjson()) {
file = toCamel(file)

if (progressFn && file.bytes) {
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/block/rm.js
Expand Up @@ -23,13 +23,13 @@ module.exports = configure(api => {
searchParams.append('arg', new CID(cid).toString())
})

const res = await api.ndjson('block/rm', {
const res = await api.post('block/rm', {
timeout: options.timeout,
signal: options.signal,
searchParams: searchParams
})

for await (const removed of res) {
for await (const removed of res.ndjson()) {
yield toCoreInterface(removed)
}
}
Expand Down
9 changes: 3 additions & 6 deletions packages/ipfs-http-client/src/cat.js
@@ -1,7 +1,6 @@
'use strict'

const CID = require('cids')
const { Buffer } = require('buffer')
const merge = require('merge-options')
const configure = require('./lib/configure')

Expand All @@ -13,15 +12,13 @@ module.exports = configure(api => {
arg: typeof path === 'string' ? path : new CID(path).toString()
}
)
const res = await api.iterator('cat', {
method: 'POST',

const res = await api.post('cat', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

for await (const chunk of res) {
yield Buffer.from(chunk)
}
yield * res.iterator()
}
})
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/dht/find-peer.js
Expand Up @@ -9,13 +9,13 @@ module.exports = configure(api => {
return async function findPeer (peerId, options = {}) {
options.arg = `${Buffer.isBuffer(peerId) ? new CID(peerId) : peerId}`

const res = await api.ndjson('dht/findpeer', {
const res = await api.post('dht/findpeer', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

for await (const data of res) {
for await (const data of res.ndjson()) {
if (data.Type === 3) {
throw new Error(data.Extra)
}
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/dht/find-provs.js
Expand Up @@ -7,14 +7,13 @@ const configure = require('../lib/configure')
module.exports = configure(api => {
return async function * findProvs (cid, options = {}) {
options.arg = `${new CID(cid)}`
const res = await api.ndjson('dht/findprovs', {
method: 'POST',
const res = await api.post('dht/findprovs', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

for await (const message of res) {
for await (const message of res.ndjson()) {
// 3 = QueryError
// https://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L18
// https://github.com/libp2p/go-libp2p-kad-dht/blob/master/routing.go#L525-L526
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/dht/get.js
Expand Up @@ -11,14 +11,13 @@ module.exports = configure(api => {
}

options.key = encodeBufferURIComponent(key)
const res = await api.ndjson('dht/get', {
method: 'POST',
const res = await api.post('dht/get', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

for await (const message of res) {
for await (const message of res.ndjson()) {
// 3 = QueryError
// https://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L18
// https://github.com/ipfs/go-ipfs/blob/eb11f569b064b960d1aba4b5b8ca155a3bd2cb21/core/commands/dht.go#L472-L473
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/dht/provide.js
Expand Up @@ -12,14 +12,13 @@ module.exports = configure(api => {
const searchParams = new URLSearchParams(options)
cids.forEach(cid => searchParams.append('arg', `${new CID(cid)}`))

const res = await api.ndjson('dht/provide', {
method: 'POST',
const res = await api.post('dht/provide', {
timeout: options.timeout,
signal: options.signal,
searchParams
})

for await (let message of res) {
for await (let message of res.ndjson()) {
// 3 = QueryError
// https://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L18
// https://github.com/ipfs/go-ipfs/blob/eb11f569b064b960d1aba4b5b8ca155a3bd2cb21/core/commands/dht.go#L283-L284
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/dht/put.js
Expand Up @@ -11,13 +11,13 @@ module.exports = configure(api => {

searchParams.append('arg', key)
searchParams.append('arg', value)
const res = await api.ndjson('dht/put', {
const res = await api.post('dht/put', {
timeout: options.timeout,
signal: options.signal,
searchParams
})

for await (let message of res) {
for await (let message of res.ndjson()) {
// 3 = QueryError
// https://github.com/libp2p/go-libp2p-core/blob/6e566d10f4a5447317a66d64c7459954b969bdab/routing/query.go#L18
// https://github.com/ipfs/go-ipfs/blob/eb11f569b064b960d1aba4b5b8ca155a3bd2cb21/core/commands/dht.go#L472-L473
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/dht/query.js
Expand Up @@ -8,13 +8,13 @@ const configure = require('../lib/configure')
module.exports = configure(api => {
return async function * query (peerId, options = {}) {
options.arg = new CID(peerId)
const res = await api.ndjson('dht/query', {
const res = await api.post('dht/query', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

for await (let message of res) {
for await (let message of res.ndjson()) {
message = toCamel(message)
message.id = new CID(message.id)
message.responses = (message.responses || []).map(({ ID, Addrs }) => ({
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/files/ls.js
Expand Up @@ -12,8 +12,7 @@ module.exports = configure(api => {
path = '/'
}

const res = await api.ndjson('files/ls', {
method: 'POST',
const res = await api.post('files/ls', {
timeout: options.timeout,
signal: options.signal,
searchParams: toUrlSearchParams(
Expand All @@ -30,7 +29,7 @@ module.exports = configure(api => {
)
})

for await (const result of res) {
for await (const result of res.ndjson()) {
// go-ipfs does not yet support the "stream" option
if ('Entries' in result) {
for (const entry of result.Entries || []) {
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/get.js
Expand Up @@ -9,16 +9,15 @@ module.exports = configure(api => {
return async function * get (path, options = {}) {
options.arg = `${Buffer.isBuffer(path) ? new CID(path) : path}`

const res = await api.iterator('get', {
method: 'POST',
const res = await api.post('get', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

const extractor = Tar.extract()

for await (const { header, body } of extractor(res)) {
for await (const { header, body } of extractor(res.iterator())) {
if (header.type === 'directory') {
yield {
path: header.name
Expand Down
14 changes: 14 additions & 0 deletions packages/ipfs-http-client/src/lib/core.js
Expand Up @@ -7,6 +7,7 @@ const { URL } = require('iso-url')
const parseDuration = require('parse-duration')
const log = require('debug')('ipfs-http-client:lib:error-handler')
const HTTP = require('ipfs-utils/src/http')
const merge = require('merge-options')

const isMultiaddr = (input) => {
try {
Expand Down Expand Up @@ -126,6 +127,19 @@ class Client extends HTTP {
return out
}
})

delete this.get
delete this.put
delete this.delete
delete this.options

const fetch = this.fetch

this.fetch = (resource, options = {}) => {
return fetch.call(this, resource, merge(options, {
method: 'POST'
}))
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/log/tail.js
Expand Up @@ -4,12 +4,12 @@ const configure = require('../lib/configure')

module.exports = configure(api => {
return async function * tail (options = {}) {
const res = await api.ndjson('log/tail', {
const res = await api.post('log/tail', {
timeout: options.timeout,
signal: options.signal,
searchParams: options
})

yield * res
yield * res.ndjson()
}
})
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/ls.js
Expand Up @@ -9,14 +9,13 @@ module.exports = configure(api => {
const searchParams = new URLSearchParams(options)
searchParams.set('arg', `${Buffer.isBuffer(path) ? new CID(path) : path}`)

const res = await api.ndjson('ls', {
method: 'POST',
const res = await api.post('ls', {
timeout: options.timeout,
signal: options.signal,
searchParams
})

for await (let result of res) {
for await (let result of res.ndjson()) {
result = result.Objects

if (!result) {
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs-http-client/src/name/resolve.js
Expand Up @@ -8,13 +8,13 @@ module.exports = configure(api => {
searchParams.set('arg', path)
searchParams.set('stream', options.stream || true)

const res = await api.ndjson('name/resolve', {
const res = await api.post('name/resolve', {
timeout: options.timeout,
signal: options.signal,
searchParams
})

for await (const result of res) {
for await (const result of res.ndjson()) {
yield result.Path
}
}
Expand Down
5 changes: 2 additions & 3 deletions packages/ipfs-http-client/src/pin/ls.js
Expand Up @@ -16,14 +16,13 @@ module.exports = configure(api => {
searchParams.set('stream', options.stream || true)
path.forEach(p => searchParams.append('arg', `${p}`))

const source = api.ndjson('pin/ls', {
method: 'POST',
const res = await api.post('pin/ls', {
timeout: options.timeout,
signal: options.signal,
searchParams
})

for await (const pin of source) {
for await (const pin of res.ndjson()) {
if (pin.Keys) { // non-streaming response
// eslint-disable-next-line guard-for-in
for (const key in pin.Keys) {
Expand Down
7 changes: 4 additions & 3 deletions packages/ipfs-http-client/src/ping.js
Expand Up @@ -4,16 +4,17 @@ const toCamel = require('./lib/object-to-camel')
const configure = require('./lib/configure')

module.exports = configure(api => {
return function ping (peerId, options = {}) {
return async function * ping (peerId, options = {}) {
const searchParams = new URLSearchParams(options)
searchParams.set('arg', `${peerId}`)

return api.ndjson('ping', {
method: 'POST',
const res = await api.post('ping', {
timeout: options.timeout,
signal: options.signal,
searchParams,
transform: toCamel
})

yield * res.ndjson()
}
})
6 changes: 2 additions & 4 deletions packages/ipfs-http-client/src/pubsub/subscribe.js
Expand Up @@ -4,7 +4,6 @@ const bs58 = require('bs58')
const { Buffer } = require('buffer')
const log = require('debug')('ipfs-http-client:pubsub:subscribe')
const SubscriptionTracker = require('./subscription-tracker')
const { streamToAsyncIterator, ndjson } = require('../lib/core')
const configure = require('../lib/configure')

module.exports = configure((api, options) => {
Expand Down Expand Up @@ -32,8 +31,7 @@ module.exports = configure((api, options) => {
}, 1000)

try {
res = await api.stream('pubsub/sub', {
method: 'POST',
res = await api.post('pubsub/sub', {
timeout: options.timeout,
signal: options.signal,
searchParams
Expand All @@ -45,7 +43,7 @@ module.exports = configure((api, options) => {

clearTimeout(ffWorkaround)

readMessages(ndjson(streamToAsyncIterator(res)), {
readMessages(res.ndjson(), {
onMessage: handler,
onEnd: () => subsTracker.unsubscribe(topic, handler),
onError: options.onError
Expand Down

0 comments on commit 943d4a8

Please sign in to comment.