@certd/acme-client 1.39.10 → 1.39.12
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/package.json +3 -3
- package/src/auto.js +2 -1
- package/src/client.js +2 -2
- package/src/logs/app.log +0 -0
- package/src/util.js +8 -3
- package/src/verify.js +181 -167
- package/types/index.d.ts +4 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Simple and unopinionated ACME client",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "nmorsman",
|
|
6
|
-
"version": "1.39.
|
|
6
|
+
"version": "1.39.12",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "scr/index.js",
|
|
9
9
|
"main": "src/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"types"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@certd/basic": "^1.39.
|
|
21
|
+
"@certd/basic": "^1.39.12",
|
|
22
22
|
"@peculiar/x509": "^1.11.0",
|
|
23
23
|
"asn1js": "^3.0.5",
|
|
24
24
|
"axios": "^1.9.0",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"bugs": {
|
|
71
71
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
|
74
74
|
}
|
package/src/auto.js
CHANGED
package/src/client.js
CHANGED
|
@@ -494,7 +494,7 @@ class AcmeClient {
|
|
|
494
494
|
throw new Error('Unable to verify ACME challenge, URL not found');
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
const {challenges} = createChallengeFn({logger:this.logger});
|
|
497
|
+
const {challenges} = createChallengeFn({logger:this.logger,walkFromAuthoritative: this.opts.walkFromAuthoritative});
|
|
498
498
|
|
|
499
499
|
const verify = challenges
|
|
500
500
|
if (typeof verify[challenge.type] === 'undefined') {
|
|
@@ -577,7 +577,7 @@ class AcmeClient {
|
|
|
577
577
|
|
|
578
578
|
const verifyFn = async (abort) => {
|
|
579
579
|
if (this.opts.signal && this.opts.signal.aborted) {
|
|
580
|
-
abort();
|
|
580
|
+
abort(true);
|
|
581
581
|
throw new CancelError('用户取消');
|
|
582
582
|
}
|
|
583
583
|
|
package/src/logs/app.log
ADDED
|
File without changes
|
package/src/util.js
CHANGED
|
@@ -50,15 +50,18 @@ class Backoff {
|
|
|
50
50
|
|
|
51
51
|
async function retryPromise(fn, attempts, backoff, logger = log) {
|
|
52
52
|
let aborted = false;
|
|
53
|
+
let abortedFromUser = false;
|
|
53
54
|
|
|
54
55
|
try {
|
|
55
|
-
const setAbort = () => { aborted = true; }
|
|
56
|
+
const setAbort = (fromUser = false) => { aborted = true; abortedFromUser = fromUser; }
|
|
56
57
|
const data = await fn(setAbort);
|
|
57
58
|
return data;
|
|
58
59
|
}
|
|
59
60
|
catch (e) {
|
|
60
61
|
if (aborted){
|
|
61
|
-
|
|
62
|
+
if (abortedFromUser){
|
|
63
|
+
logger(`用户取消重试`);
|
|
64
|
+
}
|
|
62
65
|
throw e;
|
|
63
66
|
}
|
|
64
67
|
if ( ((backoff.attempts + 1) >= attempts)) {
|
|
@@ -249,7 +252,7 @@ async function resolveDomainBySoaRecord(recordName, logger = log) {
|
|
|
249
252
|
|
|
250
253
|
async function getAuthoritativeDnsResolver(recordName, logger = log) {
|
|
251
254
|
logger(`获取域名${recordName}的权威NS服务器: `);
|
|
252
|
-
const resolver = new dns.Resolver();
|
|
255
|
+
const resolver = new dns.Resolver({timeout: 2000,tries: 2});
|
|
253
256
|
|
|
254
257
|
try {
|
|
255
258
|
/* Resolve root domain by SOA */
|
|
@@ -349,3 +352,5 @@ export {
|
|
|
349
352
|
resolveDomainBySoaRecord
|
|
350
353
|
};
|
|
351
354
|
|
|
355
|
+
|
|
356
|
+
|
package/src/verify.js
CHANGED
|
@@ -4,19 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
import dnsSdk from "dns"
|
|
6
6
|
import https from 'https'
|
|
7
|
-
import {log as defaultLog} from './logger.js'
|
|
7
|
+
import { log as defaultLog } from './logger.js'
|
|
8
8
|
import axios from './axios.js'
|
|
9
9
|
import * as util from './util.js'
|
|
10
|
-
import {isAlpnCertificateAuthorizationValid} from './crypto/index.js'
|
|
11
|
-
import {utils} from '@certd/basic'
|
|
10
|
+
import { isAlpnCertificateAuthorizationValid } from './crypto/index.js'
|
|
11
|
+
import { utils } from '@certd/basic'
|
|
12
12
|
|
|
13
13
|
const dns = dnsSdk.promises
|
|
14
14
|
|
|
15
|
+
let walkFromAuthoritative = true
|
|
16
|
+
export function setWalkFromAuthoritative(value = true) {
|
|
17
|
+
walkFromAuthoritative = value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createChallengeFn(opts = {}) {
|
|
21
|
+
const logger = opts?.logger || { info: defaultLog, error: defaultLog, warn: defaultLog, debug: defaultLog }
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
const logger = opts?.logger || {info:defaultLog,error:defaultLog,warn:defaultLog,debug:defaultLog}
|
|
18
|
-
|
|
19
|
-
const log = function(...args){
|
|
23
|
+
const log = function (...args) {
|
|
20
24
|
logger.info(...args)
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
@@ -31,202 +35,212 @@ export function createChallengeFn(opts = {}){
|
|
|
31
35
|
* @returns {Promise<boolean>}
|
|
32
36
|
*/
|
|
33
37
|
|
|
34
|
-
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
|
|
38
|
+
async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
async function doQuery(challengeUrl) {
|
|
41
|
+
log(`正在测试请求 ${challengeUrl} `)
|
|
42
|
+
// const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
|
43
|
+
// const challengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
/* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
|
|
46
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!data || (data !== keyAuthorization)) {
|
|
55
|
-
log(`[error] Authorization not found in HTTP response from ${authz.identifier.value}`);
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
58
|
-
return true
|
|
48
|
+
log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
|
|
49
|
+
let data = ""
|
|
50
|
+
try {
|
|
51
|
+
const resp = await axios.get(challengeUrl, { httpsAgent });
|
|
52
|
+
data = (resp.data || '').replace(/\s+$/, '');
|
|
53
|
+
} catch (e) {
|
|
54
|
+
log(`[error] HTTP request error from ${authz.identifier.value}`, e.message);
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
if (!data || (data !== keyAuthorization)) {
|
|
59
|
+
log(`[error] Authorization not found in HTTP response from ${authz.identifier.value}`);
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
return true
|
|
61
63
|
|
|
62
|
-
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
|
63
|
-
let host = authz.identifier.value;
|
|
64
|
-
if(utils.domain.isIpv6(host)){
|
|
65
|
-
host = `[${host}]`;
|
|
66
|
-
}
|
|
67
|
-
const challengeUrl = `http://${host}:${httpPort}${suffix}`;
|
|
68
|
-
|
|
69
|
-
if (!await doQuery(challengeUrl)) {
|
|
70
|
-
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
|
71
|
-
const httpsChallengeUrl = `https://${host}:${httpsPort}${suffix}`;
|
|
72
|
-
const res = await doQuery(httpsChallengeUrl)
|
|
73
|
-
if (!res) {
|
|
74
|
-
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
|
|
75
64
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
65
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
66
|
+
const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
|
|
67
|
+
let host = authz.identifier.value;
|
|
68
|
+
if (utils.domain.isIpv6(host)) {
|
|
69
|
+
host = `[${host}]`;
|
|
70
|
+
}
|
|
71
|
+
const challengeUrl = `http://${host}:${httpPort}${suffix}`;
|
|
72
|
+
|
|
73
|
+
if (!await doQuery(challengeUrl)) {
|
|
74
|
+
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
|
|
75
|
+
const httpsChallengeUrl = `https://${host}:${httpsPort}${suffix}`;
|
|
76
|
+
const res = await doQuery(httpsChallengeUrl)
|
|
77
|
+
if (!res) {
|
|
78
|
+
throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
88
81
|
|
|
89
|
-
let records = [];
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const txtRecords = await resolver.resolveTxt(recordName);
|
|
83
|
+
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Walk DNS until TXT records are found
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
async function walkDnsChallengeRecord(recordName, resolver = dns, deep = 0) {
|
|
92
|
+
|
|
93
|
+
let records = [];
|
|
94
|
+
|
|
95
|
+
const isAuthoritative = resolver === dns
|
|
96
|
+
/* Resolve TXT records */
|
|
97
|
+
try {
|
|
98
|
+
log(`检查域名 ${recordName} 的TXT记录(from ${isAuthoritative ? '本地DNS' : '权威DNS服务器'})`);
|
|
99
|
+
const txtRecords = await resolver.resolveTxt(recordName);
|
|
100
|
+
if (txtRecords && txtRecords.length) {
|
|
101
|
+
log(`找到 ${txtRecords.length} 条 TXT记录( ${recordName})`);
|
|
102
|
+
log(`TXT records: ${JSON.stringify(txtRecords)}`);
|
|
103
|
+
records = records.concat(...txtRecords);
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
log(`解析 TXT 记录出错, ${recordName} :${e.message}`);
|
|
100
107
|
}
|
|
101
|
-
} catch (e) {
|
|
102
|
-
log(`解析 TXT 记录出错, ${recordName} :${e.message}`);
|
|
103
|
-
}
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
/* Resolve CNAME record first */
|
|
110
|
+
try {
|
|
111
|
+
log(`检查是否存在CNAME映射: ${recordName}`);
|
|
112
|
+
const cnameRecords = await resolver.resolveCname(recordName);
|
|
113
|
+
|
|
114
|
+
if (cnameRecords.length) {
|
|
115
|
+
const cnameRecord = cnameRecords[0];
|
|
116
|
+
log(`已找到${recordName}的CNAME记录,将检查: ${cnameRecord}`);
|
|
117
|
+
let res = await walkTxtRecord(cnameRecord, deep + 1);
|
|
118
|
+
if (res && res.length) {
|
|
119
|
+
log(`从CNAME中找到TXT记录: ${JSON.stringify(res)}`);
|
|
120
|
+
records = records.concat(...res);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
log(`没有CNAME映射(${recordName})`);
|
|
117
124
|
}
|
|
118
|
-
}
|
|
119
|
-
log(
|
|
125
|
+
} catch (e) {
|
|
126
|
+
log(`检查CNAME出错(${recordName}) :${e.message}`);
|
|
120
127
|
}
|
|
121
|
-
|
|
122
|
-
log(`检查CNAME出错(${recordName}) :${e.message}`);
|
|
128
|
+
return records
|
|
123
129
|
}
|
|
124
|
-
return records
|
|
125
|
-
}
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
async function walkTxtRecord(recordName, deep = 0) {
|
|
132
|
+
if (deep > 5) {
|
|
133
|
+
log(`walkTxtRecord too deep (#${deep}) , skip walk`)
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const txtRecords = []
|
|
138
|
+
try {
|
|
139
|
+
/* Default DNS resolver first */
|
|
140
|
+
log('从本地DNS服务器获取TXT解析记录');
|
|
141
|
+
const res = await walkDnsChallengeRecord(recordName, dns, deep);
|
|
142
|
+
if (res && res.length > 0) {
|
|
143
|
+
for (const item of res) {
|
|
144
|
+
txtRecords.push(item)
|
|
145
|
+
}
|
|
141
146
|
}
|
|
142
|
-
}
|
|
143
147
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
} catch (e) {
|
|
149
|
+
log(`本地获取TXT解析记录失败:${e.message}`)
|
|
150
|
+
}
|
|
147
151
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
if (walkFromAuthoritative !==false) {
|
|
153
|
+
try {
|
|
154
|
+
/* Authoritative DNS resolver */
|
|
155
|
+
log(`从域名权威服务器获取TXT解析记录`);
|
|
156
|
+
const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName, log);
|
|
157
|
+
const res = await walkDnsChallengeRecord(recordName, authoritativeResolver, deep);
|
|
158
|
+
if (res && res.length > 0) {
|
|
159
|
+
for (const item of res) {
|
|
160
|
+
txtRecords.push(item)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
log(`权威服务器获取TXT解析记录失败:${e.message}`)
|
|
156
165
|
}
|
|
166
|
+
}else{
|
|
167
|
+
log(`跳过从权威服务器获取TXT解析记录`);
|
|
157
168
|
}
|
|
158
|
-
}catch (e) {
|
|
159
|
-
log(`权威服务器获取TXT解析记录失败:${e.message}`)
|
|
160
|
-
}
|
|
161
169
|
|
|
162
|
-
if (txtRecords.length === 0) {
|
|
163
|
-
throw new Error(`没有找到TXT解析记录(${recordName})`);
|
|
164
|
-
}
|
|
165
|
-
return txtRecords;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Verify ACME DNS challenge
|
|
170
|
-
*
|
|
171
|
-
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.4
|
|
172
|
-
*
|
|
173
|
-
* @param {object} authz Identifier authorization
|
|
174
|
-
* @param {object} challenge Authorization challenge
|
|
175
|
-
* @param {string} keyAuthorization Challenge key authorization
|
|
176
|
-
* @param {string} [prefix] DNS prefix
|
|
177
|
-
* @returns {Promise<boolean>}
|
|
178
|
-
*/
|
|
179
170
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
//去重
|
|
185
|
-
recordValues = [...new Set(recordValues)];
|
|
186
|
-
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
|
|
187
|
-
if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
|
|
188
|
-
const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`
|
|
189
|
-
throw new Error(err);
|
|
171
|
+
if (txtRecords.length === 0) {
|
|
172
|
+
throw new Error(`没有找到TXT解析记录(${recordName})`);
|
|
173
|
+
}
|
|
174
|
+
return txtRecords;
|
|
190
175
|
}
|
|
191
176
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Verify ACME DNS challenge
|
|
179
|
+
*
|
|
180
|
+
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.4
|
|
181
|
+
*
|
|
182
|
+
* @param {object} authz Identifier authorization
|
|
183
|
+
* @param {object} challenge Authorization challenge
|
|
184
|
+
* @param {string} keyAuthorization Challenge key authorization
|
|
185
|
+
* @param {string} [prefix] DNS prefix
|
|
186
|
+
* @returns {Promise<boolean>}
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
|
|
190
|
+
const recordName = `${prefix}${authz.identifier.value}`;
|
|
191
|
+
log(`本地校验TXT记录): ${recordName}`);
|
|
192
|
+
let recordValues = await walkTxtRecord(recordName, 0, walkFromAuthoritative);
|
|
193
|
+
//去重
|
|
194
|
+
recordValues = [...new Set(recordValues)];
|
|
195
|
+
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
|
|
196
|
+
if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
|
|
197
|
+
const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`
|
|
198
|
+
throw new Error(err);
|
|
199
|
+
}
|
|
206
200
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
log(`Establishing TLS connection with host: ${host}:${tlsAlpnPort}`);
|
|
201
|
+
log(`关键授权匹配成功(${challenge.type}/${recordName}):${keyAuthorization},校验成功, ACME challenge verified`);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
211
204
|
|
|
212
|
-
|
|
213
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Verify ACME TLS ALPN challenge
|
|
207
|
+
*
|
|
208
|
+
* https://datatracker.ietf.org/doc/html/rfc8737
|
|
209
|
+
*
|
|
210
|
+
* @param {object} authz Identifier authorization
|
|
211
|
+
* @param {object} challenge Authorization challenge
|
|
212
|
+
* @param {string} keyAuthorization Challenge key authorization
|
|
213
|
+
* @returns {Promise<boolean>}
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
|
|
217
|
+
const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
|
|
218
|
+
const host = authz.identifier.value;
|
|
219
|
+
log(`Establishing TLS connection with host: ${host}:${tlsAlpnPort}`);
|
|
220
|
+
|
|
221
|
+
const certificate = await util.retrieveTlsAlpnCertificate(host, tlsAlpnPort);
|
|
222
|
+
log('Certificate received from server successfully, matching key authorization in ALPN');
|
|
223
|
+
|
|
224
|
+
if (!isAlpnCertificateAuthorizationValid(certificate, keyAuthorization)) {
|
|
225
|
+
throw new Error(`Authorization not found in certificate from ${authz.identifier.value}`);
|
|
226
|
+
}
|
|
214
227
|
|
|
215
|
-
|
|
216
|
-
|
|
228
|
+
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
229
|
+
return true;
|
|
217
230
|
}
|
|
218
231
|
|
|
219
|
-
log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
232
|
return {
|
|
224
|
-
challenges:{
|
|
233
|
+
challenges: {
|
|
225
234
|
'http-01': verifyHttpChallenge,
|
|
226
235
|
'dns-01': verifyDnsChallenge,
|
|
227
236
|
'tls-alpn-01': verifyTlsAlpnChallenge,
|
|
228
237
|
},
|
|
229
238
|
walkTxtRecord,
|
|
239
|
+
walkDnsChallengeRecord,
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
// createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handsfree.work")
|
package/types/index.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export interface ClientAutoOptions {
|
|
|
68
68
|
preferredChain?: string;
|
|
69
69
|
signal?: AbortSignal;
|
|
70
70
|
profile?:string;
|
|
71
|
+
waitDnsDiffuseTime?: number;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export class Client {
|
|
@@ -218,4 +219,6 @@ export function getAuthoritativeDnsResolver(record:string): Promise<any>;
|
|
|
218
219
|
|
|
219
220
|
export const CancelError: typeof CancelError;
|
|
220
221
|
|
|
221
|
-
export function resolveDomainBySoaRecord(domain: string): Promise<string>;
|
|
222
|
+
export function resolveDomainBySoaRecord(domain: string): Promise<string>;
|
|
223
|
+
|
|
224
|
+
export function setWalkFromAuthoritative(value = true): void;
|