@nodeart/cloudflare-provisioning 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +4 -0
- package/README.md +0 -0
- package/cloudflare.js +603 -0
- package/index.js +73 -0
- package/package.json +19 -0
- package/template.js +128 -0
package/README.md
ADDED
|
File without changes
|
package/cloudflare.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { request } = require('undici')
|
|
4
|
+
|
|
5
|
+
const CLOUDFLARE_API_URL = 'https://api.cloudflare.com/client/v4/'
|
|
6
|
+
|
|
7
|
+
class CloudFlare {
|
|
8
|
+
constructor (zoneId, options) {
|
|
9
|
+
this.zoneId = zoneId
|
|
10
|
+
|
|
11
|
+
this.authorizationHeaders = null
|
|
12
|
+
if (options.email !== undefined && options.apiKey !== undefined) {
|
|
13
|
+
this.authorizationHeaders = {
|
|
14
|
+
'X-Auth-Email': options.email,
|
|
15
|
+
'X-Auth-Key': options.apiKey
|
|
16
|
+
}
|
|
17
|
+
} else if (options.token !== undefined) {
|
|
18
|
+
this.authorizationHeaders = {
|
|
19
|
+
authorization: 'Bearer ' + options.token
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error('You must provide either an email and api key or a token')
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async setIPv6 (value) {
|
|
27
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/ipv6`
|
|
28
|
+
|
|
29
|
+
const { statusCode, body } = await request(url, {
|
|
30
|
+
method: 'PATCH',
|
|
31
|
+
headers: {
|
|
32
|
+
...this.authorizationHeaders,
|
|
33
|
+
'Content-Type': 'application/json'
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({ value })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const response = await body.json()
|
|
39
|
+
|
|
40
|
+
if (statusCode !== 200) {
|
|
41
|
+
throw new Error(`Could not change IPv6: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return response
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async setEmailObfuscation (value) {
|
|
48
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/email_obfuscation`
|
|
49
|
+
|
|
50
|
+
const { statusCode, body } = await request(url, {
|
|
51
|
+
method: 'PATCH',
|
|
52
|
+
headers: {
|
|
53
|
+
...this.authorizationHeaders,
|
|
54
|
+
'Content-Type': 'application/json'
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({ value })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const response = await body.json()
|
|
60
|
+
|
|
61
|
+
if (statusCode !== 200) {
|
|
62
|
+
throw new Error(`Could not change email obfuscation: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return response
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async setSSL (value) {
|
|
69
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/ssl`
|
|
70
|
+
|
|
71
|
+
const { statusCode, body } = await request(url, {
|
|
72
|
+
method: 'PATCH',
|
|
73
|
+
headers: {
|
|
74
|
+
...this.authorizationHeaders,
|
|
75
|
+
'Content-Type': 'application/json'
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({ value })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const response = await body.json()
|
|
81
|
+
|
|
82
|
+
if (statusCode !== 200) {
|
|
83
|
+
throw new Error(`Could not change SSL: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return response
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async setBrotli (value) {
|
|
90
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/brotli`
|
|
91
|
+
|
|
92
|
+
const { statusCode, body } = await request(url, {
|
|
93
|
+
method: 'PATCH',
|
|
94
|
+
headers: {
|
|
95
|
+
...this.authorizationHeaders,
|
|
96
|
+
'Content-Type': 'application/json'
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({ value })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const response = await body.json()
|
|
102
|
+
|
|
103
|
+
if (statusCode !== 200) {
|
|
104
|
+
throw new Error(`Could not change brotli: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return response
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getDNSRecords () {
|
|
111
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/dns_records`
|
|
112
|
+
|
|
113
|
+
const { statusCode, body } = await request(url, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
headers: {
|
|
116
|
+
...this.authorizationHeaders,
|
|
117
|
+
'Content-Type': 'application/json'
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const response = await body.json()
|
|
122
|
+
|
|
123
|
+
if (statusCode !== 200) {
|
|
124
|
+
throw new Error(`Could not get DNS records: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return response
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async rewriteDNSRecords (dnsRecords) {
|
|
131
|
+
const currentDNSRecords = await this.getDNSRecords()
|
|
132
|
+
|
|
133
|
+
for (const dnsRecord of dnsRecords) {
|
|
134
|
+
const currentDNSRecord = currentDNSRecords.result.find(
|
|
135
|
+
record => record.name === dnsRecord.name
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
if (currentDNSRecord) {
|
|
140
|
+
await this.updateDNSRecord(currentDNSRecord.id, dnsRecord)
|
|
141
|
+
} else {
|
|
142
|
+
await this.createDNSRecord(dnsRecord)
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`Could not update DNS record: ${JSON.stringify(dnsRecord)}, error: ${error}`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async createDNSRecord (dnsRecord) {
|
|
151
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/dns_records`
|
|
152
|
+
|
|
153
|
+
const { statusCode, body } = await request(url, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: {
|
|
156
|
+
...this.authorizationHeaders,
|
|
157
|
+
'Content-Type': 'application/json'
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify(dnsRecord)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const response = await body.json()
|
|
163
|
+
|
|
164
|
+
if (statusCode !== 200) {
|
|
165
|
+
throw new Error(`Could not create DNS record: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return response
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async updateDNSRecord (id, dnsRecord) {
|
|
172
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/dns_records/${id}`
|
|
173
|
+
|
|
174
|
+
const { statusCode, body } = await request(url, {
|
|
175
|
+
method: 'PATCH',
|
|
176
|
+
headers: {
|
|
177
|
+
...this.authorizationHeaders,
|
|
178
|
+
'Content-Type': 'application/json'
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify(dnsRecord)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const response = await body.json()
|
|
184
|
+
|
|
185
|
+
if (statusCode !== 200) {
|
|
186
|
+
throw new Error(`Could not update DNS record: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return response
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async createFirewallRule (firewallRule) {
|
|
193
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/firewall/rules`
|
|
194
|
+
|
|
195
|
+
const { statusCode, body } = await request(url, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: {
|
|
198
|
+
...this.authorizationHeaders,
|
|
199
|
+
'Content-Type': 'application/json'
|
|
200
|
+
},
|
|
201
|
+
body: JSON.stringify([firewallRule])
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const response = await body.json()
|
|
205
|
+
|
|
206
|
+
if (statusCode !== 200) {
|
|
207
|
+
throw new Error(`Could not create a firewall rule: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return response
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async createFirewallRules (firewallRules) {
|
|
214
|
+
const results = await Promise.allSettled(firewallRules.map(firewallRule => this.createFirewallRule(firewallRule)))
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < results.length; i++) {
|
|
217
|
+
const result = results[i]
|
|
218
|
+
const firewallRule = firewallRules[i]
|
|
219
|
+
|
|
220
|
+
if (result.status === 'rejected') {
|
|
221
|
+
console.log(`Could not create firewallRule route ${JSON.stringify(firewallRule)}: ${result.reason}\n`)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async setPolish (value) {
|
|
227
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/polish`
|
|
228
|
+
|
|
229
|
+
const { statusCode, body } = await request(url, {
|
|
230
|
+
method: 'PATCH',
|
|
231
|
+
headers: {
|
|
232
|
+
...this.authorizationHeaders,
|
|
233
|
+
'Content-Type': 'application/json'
|
|
234
|
+
},
|
|
235
|
+
body: JSON.stringify({ value })
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const response = await body.json()
|
|
239
|
+
|
|
240
|
+
if (statusCode !== 200) {
|
|
241
|
+
throw new Error(`Could not set polish: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return response
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async setMinify (value) {
|
|
248
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/minify`
|
|
249
|
+
|
|
250
|
+
const { statusCode, body } = await request(url, {
|
|
251
|
+
method: 'PATCH',
|
|
252
|
+
headers: {
|
|
253
|
+
...this.authorizationHeaders,
|
|
254
|
+
'Content-Type': 'application/json'
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({ value })
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const response = await body.json()
|
|
260
|
+
|
|
261
|
+
if (statusCode !== 200) {
|
|
262
|
+
throw new Error(`Could not set minify: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return response
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async setHTTP2Prioritization (value) {
|
|
269
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/h2_prioritization`
|
|
270
|
+
|
|
271
|
+
const { statusCode, body } = await request(url, {
|
|
272
|
+
method: 'PATCH',
|
|
273
|
+
headers: {
|
|
274
|
+
...this.authorizationHeaders,
|
|
275
|
+
'Content-Type': 'application/json'
|
|
276
|
+
},
|
|
277
|
+
body: JSON.stringify({ value })
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
const response = await body.json()
|
|
281
|
+
|
|
282
|
+
if (statusCode !== 200) {
|
|
283
|
+
throw new Error(`Could not set HTTP2 prioritization: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return response
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async setPrefetchURLs (value) {
|
|
290
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/prefetch_preload`
|
|
291
|
+
|
|
292
|
+
const { statusCode, body } = await request(url, {
|
|
293
|
+
method: 'PATCH',
|
|
294
|
+
headers: {
|
|
295
|
+
...this.authorizationHeaders,
|
|
296
|
+
'Content-Type': 'application/json'
|
|
297
|
+
},
|
|
298
|
+
body: JSON.stringify({ value })
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const response = await body.json()
|
|
302
|
+
|
|
303
|
+
if (statusCode !== 200) {
|
|
304
|
+
throw new Error(`Could not set prefetch URLs: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return response
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async setHttp2 (value) {
|
|
311
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/http2`
|
|
312
|
+
|
|
313
|
+
const { statusCode, body } = await request(url, {
|
|
314
|
+
method: 'PATCH',
|
|
315
|
+
headers: {
|
|
316
|
+
...this.authorizationHeaders,
|
|
317
|
+
'Content-Type': 'application/json'
|
|
318
|
+
},
|
|
319
|
+
body: JSON.stringify({ value })
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const response = await body.json()
|
|
323
|
+
|
|
324
|
+
if (statusCode !== 200) {
|
|
325
|
+
throw new Error(`Could not set HTTP2: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return response
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async setHttp3 (value) {
|
|
332
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/http3`
|
|
333
|
+
|
|
334
|
+
const { statusCode, body } = await request(url, {
|
|
335
|
+
method: 'PATCH',
|
|
336
|
+
headers: {
|
|
337
|
+
...this.authorizationHeaders,
|
|
338
|
+
'Content-Type': 'application/json'
|
|
339
|
+
},
|
|
340
|
+
body: JSON.stringify({ value })
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
const response = await body.json()
|
|
344
|
+
|
|
345
|
+
if (statusCode !== 200) {
|
|
346
|
+
throw new Error(`Could not set HTTP3: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return response
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async set0RTT (value) {
|
|
353
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/settings/0rtt`
|
|
354
|
+
|
|
355
|
+
const { statusCode, body } = await request(url, {
|
|
356
|
+
method: 'PATCH',
|
|
357
|
+
headers: {
|
|
358
|
+
...this.authorizationHeaders,
|
|
359
|
+
'Content-Type': 'application/json'
|
|
360
|
+
},
|
|
361
|
+
body: JSON.stringify({ value })
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const response = await body.json()
|
|
365
|
+
|
|
366
|
+
if (statusCode !== 200) {
|
|
367
|
+
throw new Error(`Could not set 0-RTT: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return response
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async setArgoSmartRouting (value) {
|
|
374
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/argo/smart_routing`
|
|
375
|
+
|
|
376
|
+
const { statusCode, body } = await request(url, {
|
|
377
|
+
method: 'PATCH',
|
|
378
|
+
headers: {
|
|
379
|
+
...this.authorizationHeaders,
|
|
380
|
+
'Content-Type': 'application/json'
|
|
381
|
+
},
|
|
382
|
+
body: JSON.stringify({ value })
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
const response = await body.json()
|
|
386
|
+
|
|
387
|
+
if (statusCode !== 200) {
|
|
388
|
+
throw new Error(`Could not set Argo Smart Routing: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return response
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async getPageRules () {
|
|
395
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/pagerules`
|
|
396
|
+
|
|
397
|
+
const { statusCode, body } = await request(url, {
|
|
398
|
+
method: 'GET',
|
|
399
|
+
headers: {
|
|
400
|
+
...this.authorizationHeaders,
|
|
401
|
+
'Content-Type': 'application/json'
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
const response = await body.json()
|
|
406
|
+
|
|
407
|
+
if (statusCode !== 200) {
|
|
408
|
+
throw new Error(`Could not get page rules: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return response
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async updatePageRule (pageRuleId, pageRule) {
|
|
415
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/pagerules/${pageRuleId}`
|
|
416
|
+
|
|
417
|
+
const { statusCode, body } = await request(url, {
|
|
418
|
+
method: 'PUT',
|
|
419
|
+
headers: {
|
|
420
|
+
...this.authorizationHeaders,
|
|
421
|
+
'Content-Type': 'application/json'
|
|
422
|
+
},
|
|
423
|
+
body: JSON.stringify(pageRule)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
const response = await body.json()
|
|
427
|
+
|
|
428
|
+
if (statusCode !== 200) {
|
|
429
|
+
throw new Error(`Could not update page rule: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return response
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async createPageRule (pageRule) {
|
|
436
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/pagerules`
|
|
437
|
+
|
|
438
|
+
const { statusCode, body } = await request(url, {
|
|
439
|
+
method: 'POST',
|
|
440
|
+
headers: {
|
|
441
|
+
...this.authorizationHeaders,
|
|
442
|
+
'Content-Type': 'application/json'
|
|
443
|
+
},
|
|
444
|
+
body: JSON.stringify(pageRule)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
const response = await body.json()
|
|
448
|
+
|
|
449
|
+
if (statusCode !== 200) {
|
|
450
|
+
throw new Error(`Could not create page rule: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return response
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async rewritePageRules (pageRules) {
|
|
457
|
+
const currentPageRules = await this.getPageRules()
|
|
458
|
+
|
|
459
|
+
for (const pageRule of pageRules) {
|
|
460
|
+
const currentPageRule = currentPageRules.result.find(currentPageRule => {
|
|
461
|
+
for (const currentPageRuleTarget of currentPageRule.targets) {
|
|
462
|
+
const pageRuleTarget = pageRule.targets.find(pageRuleTarget => {
|
|
463
|
+
return currentPageRuleTarget.target === pageRuleTarget.target &&
|
|
464
|
+
currentPageRuleTarget.constraint?.operator === pageRuleTarget.constraint?.operator &&
|
|
465
|
+
currentPageRuleTarget.constraint?.value === pageRuleTarget.constraint?.value
|
|
466
|
+
})
|
|
467
|
+
if (pageRuleTarget === undefined) return false
|
|
468
|
+
}
|
|
469
|
+
return true
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
if (currentPageRule) {
|
|
474
|
+
await this.updatePageRule(currentPageRule.id, pageRule)
|
|
475
|
+
} else {
|
|
476
|
+
await this.createPageRule(pageRule)
|
|
477
|
+
}
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.log(`Could not update or create page rule: ${error.message}\n`)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async getAvailablePageRules () {
|
|
485
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/pagerules/settings`
|
|
486
|
+
|
|
487
|
+
const { statusCode, body } = await request(url, {
|
|
488
|
+
method: 'GET',
|
|
489
|
+
headers: {
|
|
490
|
+
...this.authorizationHeaders,
|
|
491
|
+
'Content-Type': 'application/json'
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
const response = await body.json()
|
|
496
|
+
|
|
497
|
+
if (statusCode !== 200) {
|
|
498
|
+
throw new Error(`Could not get available page rules: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return response
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async createWorkerRoute (workerRoute) {
|
|
505
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/workers/routes`
|
|
506
|
+
|
|
507
|
+
const { statusCode, body } = await request(url, {
|
|
508
|
+
method: 'POST',
|
|
509
|
+
headers: {
|
|
510
|
+
...this.authorizationHeaders,
|
|
511
|
+
'Content-Type': 'application/json'
|
|
512
|
+
},
|
|
513
|
+
body: JSON.stringify(workerRoute)
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
const response = await body.json()
|
|
517
|
+
|
|
518
|
+
if (statusCode !== 200) {
|
|
519
|
+
throw new Error(`Could not create worker routes: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return response
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async createWorkerRoutes (workerRoutes) {
|
|
526
|
+
const results = await Promise.allSettled(workerRoutes.map(workerRoute => this.createWorkerRoute(workerRoute)))
|
|
527
|
+
|
|
528
|
+
for (let i = 0; i < results.length; i++) {
|
|
529
|
+
const result = results[i]
|
|
530
|
+
const workerRoute = workerRoutes[i]
|
|
531
|
+
|
|
532
|
+
if (result.status === 'rejected') {
|
|
533
|
+
console.log(`Could not create worker route ${JSON.stringify(workerRoute)}: ${result.reason}\n`)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async getWorkerRoutes () {
|
|
539
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/workers/routes`
|
|
540
|
+
|
|
541
|
+
const { statusCode, body } = await request(url, {
|
|
542
|
+
method: 'GET',
|
|
543
|
+
headers: {
|
|
544
|
+
...this.authorizationHeaders,
|
|
545
|
+
'Content-Type': 'application/json'
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
const response = await body.json()
|
|
550
|
+
|
|
551
|
+
if (statusCode !== 200) {
|
|
552
|
+
throw new Error(`Could not get worker routes: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return response
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async deleteWorkerRoute (routeId) {
|
|
559
|
+
const url = CLOUDFLARE_API_URL + `zones/${this.zoneId}/workers/routes/${routeId}`
|
|
560
|
+
|
|
561
|
+
const { statusCode, body } = await request(url, {
|
|
562
|
+
method: 'DELETE',
|
|
563
|
+
headers: {
|
|
564
|
+
...this.authorizationHeaders,
|
|
565
|
+
'Content-Type': 'application/json'
|
|
566
|
+
}
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
const response = await body.json()
|
|
570
|
+
|
|
571
|
+
if (statusCode !== 200) {
|
|
572
|
+
throw new Error(`Could not delete worker route: ${statusCode}, error: ${JSON.stringify(response)}`)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return response
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async deleteWorkerRoutes (routeIds) {
|
|
579
|
+
const results = await Promise.allSettled(routeIds.map(routeId => this.deleteWorkerRoute(routeId)))
|
|
580
|
+
|
|
581
|
+
for (let i = 0; i < results.length; i++) {
|
|
582
|
+
const result = results[i]
|
|
583
|
+
const routeId = routeIds[i]
|
|
584
|
+
|
|
585
|
+
if (result.status === 'rejected') {
|
|
586
|
+
console.log(`Could not delete worker route ${routeId}: ${result.reason}\n`)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async rewriteWorkerRoutes (workerRoutes) {
|
|
592
|
+
const currentWorkerRoutes = await this.getWorkerRoutes()
|
|
593
|
+
const currentWorkerRoutesIds = currentWorkerRoutes.result.map(route => route.id)
|
|
594
|
+
|
|
595
|
+
if (currentWorkerRoutesIds.length > 0) {
|
|
596
|
+
await this.deleteWorkerRoutes(currentWorkerRoutesIds)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
await this.createWorkerRoutes(workerRoutes)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
module.exports = CloudFlare
|
package/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const template = require('./template')
|
|
4
|
+
const CloudFlare = require('./cloudflare')
|
|
5
|
+
|
|
6
|
+
const cloudflareSettingsHandlers = {
|
|
7
|
+
ssl: CloudFlare.prototype.setSSL,
|
|
8
|
+
ipV6: CloudFlare.prototype.setIPv6,
|
|
9
|
+
emailObfuscation: CloudFlare.prototype.setEmailObfuscation,
|
|
10
|
+
brotli: CloudFlare.prototype.setBrotli,
|
|
11
|
+
dnsRecords: CloudFlare.prototype.rewriteDNSRecords,
|
|
12
|
+
firewallRules: CloudFlare.prototype.createFirewallRules,
|
|
13
|
+
polish: CloudFlare.prototype.setPolish,
|
|
14
|
+
minify: CloudFlare.prototype.setMinify,
|
|
15
|
+
http2Prioritization: CloudFlare.prototype.setHTTP2Prioritization,
|
|
16
|
+
prefetchURLs: CloudFlare.prototype.setPrefetchURLs,
|
|
17
|
+
http2: CloudFlare.prototype.setHttp2,
|
|
18
|
+
http3: CloudFlare.prototype.setHttp3,
|
|
19
|
+
'0-RTT': CloudFlare.prototype.set0RTT,
|
|
20
|
+
argoSmartRouting: CloudFlare.prototype.setArgoSmartRouting,
|
|
21
|
+
workers: CloudFlare.prototype.rewriteWorkerRoutes,
|
|
22
|
+
pageRules: CloudFlare.prototype.rewritePageRules
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function substituteDomainName (settings, domainName) {
|
|
26
|
+
return JSON.parse(JSON.stringify(settings).replaceAll('$DOMAIN', domainName))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function applyCloudflareSettings (config) {
|
|
30
|
+
if (config.domains === undefined) {
|
|
31
|
+
throw new Error('No domains defined in config')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const accountEmail = process.env.CLOUDFLARE_EMAIL
|
|
35
|
+
const accountKey = process.env.CLOUDFLARE_API_KEY
|
|
36
|
+
|
|
37
|
+
if (config.enabled === false) {
|
|
38
|
+
console.log('Config is disabled and would not be applied:', config.domains)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { domains: sites, settings } = config
|
|
43
|
+
|
|
44
|
+
for (const site of sites) {
|
|
45
|
+
const zoneId = site.zoneId
|
|
46
|
+
if (zoneId === undefined) {
|
|
47
|
+
throw new Error('Cloudflare zone ID is not defined')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const options = site.token === undefined
|
|
51
|
+
? { email: accountEmail, apiKey: accountKey }
|
|
52
|
+
: { token: site.token }
|
|
53
|
+
|
|
54
|
+
const cloudFlare = new CloudFlare(zoneId, options)
|
|
55
|
+
const domainSettings = substituteDomainName(settings, site.domain)
|
|
56
|
+
|
|
57
|
+
for (const [key, value] of Object.entries(domainSettings)) {
|
|
58
|
+
try {
|
|
59
|
+
const settingHandler = cloudflareSettingsHandlers[key]
|
|
60
|
+
|
|
61
|
+
if (settingHandler === undefined) {
|
|
62
|
+
throw new Error(`Unsupported Cloudflare setting: ${key}, for domain: ${site.domain}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await settingHandler.call(cloudFlare, value)
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Failed to set Cloudflare setting ${key} for domain ${site.domain}: ${error.message}\n`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { applyCloudflareSettings, template }
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nodeart/cloudflare-provisioning",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint": "standard",
|
|
8
|
+
"prepare": "husky install"
|
|
9
|
+
},
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"husky": "^8.0.1",
|
|
14
|
+
"standard": "^17.0.0"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"undici": "^5.6.1"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/template.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
ssl: 'full',
|
|
5
|
+
firewallRules: {
|
|
6
|
+
blockSearchForMirrors: [
|
|
7
|
+
{
|
|
8
|
+
description: 'block bots',
|
|
9
|
+
action: 'block',
|
|
10
|
+
filter: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
expression: '(cf.client.bot and not http.request.uri.path contains ".well-known")'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
bypassCMSApi: [
|
|
17
|
+
{
|
|
18
|
+
description: 'bypass cms api with proxy',
|
|
19
|
+
action: 'bypass',
|
|
20
|
+
products: ['uaBlock', 'bic', 'securityLevel'],
|
|
21
|
+
filter: {
|
|
22
|
+
enabled: true,
|
|
23
|
+
expression: '(http.request.uri.path contains "/api/cms/pages" and http.user_agent eq "sitemap-generator-ss") or (http.request.uri.path eq "/api/info/locales" and http.user_agent eq "sitemap-generator-ss")'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
allowHotlinkFromKingtraf: [
|
|
28
|
+
{
|
|
29
|
+
description: 'allow hotlink from kingtraf',
|
|
30
|
+
action: 'bypass',
|
|
31
|
+
products: ['hot'],
|
|
32
|
+
filter: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
expression: '(http.referer contains "kingtraf.com")'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
speedOptimization: {
|
|
40
|
+
polish: 'lossy',
|
|
41
|
+
minify: { css: 'off', html: 'off', js: 'off' },
|
|
42
|
+
brotli: 'off',
|
|
43
|
+
http2Prioritization: 'on',
|
|
44
|
+
prefetchURLs: 'on'
|
|
45
|
+
},
|
|
46
|
+
workers: {
|
|
47
|
+
sitemapCurasao: {
|
|
48
|
+
pattern: '*$DOMAIN/sitemap.xml*',
|
|
49
|
+
script: 'sitemap-curasao'
|
|
50
|
+
},
|
|
51
|
+
sitemapMalta: {
|
|
52
|
+
pattern: '*$DOMAIN/sitemap.xml*',
|
|
53
|
+
script: 'sitemap-malta'
|
|
54
|
+
},
|
|
55
|
+
sitemapAustralia: {
|
|
56
|
+
pattern: '*$DOMAIN/sitemap.xml*',
|
|
57
|
+
script: 'sitemap-xml-au'
|
|
58
|
+
},
|
|
59
|
+
robotsCurasao: {
|
|
60
|
+
pattern: '*$DOMAIN/robots.txt*',
|
|
61
|
+
script: 'kingbillycasinocom-robotstxt'
|
|
62
|
+
},
|
|
63
|
+
robotsMalta: {
|
|
64
|
+
pattern: '*$DOMAIN/robots.txt*',
|
|
65
|
+
script: 'kingbillycom-robotstxt'
|
|
66
|
+
},
|
|
67
|
+
robotsAustralia: {
|
|
68
|
+
pattern: '*$DOMAIN/robots.txt*',
|
|
69
|
+
script: 'robots_block_seo'
|
|
70
|
+
},
|
|
71
|
+
disableApi: {
|
|
72
|
+
pattern: '*$DOMAIN/api/*',
|
|
73
|
+
script: null
|
|
74
|
+
},
|
|
75
|
+
rootDomainCookies: {
|
|
76
|
+
pattern: '*ia.$DOMAIN/C.ashx*',
|
|
77
|
+
script: 'root-domain-cookies'
|
|
78
|
+
},
|
|
79
|
+
lpGeoRedirect: {
|
|
80
|
+
pattern: 'lp.$DOMAIN/*',
|
|
81
|
+
script: 'lp-geo-redirect'
|
|
82
|
+
},
|
|
83
|
+
geoRedirect: {
|
|
84
|
+
pattern: '*$DOMAIN/*',
|
|
85
|
+
script: 'geo-redirect'
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
pageRules: {
|
|
89
|
+
ssApiRequirements: {
|
|
90
|
+
targets: [{ target: 'url', constraint: { operator: 'matches', value: '*$DOMAIN/api/*' } }],
|
|
91
|
+
actions: [{ id: 'disable_security' }, { id: 'security_level', value: 'essentially_off' }, { id: 'browser_check', value: 'off' }],
|
|
92
|
+
status: 'active'
|
|
93
|
+
},
|
|
94
|
+
lpCache: {
|
|
95
|
+
targets: [{ target: 'url', constraint: { operator: 'matches', value: 'lp.$DOMAIN/*' } }],
|
|
96
|
+
actions: [
|
|
97
|
+
{ id: 'ssl', value: 'full' },
|
|
98
|
+
{ id: 'cache_level', value: 'cache_everything' },
|
|
99
|
+
{ id: 'edge_cache_ttl', value: 172800 }
|
|
100
|
+
],
|
|
101
|
+
status: 'active'
|
|
102
|
+
},
|
|
103
|
+
dataExportCache: {
|
|
104
|
+
targets: [{ target: 'url', constraint: { operator: 'matches', value: 'https://www.$DOMAIN/export/*' } }],
|
|
105
|
+
actions: [{ id: 'cache_level', value: 'bypass' }],
|
|
106
|
+
status: 'active'
|
|
107
|
+
},
|
|
108
|
+
rootForward: {
|
|
109
|
+
targets: [{ target: 'url', constraint: { operator: 'matches', value: 'https://$DOMAIN/*' } }],
|
|
110
|
+
actions: [{ id: 'forwarding_url', value: { status_code: 301, url: 'https://www.$DOMAIN/$1' } }],
|
|
111
|
+
status: 'active'
|
|
112
|
+
},
|
|
113
|
+
ia: {
|
|
114
|
+
targets: [{ target: 'url', constraint: { operator: 'matches', value: '*ia.$DOMAIN/*' } }],
|
|
115
|
+
actions: [{ id: 'disable_security' }, { id: 'cache_level', value: 'bypass' }],
|
|
116
|
+
status: 'active'
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
network: {
|
|
120
|
+
http2: 'on',
|
|
121
|
+
http3: 'on',
|
|
122
|
+
'0-RTT': 'on',
|
|
123
|
+
ipV6: 'off'
|
|
124
|
+
},
|
|
125
|
+
traffic: {
|
|
126
|
+
argoSmartRouting: 'on'
|
|
127
|
+
}
|
|
128
|
+
}
|