querysub 0.437.0 → 0.438.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/.eslintrc.js +50 -50
- package/bin/deploy.js +0 -0
- package/bin/function.js +0 -0
- package/bin/server.js +0 -0
- package/costsBenefits.txt +115 -115
- package/deploy.ts +2 -2
- package/package.json +1 -1
- package/spec.txt +1192 -1192
- package/src/-a-archives/archives.ts +202 -202
- package/src/-a-archives/archivesDisk.ts +454 -454
- package/src/-a-auth/certs.ts +540 -540
- package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
- package/src/-b-authorities/dnsAuthority.ts +138 -138
- package/src/-c-identity/IdentityController.ts +258 -258
- package/src/-d-trust/NetworkTrust2.ts +180 -180
- package/src/-e-certs/EdgeCertController.ts +252 -252
- package/src/-e-certs/certAuthority.ts +201 -201
- package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
- package/src/-g-core-values/NodeCapabilities.ts +200 -200
- package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
- package/src/0-path-value-core/PathValueCommitter.ts +468 -468
- package/src/0-path-value-core/PathValueController.ts +0 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
- package/src/2-proxy/TransactionDelayer.ts +94 -94
- package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
- package/src/2-proxy/pathValueProxy.ts +159 -159
- package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
- package/src/3-path-functions/pathFunctionLoader.ts +516 -516
- package/src/3-path-functions/tests/rejectTest.ts +76 -76
- package/src/4-deploy/deployCheck.ts +6 -6
- package/src/4-dom/css.tsx +29 -29
- package/src/4-dom/cssTypes.d.ts +211 -211
- package/src/4-dom/qreact.tsx +2799 -2799
- package/src/4-dom/qreactTest.tsx +410 -410
- package/src/4-querysub/permissions.ts +335 -335
- package/src/4-querysub/querysubPrediction.ts +483 -483
- package/src/5-diagnostics/qreactDebug.tsx +377 -346
- package/src/TestController.ts +34 -34
- package/src/bits.ts +104 -104
- package/src/buffers.ts +69 -69
- package/src/diagnostics/ActionsHistory.ts +57 -57
- package/src/diagnostics/listenOnDebugger.ts +71 -71
- package/src/diagnostics/periodic.ts +111 -111
- package/src/diagnostics/trackResources.ts +91 -91
- package/src/diagnostics/watchdog.ts +120 -120
- package/src/errors.ts +133 -133
- package/src/forceProduction.ts +2 -2
- package/src/fs.ts +80 -80
- package/src/functional/diff.ts +857 -857
- package/src/functional/promiseCache.ts +78 -78
- package/src/functional/random.ts +8 -8
- package/src/functional/stats.ts +60 -60
- package/src/heapDumps.ts +665 -665
- package/src/https.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +87 -87
- package/src/library-components/ButtonSelector.tsx +64 -64
- package/src/library-components/DropdownCustom.tsx +150 -150
- package/src/library-components/DropdownSelector.tsx +31 -31
- package/src/library-components/InlinePopup.tsx +66 -66
- package/src/misc/color.ts +29 -29
- package/src/misc/hash.ts +83 -83
- package/src/misc/ipPong.js +13 -13
- package/src/misc/networking.ts +1 -1
- package/src/misc/random.ts +44 -44
- package/src/misc.ts +196 -196
- package/src/path.ts +255 -255
- package/src/persistentLocalStore.ts +41 -41
- package/src/promise.ts +14 -14
- package/src/storage/fileSystemPointer.ts +71 -71
- package/src/test/heapProcess.ts +35 -35
- package/src/zip.ts +15 -15
- package/tsconfig.json +26 -26
- package/yarnSpec.txt +56 -56
|
@@ -1,201 +1,201 @@
|
|
|
1
|
-
import acme from "acme-client";
|
|
2
|
-
import { generateRSAKeyPair, parseCert, privateKeyToPem } from "../-a-auth/certs";
|
|
3
|
-
import { cache, lazy } from "socket-function/src/caching";
|
|
4
|
-
import { hasDNSWritePermissions, setRecord } from "../-b-authorities/dnsAuthority";
|
|
5
|
-
import { magenta } from "socket-function/src/formatting/logColors";
|
|
6
|
-
import { createKeyStore } from "../persistentLocalStore";
|
|
7
|
-
import { getArchives } from "../-a-archives/archives";
|
|
8
|
-
import { isNoNetwork } from "../config";
|
|
9
|
-
import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
|
|
10
|
-
import { delay } from "socket-function/src/batching";
|
|
11
|
-
import { timeInMinute } from "socket-function/src/misc";
|
|
12
|
-
|
|
13
|
-
const archives = lazy(() => getArchives(`https_certs_3/`));
|
|
14
|
-
// Expire EXPIRATION_THRESHOLD% of the way through the certificate's lifetime
|
|
15
|
-
const EXPIRATION_THRESHOLD = 0.4;
|
|
16
|
-
|
|
17
|
-
export const getHTTPSKeyCert = cache(async (domain: string): Promise<{ key: string; cert: string }> => {
|
|
18
|
-
if (!await hasDNSWritePermissions()) {
|
|
19
|
-
throw new Error(`Cannot generate HTTPS key and cert unless we have DNS write permissions (need credentials in archives, at b2:/keys/cloudflare.json)`);
|
|
20
|
-
}
|
|
21
|
-
if (!domain.endsWith(".")) {
|
|
22
|
-
domain = domain + ".";
|
|
23
|
-
}
|
|
24
|
-
let keyCert: { key: string; cert: string } | undefined;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
let keyCertJSON = await archives().get(domain);
|
|
28
|
-
if (keyCertJSON) {
|
|
29
|
-
keyCert = JSON.parse(keyCertJSON.toString());
|
|
30
|
-
}
|
|
31
|
-
} catch { }
|
|
32
|
-
if (keyCert) {
|
|
33
|
-
// If 40% of the lifetime has passed, renew it (has to be < the threshold
|
|
34
|
-
// in EdgeCertController).
|
|
35
|
-
let certObj = parseCert(keyCert.cert);
|
|
36
|
-
let expirationTime = +new Date(certObj.validity.notAfter);
|
|
37
|
-
let createTime = +new Date(certObj.validity.notBefore);
|
|
38
|
-
let renewDate = createTime + (expirationTime - createTime) * EXPIRATION_THRESHOLD;
|
|
39
|
-
if (renewDate < Date.now()) {
|
|
40
|
-
console.log(magenta(`Renewing domain ${domain} (renew target is ${formatDateTime(renewDate)}).`));
|
|
41
|
-
keyCert = undefined;
|
|
42
|
-
}
|
|
43
|
-
if (keyCert) {
|
|
44
|
-
let timeUntilRenew = renewDate - Date.now();
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
console.log(magenta(`Clearing getHTTPSKeyCert to try to renew ${domain} (renew target is ${formatDateTime(renewDate)}).`));
|
|
47
|
-
getHTTPSKeyCert.clear(domain);
|
|
48
|
-
void getHTTPSKeyCert(domain);
|
|
49
|
-
}, Math.min(Math.floor(timeUntilRenew * 1.1), 2 ** 30));
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
52
|
-
console.log(magenta(`No cert found for domain ${domain}, generating shortly.`));
|
|
53
|
-
}
|
|
54
|
-
if (keyCert) {
|
|
55
|
-
return keyCert;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const accountKey = getAccountKey();
|
|
59
|
-
let altDomains: string[] = [];
|
|
60
|
-
|
|
61
|
-
// // NOTE: Allowing local access is just an optimization, not to avoid having to forward ports
|
|
62
|
-
// // (unless you type 127-0-0-1.domain into the browser... then I guess you don't have to forward ports?)
|
|
63
|
-
// altDomains.push("127-0-0-1." + domain);
|
|
64
|
-
|
|
65
|
-
// NOTE: I forget why we were not allowing wildcard domains. I think it was to prevent
|
|
66
|
-
// any HTTPS domains from impersonating servers. But... servers have two levels, so that isn't
|
|
67
|
-
// an issue. And even if they didn't they store their public key in their domain, so you
|
|
68
|
-
// can't really impersonate them anyways...
|
|
69
|
-
// - AND, we need this for IP type A records, which... we need to pick the server we want
|
|
70
|
-
// to connect to.
|
|
71
|
-
altDomains.push("*." + domain);
|
|
72
|
-
|
|
73
|
-
let isPending = await archives().getInfo(domain + "_pending");
|
|
74
|
-
if (isPending && isPending.writeTime > Date.now() - timeInMinute * 10) {
|
|
75
|
-
let pendingTime = +isPending.toString();
|
|
76
|
-
// Wait 2 minutes
|
|
77
|
-
let waitTime = Math.max(0, pendingTime - Date.now() + timeInMinute * 2);
|
|
78
|
-
if (waitTime) {
|
|
79
|
-
console.log(`getHTTPSKeyCert pending in another process, waiting ${formatTime(waitTime)} for other process to create certificate`);
|
|
80
|
-
await delay(waitTime);
|
|
81
|
-
return await getHTTPSKeyCert(domain);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
let pendingTime = Date.now().toString();
|
|
85
|
-
await archives().set(domain + "_pending", Buffer.from(pendingTime));
|
|
86
|
-
// Wait a bit, and then make sure we are the last to write to pending. This helps a lot with
|
|
87
|
-
// race conditions (although they can still happen).
|
|
88
|
-
{
|
|
89
|
-
await delay(10 * 1000);
|
|
90
|
-
let pendingValue = await archives().get(domain + "_pending");
|
|
91
|
-
if (pendingValue && pendingValue.toString() !== pendingTime) {
|
|
92
|
-
return await getHTTPSKeyCert(domain);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
keyCert = await generateCert({ accountKey, domain, altDomains });
|
|
97
|
-
await archives().del(domain + "_pending");
|
|
98
|
-
} catch (e) {
|
|
99
|
-
if (String(e).includes("authorization must be pending")) {
|
|
100
|
-
console.log(`Authorization appears to be pending, waiting 2 minutes for other process to create certificate`);
|
|
101
|
-
await delay(timeInMinute * 2);
|
|
102
|
-
return await getHTTPSKeyCert(domain);
|
|
103
|
-
}
|
|
104
|
-
throw e;
|
|
105
|
-
}
|
|
106
|
-
await archives().set(domain, Buffer.from(JSON.stringify(keyCert)));
|
|
107
|
-
return keyCert;
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
let accountKeyCache = createKeyStore<{ key: string }>("letsEncryptAccountKey_1");
|
|
111
|
-
let getAccountKey: () => string = lazy(() => {
|
|
112
|
-
let keyCache = accountKeyCache.value;
|
|
113
|
-
if (!keyCache) {
|
|
114
|
-
console.log(`Generating new letsencrypt account key`);
|
|
115
|
-
const keyPair = generateRSAKeyPair();
|
|
116
|
-
keyCache = { key: privateKeyToPem(keyPair.privateKey) };
|
|
117
|
-
accountKeyCache.value = keyCache;
|
|
118
|
-
}
|
|
119
|
-
return keyCache.key;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
async function generateCert(config: {
|
|
123
|
-
accountKey: string;
|
|
124
|
-
domain: string;
|
|
125
|
-
altDomains?: string[];
|
|
126
|
-
}): Promise<{
|
|
127
|
-
domains: string[];
|
|
128
|
-
key: string;
|
|
129
|
-
cert: string;
|
|
130
|
-
}> {
|
|
131
|
-
let { accountKey, domain } = config;
|
|
132
|
-
|
|
133
|
-
console.log(magenta(`Generating new cert for ${domain}`));
|
|
134
|
-
|
|
135
|
-
let domainList = [domain, ...config.altDomains || []];
|
|
136
|
-
// Strip trailing "."
|
|
137
|
-
domainList = domainList.map(x => x.endsWith(".") ? x.slice(0, -1) : x);
|
|
138
|
-
|
|
139
|
-
const [certificateKey, certificateCsr] = await acme.forge.createCsr({
|
|
140
|
-
commonName: domainList[0],
|
|
141
|
-
altNames: domainList.slice(1),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// So... acme-client is fine. Just re-implement the "auto" mode ourselves, to have more control over it.
|
|
145
|
-
const client = new acme.Client({
|
|
146
|
-
directoryUrl: acme.directory.letsencrypt.production,
|
|
147
|
-
accountKey: accountKey,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const accountPayload = {
|
|
151
|
-
termsOfServiceAgreed: true,
|
|
152
|
-
contact: [`mailto:sliftist@gmail.com`],
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
await client.getAccountUrl();
|
|
157
|
-
} catch {
|
|
158
|
-
await client.createAccount(accountPayload);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const orderPayload = {
|
|
162
|
-
identifiers: domainList.map(domain => ({ type: "dns", value: domain })),
|
|
163
|
-
};
|
|
164
|
-
const order = await client.createOrder(orderPayload);
|
|
165
|
-
const authorizations = await client.getAuthorizations(order);
|
|
166
|
-
|
|
167
|
-
for (let auth of authorizations) {
|
|
168
|
-
if (auth.status === "valid") {
|
|
169
|
-
console.log(`Authorization already valid for ${auth.identifier.value}`);
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
console.log(`Starting authorization for ${auth.identifier.value}`);
|
|
173
|
-
|
|
174
|
-
// Only use DNS authorization
|
|
175
|
-
let challenge = auth.challenges.find(x => x.type === "dns-01");
|
|
176
|
-
if (!challenge) {
|
|
177
|
-
throw new Error("No DNS challenge found");
|
|
178
|
-
}
|
|
179
|
-
const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
|
|
180
|
-
|
|
181
|
-
let hostname = auth.identifier.value;
|
|
182
|
-
let challengeRecordName = "_acme-challenge." + hostname + ".";
|
|
183
|
-
await setRecord("TXT", challengeRecordName, keyAuthorization);
|
|
184
|
-
|
|
185
|
-
await client.completeChallenge(challenge);
|
|
186
|
-
console.log(`Challenge completed`);
|
|
187
|
-
|
|
188
|
-
await client.waitForValidStatus(challenge);
|
|
189
|
-
console.log(`Status of order is valid`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const finalized = await client.finalizeOrder(order, certificateCsr);
|
|
193
|
-
console.log(`Order finalized`);
|
|
194
|
-
|
|
195
|
-
let cert = await client.getCertificate(finalized);
|
|
196
|
-
return {
|
|
197
|
-
domains: domainList,
|
|
198
|
-
key: certificateKey.toString(),
|
|
199
|
-
cert: cert,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
1
|
+
import acme from "acme-client";
|
|
2
|
+
import { generateRSAKeyPair, parseCert, privateKeyToPem } from "../-a-auth/certs";
|
|
3
|
+
import { cache, lazy } from "socket-function/src/caching";
|
|
4
|
+
import { hasDNSWritePermissions, setRecord } from "../-b-authorities/dnsAuthority";
|
|
5
|
+
import { magenta } from "socket-function/src/formatting/logColors";
|
|
6
|
+
import { createKeyStore } from "../persistentLocalStore";
|
|
7
|
+
import { getArchives } from "../-a-archives/archives";
|
|
8
|
+
import { isNoNetwork } from "../config";
|
|
9
|
+
import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
|
|
10
|
+
import { delay } from "socket-function/src/batching";
|
|
11
|
+
import { timeInMinute } from "socket-function/src/misc";
|
|
12
|
+
|
|
13
|
+
const archives = lazy(() => getArchives(`https_certs_3/`));
|
|
14
|
+
// Expire EXPIRATION_THRESHOLD% of the way through the certificate's lifetime
|
|
15
|
+
const EXPIRATION_THRESHOLD = 0.4;
|
|
16
|
+
|
|
17
|
+
export const getHTTPSKeyCert = cache(async (domain: string): Promise<{ key: string; cert: string }> => {
|
|
18
|
+
if (!await hasDNSWritePermissions()) {
|
|
19
|
+
throw new Error(`Cannot generate HTTPS key and cert unless we have DNS write permissions (need credentials in archives, at b2:/keys/cloudflare.json)`);
|
|
20
|
+
}
|
|
21
|
+
if (!domain.endsWith(".")) {
|
|
22
|
+
domain = domain + ".";
|
|
23
|
+
}
|
|
24
|
+
let keyCert: { key: string; cert: string } | undefined;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
let keyCertJSON = await archives().get(domain);
|
|
28
|
+
if (keyCertJSON) {
|
|
29
|
+
keyCert = JSON.parse(keyCertJSON.toString());
|
|
30
|
+
}
|
|
31
|
+
} catch { }
|
|
32
|
+
if (keyCert) {
|
|
33
|
+
// If 40% of the lifetime has passed, renew it (has to be < the threshold
|
|
34
|
+
// in EdgeCertController).
|
|
35
|
+
let certObj = parseCert(keyCert.cert);
|
|
36
|
+
let expirationTime = +new Date(certObj.validity.notAfter);
|
|
37
|
+
let createTime = +new Date(certObj.validity.notBefore);
|
|
38
|
+
let renewDate = createTime + (expirationTime - createTime) * EXPIRATION_THRESHOLD;
|
|
39
|
+
if (renewDate < Date.now()) {
|
|
40
|
+
console.log(magenta(`Renewing domain ${domain} (renew target is ${formatDateTime(renewDate)}).`));
|
|
41
|
+
keyCert = undefined;
|
|
42
|
+
}
|
|
43
|
+
if (keyCert) {
|
|
44
|
+
let timeUntilRenew = renewDate - Date.now();
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
console.log(magenta(`Clearing getHTTPSKeyCert to try to renew ${domain} (renew target is ${formatDateTime(renewDate)}).`));
|
|
47
|
+
getHTTPSKeyCert.clear(domain);
|
|
48
|
+
void getHTTPSKeyCert(domain);
|
|
49
|
+
}, Math.min(Math.floor(timeUntilRenew * 1.1), 2 ** 30));
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log(magenta(`No cert found for domain ${domain}, generating shortly.`));
|
|
53
|
+
}
|
|
54
|
+
if (keyCert) {
|
|
55
|
+
return keyCert;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const accountKey = getAccountKey();
|
|
59
|
+
let altDomains: string[] = [];
|
|
60
|
+
|
|
61
|
+
// // NOTE: Allowing local access is just an optimization, not to avoid having to forward ports
|
|
62
|
+
// // (unless you type 127-0-0-1.domain into the browser... then I guess you don't have to forward ports?)
|
|
63
|
+
// altDomains.push("127-0-0-1." + domain);
|
|
64
|
+
|
|
65
|
+
// NOTE: I forget why we were not allowing wildcard domains. I think it was to prevent
|
|
66
|
+
// any HTTPS domains from impersonating servers. But... servers have two levels, so that isn't
|
|
67
|
+
// an issue. And even if they didn't they store their public key in their domain, so you
|
|
68
|
+
// can't really impersonate them anyways...
|
|
69
|
+
// - AND, we need this for IP type A records, which... we need to pick the server we want
|
|
70
|
+
// to connect to.
|
|
71
|
+
altDomains.push("*." + domain);
|
|
72
|
+
|
|
73
|
+
let isPending = await archives().getInfo(domain + "_pending");
|
|
74
|
+
if (isPending && isPending.writeTime > Date.now() - timeInMinute * 10) {
|
|
75
|
+
let pendingTime = +isPending.toString();
|
|
76
|
+
// Wait 2 minutes
|
|
77
|
+
let waitTime = Math.max(0, pendingTime - Date.now() + timeInMinute * 2);
|
|
78
|
+
if (waitTime) {
|
|
79
|
+
console.log(`getHTTPSKeyCert pending in another process, waiting ${formatTime(waitTime)} for other process to create certificate`);
|
|
80
|
+
await delay(waitTime);
|
|
81
|
+
return await getHTTPSKeyCert(domain);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let pendingTime = Date.now().toString();
|
|
85
|
+
await archives().set(domain + "_pending", Buffer.from(pendingTime));
|
|
86
|
+
// Wait a bit, and then make sure we are the last to write to pending. This helps a lot with
|
|
87
|
+
// race conditions (although they can still happen).
|
|
88
|
+
{
|
|
89
|
+
await delay(10 * 1000);
|
|
90
|
+
let pendingValue = await archives().get(domain + "_pending");
|
|
91
|
+
if (pendingValue && pendingValue.toString() !== pendingTime) {
|
|
92
|
+
return await getHTTPSKeyCert(domain);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
keyCert = await generateCert({ accountKey, domain, altDomains });
|
|
97
|
+
await archives().del(domain + "_pending");
|
|
98
|
+
} catch (e) {
|
|
99
|
+
if (String(e).includes("authorization must be pending")) {
|
|
100
|
+
console.log(`Authorization appears to be pending, waiting 2 minutes for other process to create certificate`);
|
|
101
|
+
await delay(timeInMinute * 2);
|
|
102
|
+
return await getHTTPSKeyCert(domain);
|
|
103
|
+
}
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
await archives().set(domain, Buffer.from(JSON.stringify(keyCert)));
|
|
107
|
+
return keyCert;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let accountKeyCache = createKeyStore<{ key: string }>("letsEncryptAccountKey_1");
|
|
111
|
+
let getAccountKey: () => string = lazy(() => {
|
|
112
|
+
let keyCache = accountKeyCache.value;
|
|
113
|
+
if (!keyCache) {
|
|
114
|
+
console.log(`Generating new letsencrypt account key`);
|
|
115
|
+
const keyPair = generateRSAKeyPair();
|
|
116
|
+
keyCache = { key: privateKeyToPem(keyPair.privateKey) };
|
|
117
|
+
accountKeyCache.value = keyCache;
|
|
118
|
+
}
|
|
119
|
+
return keyCache.key;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
async function generateCert(config: {
|
|
123
|
+
accountKey: string;
|
|
124
|
+
domain: string;
|
|
125
|
+
altDomains?: string[];
|
|
126
|
+
}): Promise<{
|
|
127
|
+
domains: string[];
|
|
128
|
+
key: string;
|
|
129
|
+
cert: string;
|
|
130
|
+
}> {
|
|
131
|
+
let { accountKey, domain } = config;
|
|
132
|
+
|
|
133
|
+
console.log(magenta(`Generating new cert for ${domain}`));
|
|
134
|
+
|
|
135
|
+
let domainList = [domain, ...config.altDomains || []];
|
|
136
|
+
// Strip trailing "."
|
|
137
|
+
domainList = domainList.map(x => x.endsWith(".") ? x.slice(0, -1) : x);
|
|
138
|
+
|
|
139
|
+
const [certificateKey, certificateCsr] = await acme.forge.createCsr({
|
|
140
|
+
commonName: domainList[0],
|
|
141
|
+
altNames: domainList.slice(1),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// So... acme-client is fine. Just re-implement the "auto" mode ourselves, to have more control over it.
|
|
145
|
+
const client = new acme.Client({
|
|
146
|
+
directoryUrl: acme.directory.letsencrypt.production,
|
|
147
|
+
accountKey: accountKey,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const accountPayload = {
|
|
151
|
+
termsOfServiceAgreed: true,
|
|
152
|
+
contact: [`mailto:sliftist@gmail.com`],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await client.getAccountUrl();
|
|
157
|
+
} catch {
|
|
158
|
+
await client.createAccount(accountPayload);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const orderPayload = {
|
|
162
|
+
identifiers: domainList.map(domain => ({ type: "dns", value: domain })),
|
|
163
|
+
};
|
|
164
|
+
const order = await client.createOrder(orderPayload);
|
|
165
|
+
const authorizations = await client.getAuthorizations(order);
|
|
166
|
+
|
|
167
|
+
for (let auth of authorizations) {
|
|
168
|
+
if (auth.status === "valid") {
|
|
169
|
+
console.log(`Authorization already valid for ${auth.identifier.value}`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
console.log(`Starting authorization for ${auth.identifier.value}`);
|
|
173
|
+
|
|
174
|
+
// Only use DNS authorization
|
|
175
|
+
let challenge = auth.challenges.find(x => x.type === "dns-01");
|
|
176
|
+
if (!challenge) {
|
|
177
|
+
throw new Error("No DNS challenge found");
|
|
178
|
+
}
|
|
179
|
+
const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
|
|
180
|
+
|
|
181
|
+
let hostname = auth.identifier.value;
|
|
182
|
+
let challengeRecordName = "_acme-challenge." + hostname + ".";
|
|
183
|
+
await setRecord("TXT", challengeRecordName, keyAuthorization);
|
|
184
|
+
|
|
185
|
+
await client.completeChallenge(challenge);
|
|
186
|
+
console.log(`Challenge completed`);
|
|
187
|
+
|
|
188
|
+
await client.waitForValidStatus(challenge);
|
|
189
|
+
console.log(`Status of order is valid`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const finalized = await client.finalizeOrder(order, certificateCsr);
|
|
193
|
+
console.log(`Order finalized`);
|
|
194
|
+
|
|
195
|
+
let cert = await client.getCertificate(finalized);
|
|
196
|
+
return {
|
|
197
|
+
domains: domainList,
|
|
198
|
+
key: certificateKey.toString(),
|
|
199
|
+
cert: cert,
|
|
200
|
+
};
|
|
201
|
+
}
|