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

fix: reject requests when cors origin list is empty (#3674) #3676

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ipfs-grpc-client/src/utils/load-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const CONVERSION_OPTS = {
* @returns {object}
*/
module.exports = function loadServices () {
// @ts-ignore protobuf's generated types are incompatible with it's own types
const root = protobuf.Root.fromJSON(protocol)
const output = {}

Expand Down
1 change: 1 addition & 0 deletions packages/ipfs-grpc-server/src/utils/load-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const CONVERSION_OPTS = {
}

module.exports = function loadServices () {
// @ts-ignore protobuf's generated types are incompatible with it's own types
const root = protobuf.Root.fromJSON(protocol)
const output = {}

Expand Down
42 changes: 39 additions & 3 deletions packages/ipfs-http-server/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ async function serverCreator (serverAddrs, createServer, ipfs, cors) {
return servers
}

/**
* @param {string} [str]
* @param {string[]} [allowedOrigins]
*/
function isAllowedOrigin (str, allowedOrigins = []) {
if (!str) {
return false
}

let origin

try {
origin = (new URL(str)).origin
} catch {
return false
}

for (const allowedOrigin of allowedOrigins) {
if (allowedOrigin === '*') {
return true
}

if (allowedOrigin === origin) {
return true
}
}

return false
}

class HttpApi {
constructor (ipfs, options = {}) {
this._ipfs = ipfs
Expand Down Expand Up @@ -150,11 +180,17 @@ class HttpApi {

const headers = request.headers || {}
const origin = headers.origin || ''
const referrer = headers.referrer || ''
const referer = headers.referer || ''
const userAgent = headers['user-agent'] || ''

// If these are set, we leave up to CORS and CSRF checks.
if (origin || referrer) {
// If these are set, check them against the configured list
if (origin || referer) {
if (!isAllowedOrigin(origin || referer, cors.origin)) {
// Hapi will not allow an empty CORS origin list so we have to manually
// reject the request if CORS origins have not been configured
throw Boom.forbidden()
}

return h.continue
}

Expand Down
111 changes: 111 additions & 0 deletions packages/ipfs-http-server/test/cors.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,23 @@ describe('cors', () => {
}
}, { ipfs, cors: { origin: [origin] } })

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

it('allows request when referer is supplied in request', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
referer: origin + '/index.html'
}
}, { ipfs, cors: { origin: [origin] } })

expect(res).to.have.property('statusCode', 200)
})

it('does not allow credentials when omitted in config', async () => {
const origin = 'http://localhost:8080'
const res = await http({
Expand Down Expand Up @@ -149,5 +163,102 @@ describe('cors', () => {

expect(res).to.have.property('statusCode', 404)
})

it('rejects requests when cors origin list is empty and origin is sent', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, {
ipfs,
cors: { origin: [] }
})

expect(res).to.have.property('statusCode', 403)
})

it('rejects requests when cors origin list is empty and referer is sent', async () => {
const referer = 'http://localhost:8080/index.html'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
referer
}
}, {
ipfs,
cors: { origin: [] }
})

expect(res).to.have.property('statusCode', 403)
})

it('rejects requests when cors origin list is empty and referer and origin are sent', async () => {
const referer = 'http://localhost:8080/index.html'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
referer,
origin: 'http://localhost:8080'
}
}, {
ipfs,
cors: { origin: [] }
})

expect(res).to.have.property('statusCode', 403)
})

it('rejects requests when cors origin list is empty and origin is sent as "null" (e.g. data urls and sandboxed iframes)', async () => {
const origin = 'null'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, {
ipfs,
cors: { origin: [] }
})

expect(res).to.have.property('statusCode', 403)
})

it('rejects requests when cors origin list does not contain the correct origin and origin is sent', async () => {
const origin = 'http://localhost:8080'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
origin
}
}, {
ipfs,
cors: { origin: ['http://example.com:8080'] }
})

expect(res).to.have.property('statusCode', 403)
})

it('rejects requests when cors origin list does not contain the correct origin and referer is sent', async () => {
const referer = 'http://localhost:8080/index.html'
const res = await http({
method: 'POST',
url: '/api/v0/id',
headers: {
referer
}
}, {
ipfs,
cors: { origin: ['http://example.com:8080'] }
})

expect(res).to.have.property('statusCode', 403)
})
})
})