Skip to content

Commit c3a5d93

Browse files
committed
refactor: remove code for "blessing" custom webui
This restores the proper way of opening webui in version provided by IPFS node. Context: Before subdomain gateway support was added, we loaded webui from gateway port. Why? API port has a hardcoded list of whitelisted webui versions and it is not possible to load non-whitelisted CID when new webui was released. To enable API access from webui loaded via Gateway port, the Companion extension removed Origin header for requests coming from its background page. This let us avoid the need for manual CORS setup, but was seen in the diff, was pretty complex process. Webui is stable now, so to decrease maintenance burden we move away from that complexity and just load version whitelisted on API port. What if someone wants to run newest webui? They can now load it from webui.ipfs.io.ipns.localhost:8080 (whitelist API access from that specific Origin by appending it to API.HTTPHeaders.Access-Control-Allow-Origin in go-ipfs config) Closes #736
1 parent eb66dfa commit c3a5d93

File tree

8 files changed

+110
-227
lines changed

8 files changed

+110
-227
lines changed

add-on/src/lib/dnslink.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,13 @@ module.exports = function createDnslinkResolver (getState) {
125125
let apiProvider
126126
// TODO: fix DNS resolver for ipfsNodeType='embedded:chromesockets', for now use ipfs.io
127127
if (!state.ipfsNodeType.startsWith('embedded') && state.peerCount !== offlinePeerCount) {
128-
apiProvider = state.apiURLString
128+
// Use gw port so it can be a GET:
129+
// Chromium does not execute onBeforeSendHeaders for synchronous calls
130+
// made from the same extension context as onBeforeSendHeaders
131+
// which means we are unable to fixup Origin on the fly for this
132+
// This will no longer be needed when we switch
133+
// to async lookup via ipfs.dns everywhere
134+
apiProvider = state.gwURLString
129135
} else {
130136
// fallback to resolver at public gateway
131137
apiProvider = 'https://ipfs.io/'

add-on/src/lib/ipfs-companion.js

+3-16
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const log = debug('ipfs-companion:main')
66
log.error = debug('ipfs-companion:main:error')
77

88
const browser = require('webextension-polyfill')
9-
const toMultiaddr = require('uri-to-multiaddr')
109
const pMemoize = require('p-memoize')
1110
const { optionDefaults, storeMissingOptions, migrateOptions, guiURLString } = require('./options')
1211
const { initState, offlinePeerCount } = require('./state')
@@ -107,7 +106,8 @@ module.exports = async function init () {
107106
function registerListeners () {
108107
const onBeforeSendInfoSpec = ['blocking', 'requestHeaders']
109108
if (browser.webRequest.OnBeforeSendHeadersOptions && 'EXTRA_HEADERS' in browser.webRequest.OnBeforeSendHeadersOptions) {
110-
// Chrome 72+ requires 'extraHeaders' for access to Referer header (used in cors whitelisting of webui)
109+
// Chrome 72+ requires 'extraHeaders' for accessing all headers
110+
// Note: we need this for code ensuring ipfs-http-client can talk to API without setting CORS
111111
onBeforeSendInfoSpec.push('extraHeaders')
112112
}
113113
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { urls: ['<all_urls>'] }, onBeforeSendInfoSpec)
@@ -385,18 +385,6 @@ module.exports = async function init () {
385385
log.error(`Unable to linkify DOM at '${details.url}' due to`, error)
386386
}
387387
}
388-
if (details.url.startsWith(state.webuiRootUrl)) {
389-
// Ensure API backend points at one from IPFS Companion
390-
const apiMultiaddr = toMultiaddr(state.apiURLString)
391-
await browser.tabs.executeScript(details.tabId, {
392-
runAt: 'document_start',
393-
code: `if (!localStorage.getItem('ipfsApi')) {
394-
console.log('[ipfs-companion] Setting API to ${apiMultiaddr}');
395-
localStorage.setItem('ipfsApi', '${apiMultiaddr}');
396-
window.location.reload();
397-
}`
398-
})
399-
}
400388
}
401389

402390
// API STATUS UPDATES
@@ -615,6 +603,7 @@ module.exports = async function init () {
615603
case 'ipfsApiUrl':
616604
state.apiURL = new URL(change.newValue)
617605
state.apiURLString = state.apiURL.toString()
606+
state.webuiRootUrl = `${state.apiURLString}webui/`
618607
shouldRestartIpfsClient = true
619608
break
620609
case 'ipfsApiPollMs':
@@ -623,8 +612,6 @@ module.exports = async function init () {
623612
case 'customGatewayUrl':
624613
state.gwURL = new URL(change.newValue)
625614
state.gwURLString = state.gwURL.toString()
626-
// TODO: for now we load webui from API port, should we remove this?
627-
// state.webuiRootUrl = `${state.gwURLString}ipfs/${state.webuiCid}/`
628615
break
629616
case 'publicGatewayUrl':
630617
state.pubGwURL = new URL(change.newValue)

add-on/src/lib/ipfs-request.js

+23-114
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,6 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
4343
const isIgnored = (id) => ignoredRequests.get(id) !== undefined
4444
const errorInFlight = new LRU({ max: 3, maxAge: 1000 })
4545

46-
const acrhHeaders = new LRU(requestCacheCfg) // webui cors fix in Chrome
47-
const originUrls = new LRU(requestCacheCfg) // request.originUrl workaround for Chrome
48-
const originUrl = (request) => {
49-
// Firefox and Chrome provide relevant value in different fields:
50-
// (Firefox) request object includes full URL of origin document, return as-is
51-
if (request.originUrl) return request.originUrl
52-
// (Chrome) is lacking: `request.initiator` is just the origin (protocol+hostname+port)
53-
// To reconstruct originUrl we read full URL from Referer header in onBeforeSendHeaders
54-
// and cache it for short time
55-
// TODO: when request.originUrl is available in Chrome the `originUrls` cache can be removed
56-
const cachedUrl = originUrls.get(request.requestId)
57-
if (cachedUrl) return cachedUrl
58-
if (request.requestHeaders) {
59-
const referer = request.requestHeaders.find(h => h.name === 'Referer')
60-
if (referer) {
61-
originUrls.set(request.requestId, referer.value)
62-
return referer.value
63-
}
64-
}
65-
}
66-
6746
// Returns a canonical hostname representing the site from url
6847
// Main reason for this is unwrapping DNSLink from local subdomain
6948
// <fqdn>.ipns.localhost → <fqdn>
@@ -208,63 +187,34 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
208187

209188
// Special handling of requests made to API
210189
if (sameGateway(request.url, state.apiURL)) {
211-
// Requests made by 'blessed' Web UI
212-
// --------------------------------------------
213-
// Goal: Web UI works without setting CORS at go-ipfs
214-
// (Without this snippet go-ipfs will return HTTP 403 due to additional origin check on the backend)
215-
const origin = originUrl(request)
216-
if (origin && origin.startsWith(state.webuiRootUrl)) {
217-
// console.log('onBeforeSendHeaders', request)
218-
// console.log('onBeforeSendHeaders.origin', origin)
219-
// Swap Origin to pass server-side check
220-
// (go-ipfs returns HTTP 403 on origin mismatch if there are no CORS headers)
221-
const swapOrigin = (at) => {
222-
request.requestHeaders[at].value = request.requestHeaders[at].value.replace(state.gwURL.origin, state.apiURL.origin)
223-
}
224-
let foundAt = request.requestHeaders.findIndex(h => h.name === 'Origin')
225-
if (foundAt > -1) swapOrigin(foundAt)
226-
foundAt = request.requestHeaders.findIndex(h => h.name === 'Referer')
227-
if (foundAt > -1) swapOrigin(foundAt)
228-
229-
// Save access-control-request-headers from preflight
230-
foundAt = request.requestHeaders.findIndex(h => h.name && h.name.toLowerCase() === 'access-control-request-headers')
231-
if (foundAt > -1) {
232-
acrhHeaders.set(request.requestId, request.requestHeaders[foundAt].value)
233-
// console.log('onBeforeSendHeaders FOUND access-control-request-headers', acrhHeaders.get(request.requestId))
234-
}
235-
// console.log('onBeforeSendHeaders fixed headers', request.requestHeaders)
236-
}
237-
190+
const { requestHeaders } = request
238191
// '403 - Forbidden' fix for Chrome and Firefox
239192
// --------------------------------------------
240-
// We remove Origin header from requests made to API URL and WebUI
241-
// by js-ipfs-http-client running in WebExtension context to remove need
242-
// for manual CORS whitelisting via Access-Control-Allow-Origin at go-ipfs
193+
// We update "Origin: *-extension://" HTTP headers in requests made to API
194+
// by js-ipfs-http-client running in the background page of browser
195+
// extension. Without this, some users would need to do manual CORS
196+
// whitelisting by adding "..extension://<UUID>" to
197+
// API.HTTPHeaders.Access-Control-Allow-Origin in go-ipfs config.
198+
// With this, API calls made by browser extension look like ones made
199+
// by webui loaded from the API port.
243200
// More info:
244201
// Firefox: https://github.com/ipfs-shipyard/ipfs-companion/issues/622
245202
// Chromium 71: https://github.com/ipfs-shipyard/ipfs-companion/pull/616
246203
// Chromium 72: https://github.com/ipfs-shipyard/ipfs-companion/issues/630
247-
const isWebExtensionOrigin = (origin) => {
248-
// console.log(`origin=${origin}, webExtensionOrigin=${webExtensionOrigin}`)
249-
// Chromium <= 71 returns opaque Origin as defined in
250-
// https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin
251-
if (origin == null || origin === 'null') {
252-
return true
253-
}
254-
// Firefox Nightly 65 sets moz-extension://{extension-installation-id}
255-
// Chromium Beta 72 sets chrome-extension://{uid}
256-
if (origin &&
204+
205+
// Firefox Nightly 65 sets moz-extension://{extension-installation-id}
206+
// Chromium Beta 72 sets chrome-extension://{uid}
207+
const isWebExtensionOrigin = (origin) =>
208+
origin &&
257209
(origin.startsWith('moz-extension://') ||
258-
origin.startsWith('chrome-extension://')) &&
259-
new URL(origin).origin === webExtensionOrigin) {
260-
return true
261-
}
262-
return false
263-
}
210+
origin.startsWith('chrome-extension://')) &&
211+
new URL(origin).origin === webExtensionOrigin
264212

265-
// Remove Origin header matching webExtensionOrigin
266-
const foundAt = request.requestHeaders.findIndex(h => h.name === 'Origin' && isWebExtensionOrigin(h.value))
267-
if (foundAt > -1) request.requestHeaders.splice(foundAt, 1)
213+
// Replace Origin header matching webExtensionOrigin with API one
214+
const foundAt = requestHeaders.findIndex(h => h.name === 'Origin' && isWebExtensionOrigin(h.value))
215+
if (foundAt > -1) {
216+
requestHeaders[foundAt].value = state.apiURL.origin
217+
}
268218

269219
// Fix "http: invalid Read on closed Body"
270220
// ----------------------------------
@@ -277,7 +227,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
277227
let addExpectHeader = true
278228
const expectHeader = { name: 'Expect', value: '100-continue' }
279229
const warningMsg = 'Executing "Expect: 100-continue" workaround for ipfs.add due to https://github.com/ipfs/go-ipfs/issues/5168'
280-
for (const header of request.requestHeaders) {
230+
for (const header of requestHeaders) {
281231
// Workaround A: https://github.com/ipfs/go-ipfs/issues/5168#issuecomment-401417420
282232
// (works in Firefox, but Chromium does not expose Connection header)
283233
/* (disabled so we use the workaround B in all browsers)
@@ -301,12 +251,10 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
301251
}
302252
if (addExpectHeader) {
303253
log(warningMsg)
304-
request.requestHeaders.push(expectHeader)
254+
requestHeaders.push(expectHeader)
305255
}
306256
}
307-
}
308-
return {
309-
requestHeaders: request.requestHeaders
257+
return { requestHeaders }
310258
}
311259
},
312260

@@ -317,41 +265,6 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
317265
const state = getState()
318266
if (!state.active) return
319267

320-
// Special handling of requests made to API
321-
if (sameGateway(request.url, state.apiURL)) {
322-
// Special handling of requests made by 'blessed' Web UI from local Gateway
323-
// Goal: Web UI works without setting CORS at go-ipfs
324-
// (This includes 'ignored' requests: CORS needs to be fixed even if no redirect is done)
325-
const origin = originUrl(request)
326-
if (origin && origin.startsWith(state.webuiRootUrl) && request.responseHeaders) {
327-
// console.log('onHeadersReceived', request)
328-
const acaOriginHeader = { name: 'Access-Control-Allow-Origin', value: state.gwURL.origin }
329-
const foundAt = findHeaderIndex(acaOriginHeader.name, request.responseHeaders)
330-
if (foundAt > -1) {
331-
request.responseHeaders[foundAt].value = acaOriginHeader.value
332-
} else {
333-
request.responseHeaders.push(acaOriginHeader)
334-
}
335-
336-
// Restore access-control-request-headers from preflight
337-
const acrhValue = acrhHeaders.get(request.requestId)
338-
if (acrhValue) {
339-
const acahHeader = { name: 'Access-Control-Allow-Headers', value: acrhValue }
340-
const foundAt = findHeaderIndex(acahHeader.name, request.responseHeaders)
341-
if (foundAt > -1) {
342-
request.responseHeaders[foundAt].value = acahHeader.value
343-
} else {
344-
request.responseHeaders.push(acahHeader)
345-
}
346-
acrhHeaders.del(request.requestId)
347-
// console.log('onHeadersReceived SET Access-Control-Allow-Headers', header)
348-
}
349-
350-
// console.log('onHeadersReceived fixed headers', request.responseHeaders)
351-
return { responseHeaders: request.responseHeaders }
352-
}
353-
}
354-
355268
// Skip if request is marked as ignored
356269
if (isIgnored(request.requestId)) {
357270
return
@@ -651,10 +564,6 @@ function normalizedUnhandledIpfsProtocol (request, pubGwUrl) {
651564
}
652565
}
653566

654-
function findHeaderIndex (name, headers) {
655-
return headers.findIndex(x => x.name && x.name.toLowerCase() === name.toLowerCase())
656-
}
657-
658567
// RECOVERY OF FAILED REQUESTS
659568
// ===================================================================
660569

add-on/src/lib/precache.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ const drain = require('pull-stream/sinks/drain')
55
const toStream = require('it-to-stream')
66
const tar = require('tar-stream')
77
const CID = require('cids')
8-
const { webuiCid } = require('./state')
98

109
const debug = require('debug')
1110
const log = debug('ipfs-companion:precache')
1211
log.error = debug('ipfs-companion:precache:error')
1312

13+
// Web UI release that should be precached
14+
// WARNING: do not remove this constant, as its used in package.json
15+
const webuiCid = 'Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip' // v2.7.2
16+
1417
const PRECACHE_ARCHIVES = [
1518
{ tarPath: '/dist/precache/webui.tar', cid: webuiCid }
1619
]

add-on/src/lib/state.js

-20
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
const { safeURL } = require('./options')
55
const offlinePeerCount = -1
66

7-
// CID of a 'blessed' Web UI release
8-
// which should work without setting CORS headers
9-
const webuiCid = 'Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip' // v2.7.2
10-
117
function initState (options, overrides) {
128
// we store options and some pregenerated values to avoid async storage
139
// reads and minimize performance impact on overall browsing experience
@@ -29,21 +25,6 @@ function initState (options, overrides) {
2925
state.gwURLString = state.gwURL.toString()
3026
delete state.customGatewayUrl
3127
state.dnslinkPolicy = String(options.dnslinkPolicy) === 'false' ? false : options.dnslinkPolicy
32-
state.webuiCid = webuiCid
33-
34-
// TODO: unify the way webui is opened
35-
// - https://github.com/ipfs-shipyard/ipfs-companion/pull/737
36-
// - https://github.com/ipfs-shipyard/ipfs-companion/pull/738
37-
// Context: previously, we loaded webui from gateway port
38-
// (`${state.gwURLString}ipfs/${state.webuiCid}/`) because API port
39-
// has hardcoded list of whitelisted webui versions.
40-
// To enable API access from webui loaded from Gateway port Companion
41-
// removed Origin header to avoid CORS, now we move away from that
42-
// complexity and for now just load version whitelisted on API port.
43-
// In the future, we want to load webui from $webuiCid.ipfs.localhost
44-
// and whitelist API access from that specific hostname
45-
// by appending it to API.HTTPHeaders.Access-Control-Allow-Origin list
46-
// When that is possible, we can remove Origin manipulation (see PR #737 for PoC)
4728
state.webuiRootUrl = `${state.apiURLString}webui/`
4829

4930
// attach helper functions
@@ -69,4 +50,3 @@ function initState (options, overrides) {
6950

7051
exports.initState = initState
7152
exports.offlinePeerCount = offlinePeerCount
72-
exports.webuiCid = webuiCid

package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"build:bundle-all": "cross-env RELEASE_CHANNEL=${RELEASE_CHANNEL:=dev} run-s bundle:chromium bundle:brave:$RELEASE_CHANNEL bundle:firefox:$RELEASE_CHANNEL",
3030
"build:rename-artifacts": "./scripts/rename-artifacts.js",
3131
"precache:clean": "shx rm -rf add-on/dist/precache",
32-
"precache:webui:cid": "shx grep 'const webuiCid' add-on/src/lib/state.js | shx sed \"s/^const webuiCid = '//\" | shx sed \"s/'.*$//\"",
32+
"precache:webui:cid": "shx grep 'const webuiCid' add-on/src/lib/precache.js | shx sed \"s/^const webuiCid = '//\" | shx sed \"s/'.*$//\"",
3333
"precache:webui": "shx mkdir -p add-on/dist/precache && ipfs-or-gateway -c $(npm run -s precache:webui:cid) -p add-on/dist/precache/webui.tar --archive",
3434
"bundle": "run-s bundle:*",
3535
"bundle:chromium": "run-s precache:webui && shx cat add-on/manifest.common.json add-on/manifest.chromium.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/chromium && run-s build:rename-artifacts",
@@ -140,7 +140,7 @@
140140
"ipfs-postmsg-proxy": "3.1.1",
141141
"ipfsx": "0.17.0",
142142
"is-fqdn": "1.0.1",
143-
"is-ipfs": "https://github.com/ipfs/is-ipfs/tarball/d5717e910d864d738faf39480897342c7cbf065d/is-ipfs.tar.gz",
143+
"is-ipfs": "https://github.com/ipfs/is-ipfs/tarball/18d80d4f3dd4d1f28c700fb156efbf52aeacbd69/is-ipfs.tar.gz",
144144
"is-svg": "4.2.0",
145145
"it-to-stream": "0.1.1",
146146
"lru-cache": "5.1.1",
@@ -158,7 +158,6 @@
158158
"tachyons": "4.11.1",
159159
"tar-stream": "2.1.2",
160160
"timers-browserify-full": "0.0.1",
161-
"uri-to-multiaddr": "3.0.1",
162161
"webextension-polyfill": "0.6.0",
163162
"webrtc-ips": "0.1.4"
164163
},

0 commit comments

Comments
 (0)