Skip to content

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 authored Apr 10, 2020
1 parent b2c6a34 commit a3a84a7
Show file tree
Hide file tree
Showing 23 changed files with 68 additions and 62 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
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 src/add.js
Original file line number Diff line number Diff line change
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 src/block/rm.js
Original file line number Diff line number Diff line change
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 src/cat.js
Original file line number Diff line number Diff line change
@@ -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 src/dht/find-peer.js
Original file line number Diff line number Diff line change
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 src/dht/find-provs.js
Original file line number Diff line number Diff line change
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 src/dht/get.js
Original file line number Diff line number Diff line change
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 src/dht/provide.js
Original file line number Diff line number Diff line change
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 src/dht/put.js
Original file line number Diff line number Diff line change
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 src/dht/query.js
Original file line number Diff line number Diff line change
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 src/files/ls.js
Original file line number Diff line number Diff line change
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 src/get.js
Original file line number Diff line number Diff line change
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 src/lib/core.js
Original file line number Diff line number Diff line change
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 src/log/tail.js
Original file line number Diff line number Diff line change
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 src/ls.js
Original file line number Diff line number Diff line change
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 src/name/resolve.js
Original file line number Diff line number Diff line change
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 src/pin/ls.js
Original file line number Diff line number Diff line change
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 src/ping.js
Original file line number Diff line number Diff line change
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 src/pubsub/subscribe.js
Original file line number Diff line number Diff line change
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
7 changes: 4 additions & 3 deletions src/refs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const toCamel = require('../lib/object-to-camel')
const configure = require('../lib/configure')

module.exports = configure((api, options) => {
const refs = (args, options = {}) => {
const refs = async function * (args, options = {}) {
const searchParams = new URLSearchParams(options)

if (!Array.isArray(args)) {
Expand All @@ -17,13 +17,14 @@ module.exports = configure((api, options) => {
searchParams.append('arg', `${Buffer.isBuffer(arg) ? new CID(arg) : arg}`)
}

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

yield * res.ndjson()
}
refs.local = require('./local')(options)

Expand Down
7 changes: 4 additions & 3 deletions src/refs/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ const toCamel = require('../lib/object-to-camel')
const configure = require('../lib/configure')

module.exports = configure(api => {
return function refsLocal (options = {}) {
return api.ndjson('refs/local', {
method: 'POST',
return async function * refsLocal (options = {}) {
const res = await api.post('refs/local', {
timeout: options.timeout,
signal: options.signal,
transform: toCamel
})

yield * res.ndjson()
}
})
Loading

0 comments on commit a3a84a7

Please sign in to comment.