roster-server 2.1.22 → 2.1.24
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
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const legacyCli = require('acme-dns-01-cli');
|
|
4
4
|
const log = require('lemonlog')('acme-dns-01');
|
|
5
|
+
const dns = require('node:dns').promises;
|
|
5
6
|
|
|
6
7
|
function toPromise(fn, context) {
|
|
7
8
|
if (typeof fn !== 'function') {
|
|
@@ -60,20 +61,70 @@ module.exports.create = function create(config = {}) {
|
|
|
60
61
|
: Number.isFinite(Number(process.env.ROSTER_DNS_DRYRUN_DELAY_MS))
|
|
61
62
|
? Number(process.env.ROSTER_DNS_DRYRUN_DELAY_MS)
|
|
62
63
|
: propagationDelay;
|
|
64
|
+
const parseBool = (value, fallback) => {
|
|
65
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
66
|
+
const normalized = String(value).trim().toLowerCase();
|
|
67
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
68
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
69
|
+
return fallback;
|
|
70
|
+
};
|
|
71
|
+
const verifyDnsBeforeContinue = config.verifyDnsBeforeContinue !== undefined
|
|
72
|
+
? parseBool(config.verifyDnsBeforeContinue, true)
|
|
73
|
+
: parseBool(process.env.ROSTER_DNS_VERIFY_BEFORE_CONTINUE, true);
|
|
74
|
+
const dnsPollIntervalMs = Number.isFinite(config.dnsPollIntervalMs)
|
|
75
|
+
? config.dnsPollIntervalMs
|
|
76
|
+
: Number.isFinite(Number(process.env.ROSTER_DNS_POLL_INTERVAL_MS))
|
|
77
|
+
? Number(process.env.ROSTER_DNS_POLL_INTERVAL_MS)
|
|
78
|
+
: 5000;
|
|
79
|
+
const dnsPollTimeoutMs = Number.isFinite(config.dnsPollTimeoutMs)
|
|
80
|
+
? config.dnsPollTimeoutMs
|
|
81
|
+
: Number.isFinite(Number(process.env.ROSTER_DNS_POLL_TIMEOUT_MS))
|
|
82
|
+
? Number(process.env.ROSTER_DNS_POLL_TIMEOUT_MS)
|
|
83
|
+
: null;
|
|
63
84
|
|
|
64
85
|
function sleep(ms) {
|
|
65
86
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
66
87
|
}
|
|
67
88
|
|
|
89
|
+
function normalizeTxtChunk(value) {
|
|
90
|
+
return String(value || '').replace(/^"+|"+$/g, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveExpectedToken(opts, ch) {
|
|
94
|
+
const candidate = ch?.dnsAuthorization || opts?.dnsAuthorization;
|
|
95
|
+
return candidate ? String(candidate).trim() : '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function hasDnsTxtToken(dnsHost, expectedToken) {
|
|
99
|
+
if (!dnsHost || !expectedToken) return false;
|
|
100
|
+
try {
|
|
101
|
+
const records = await dns.resolveTxt(dnsHost);
|
|
102
|
+
for (const recordParts of records || []) {
|
|
103
|
+
const joined = (recordParts || []).map(normalizeTxtChunk).join('').trim();
|
|
104
|
+
if (joined === expectedToken) return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
} catch (_) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function waitForDnsTxtPropagation(dnsHost, expectedToken, timeoutMs) {
|
|
113
|
+
const started = Date.now();
|
|
114
|
+
const maxWait = Math.max(0, Number.isFinite(timeoutMs) ? timeoutMs : 0);
|
|
115
|
+
if (maxWait === 0) return hasDnsTxtToken(dnsHost, expectedToken);
|
|
116
|
+
|
|
117
|
+
while ((Date.now() - started) <= maxWait) {
|
|
118
|
+
if (await hasDnsTxtToken(dnsHost, expectedToken)) return true;
|
|
119
|
+
await sleep(Math.max(1000, dnsPollIntervalMs));
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
const presentedByHost = new Map();
|
|
69
125
|
const presentedByAltname = new Map();
|
|
70
126
|
|
|
71
127
|
async function setChallenge(opts) {
|
|
72
|
-
const isInteractive = Boolean(process.stdin && process.stdin.isTTY);
|
|
73
|
-
if (isInteractive && !autoContinue) {
|
|
74
|
-
return toPromise(challenger.set, challenger)(opts);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
128
|
const ch = opts?.challenge || {};
|
|
78
129
|
log.info('');
|
|
79
130
|
log.info("[ACME dns-01 '" + (ch.altname || opts?.altname || 'unknown') + "' CHALLENGE]");
|
|
@@ -92,6 +143,8 @@ module.exports.create = function create(config = {}) {
|
|
|
92
143
|
const effectiveDelay = isDryRunChallenge
|
|
93
144
|
? Math.max(0, dryRunDelay)
|
|
94
145
|
: propagationDelay;
|
|
146
|
+
const effectiveTimeoutMs = dnsPollTimeoutMs === null ? effectiveDelay : dnsPollTimeoutMs;
|
|
147
|
+
const expectedToken = resolveExpectedToken(opts, ch);
|
|
95
148
|
|
|
96
149
|
log.info(
|
|
97
150
|
'\tTXT\t' +
|
|
@@ -101,6 +154,28 @@ module.exports.create = function create(config = {}) {
|
|
|
101
154
|
'\tTTL 60'
|
|
102
155
|
);
|
|
103
156
|
log.info('');
|
|
157
|
+
if (verifyDnsBeforeContinue && dnsHost && expectedToken) {
|
|
158
|
+
log.info(
|
|
159
|
+
'DNS verification enabled. Continuing automatically when TXT appears at ' +
|
|
160
|
+
dnsHost +
|
|
161
|
+
' (timeout ' +
|
|
162
|
+
effectiveTimeoutMs +
|
|
163
|
+
'ms, poll ' +
|
|
164
|
+
dnsPollIntervalMs +
|
|
165
|
+
'ms).'
|
|
166
|
+
);
|
|
167
|
+
const propagated = await waitForDnsTxtPropagation(dnsHost, expectedToken, effectiveTimeoutMs);
|
|
168
|
+
if (propagated) {
|
|
169
|
+
log.info(`DNS TXT detected for ${dnsHost}; continuing ACME flow.`);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
log.warn(
|
|
173
|
+
`DNS TXT not detected for ${dnsHost} within ${effectiveTimeoutMs}ms; ` +
|
|
174
|
+
'continuing anyway (ACME preflight may still fail).'
|
|
175
|
+
);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
104
179
|
log.info(
|
|
105
180
|
'Non-interactive mode (or autoContinue) detected. ' +
|
|
106
181
|
'Set the TXT record now. Continuing automatically in ' +
|