diff --git a/md/usage.md b/md/usage.md index ac48be7..a1ed025 100644 --- a/md/usage.md +++ b/md/usage.md @@ -217,6 +217,9 @@ for full access. If you prefer to use the **token**, it will need to have **read zone access for all zones and DNS **editing** permissions for the specific zone being updated. +To use the new Web3 Gateway feature set `IPFS_DEPLOY_CLOUDFLARE__USE_WEB3_GW` to `true`. +The **token** need to have **edit** permission on `Web3 Hostnames` for the specific zone. + - Usage: `-d cloudflare` - Environment variables - Credentials @@ -226,6 +229,7 @@ being updated. - Configuration - `IPFS_DEPLOY_CLOUDFLARE__ZONE=` - `IPFS_DEPLOY_CLOUDFLARE__RECORD=` + - `IPFS_DEPLOY_CLOUDFLARE__USE_WEB3_GW=<0|true>` #### Examples diff --git a/package.json b/package.json index c881419..e2f6947 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "dreamhost": "^1.0.5", "files-from-path": "^0.2.6", "form-data": "^4.0.0", + "got": "^11.8.2", "ipfs-http-client": "^50.1.0", "it-all": "^1.0.6", "lodash.isempty": "^4.4.0", diff --git a/src/cli.js b/src/cli.js index d7a6cdb..6c630a5 100755 --- a/src/cli.js +++ b/src/cli.js @@ -127,7 +127,8 @@ const options = { apiToken: argv.cloudflare && argv.cloudflare.apiToken, apiEmail: argv.cloudflare && argv.cloudflare.apiEmail, zone: argv.cloudflare && argv.cloudflare.zone, - record: argv.cloudflare && argv.cloudflare.record + record: argv.cloudflare && argv.cloudflare.record, + useWeb3Gw: argv.cloudflare && argv.cloudflare.useWeb3Gw }, route53: { accessKeyId: argv.route53 && argv.route53.accessKeyId, diff --git a/src/dnslinkers/cloudflare.js b/src/dnslinkers/cloudflare.js index bc9700e..2d960ce 100644 --- a/src/dnslinkers/cloudflare.js +++ b/src/dnslinkers/cloudflare.js @@ -4,6 +4,78 @@ const dnslink = require('dnslink-cloudflare') const isEmpty = require('lodash.isempty') +const got = require('got') +class CloudflareWeb3GatewayClient { + // @ts-ignore + async getZoneId (api, name) { + let res + + for (let i = 1; (res = await api(`zones?page=${i}`)) && res.body.result_info.total_pages >= i; i++) { + for (const zone of res.body.result) { + if (zone.name === name) { + return zone.id + } + } + } + + throw new Error(`zone ${name} couldn't be found`) + } + + // https://api.cloudflare.com/#web3-hostname-list-web3-hostnames + // @ts-ignore + async getRecord (api, id, name) { + const res = await api(`zones/${id}/web3/hostnames`) + + // @ts-ignore + const record = res.body.result.find(r => r.name === name) + + return record + } + + // @ts-ignore + getClient (apiOpts) { + const opts = { + prefixUrl: 'https://api.cloudflare.com/client/v4', + responseType: 'json' + } + + if (apiOpts.token) { + // @ts-ignore + opts.headers = { + Authorization: `Bearer ${apiOpts.token}` + } + } else { + // @ts-ignore + opts.headers = { + 'X-Auth-Email': apiOpts.email, + 'X-Auth-Key': apiOpts.key + } + } + + // @ts-ignore + return got.extend(opts) + } + + // @ts-ignore + async update (apiOpts, { zone, link, record }) { + const api = this.getClient(apiOpts) + const id = await this.getZoneId(api, zone) + const rec = await this.getRecord(api, id, record) + + if (!rec) { + throw new Error(`web3 gw for ${record} couldn't be found, must be created first`) + } + + await api.patch(`zones/${id}/web3/hostnames/${rec.id}`, { + json: { + dnslink: link + } + }) + + return `dnslink=${link}` + } +} + /** * @typedef {import('./types').DNSRecord} DNSRecord * @typedef {import('./types').CloudflareOptions} CloudflareOptions @@ -13,7 +85,7 @@ class Cloudflare { /** * @param {CloudflareOptions} options */ - constructor ({ apiEmail, apiKey, apiToken, zone, record }) { + constructor ({ apiEmail, apiKey, apiToken, zone, record, useWeb3Gw }) { if ([apiKey, apiEmail, apiToken].every(isEmpty)) { throw new Error('apiEmail and apiKey or apiToken are required for Cloudflare') } @@ -31,7 +103,7 @@ class Cloudflare { } } - this.opts = { record, zone } + this.opts = { record, zone, useWeb3Gw: Boolean(useWeb3Gw) } } /** @@ -44,7 +116,9 @@ class Cloudflare { link: `/ipfs/${cid}` } - const content = await dnslink(this.api, opts) + const content = this.opts.useWeb3Gw + ? await (new CloudflareWeb3GatewayClient()).update(this.api, opts) + : await dnslink(this.api, opts) return { record: opts.record, diff --git a/src/dnslinkers/types.ts b/src/dnslinkers/types.ts index 98ad301..aef04f1 100644 --- a/src/dnslinkers/types.ts +++ b/src/dnslinkers/types.ts @@ -14,6 +14,7 @@ export interface CloudflareOptions { apiToken?: string zone: string record: string + useWeb3Gw?: boolean } export interface DNSimpleOptions {