/
gateway.js
110 lines (92 loc) 路 3.05 KB
/
gateway.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/* eslint-env serviceworker, browser */
import pRetry from 'p-retry'
import { CID } from 'multiformats/cid'
import { InvalidUrlError } from './errors.js'
const GOODBITS_BYPASS_TAG = 'https://nftstorage.link/tags/bypass-default-csp'
/**
* Handle gateway requests
*
* @param {Request} request
* @param {import('./env').Env} env
*/
export async function gatewayGet(request, env) {
// Redirect if ipns
if (request.url.includes(env.IPNS_GATEWAY_HOSTNAME)) {
return Response.redirect(
request.url.replace(env.IPNS_GATEWAY_HOSTNAME, 'ipns.dweb.link'),
302
)
}
const response = await env.EDGE_GATEWAY.fetch(request.url, {
headers: request.headers,
cf: {
...(request.cf || {}),
// @ts-ignore custom entry in cf object
onlyIfCachedGateways: JSON.stringify(['https://w3s.link']),
},
})
// Validation layer - CSP bypass
const resourceCid = decodeURIComponent(
response.headers.get('etag') || getCidFromSubdomainUrl(new URL(request.url))
)
const goodbitsTags = await getTagsFromGoodbitsList(
env.GOODBITSLIST,
resourceCid
)
if (goodbitsTags.includes(GOODBITS_BYPASS_TAG)) {
return response
}
return getTransformedResponseWithCspHeaders(response)
}
/**
* Transforms response with custom headers.
* Content-Security-Policy header specified to only allow requests within same origin.
*
* @param {Response} response
*/
function getTransformedResponseWithCspHeaders(response) {
const clonedResponse = new Response(response.body, response)
clonedResponse.headers.set(
'content-security-policy',
"default-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: https://*.w3s.link https://*.nftstorage.link https://*.dweb.link https://ipfs.io/ipfs/ https://*.githubusercontent.com; form-action 'self'; navigate-to 'self'; connect-src 'self' blob: data: https://*.w3s.link https://*.nftstorage.link https://*.dweb.link https://ipfs.io/ipfs/ https://polygon-rpc.com https://rpc.testnet.fantom.network"
)
return clonedResponse
}
/**
* Get a given entry from the goodbits list if CID exists, and return tags
*
* @param {KVNamespace} datastore
* @param {string} cid
*/
async function getTagsFromGoodbitsList(datastore, cid) {
if (!datastore || !cid) {
return []
}
// TODO: Remove once https://github.com/nftstorage/nftstorage.link/issues/51 is fixed
const goodbitsEntry = await pRetry(() => datastore.get(cid), { retries: 5 })
if (goodbitsEntry) {
const { tags } = JSON.parse(goodbitsEntry)
return Array.isArray(tags) ? tags : []
}
return []
}
/**
* Parse subdomain URL and return cid.
*
* @param {URL} url
*/
export function getCidFromSubdomainUrl(url) {
// Replace "ipfs-staging" by "ipfs" if needed
const host = url.hostname.replace('ipfs-staging', 'ipfs')
const splitHost = host.split('.ipfs.')
if (!splitHost.length) {
throw new InvalidUrlError(url.hostname)
}
let cid
try {
cid = CID.parse(splitHost[0])
} catch (/** @type {any} */ err) {
throw new InvalidUrlError(`invalid CID: ${splitHost[0]}: ${err.message}`)
}
return cid.toV1().toString()
}