roster-server 2.1.22 → 2.1.23

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "2.1.22",
3
+ "version": "2.1.23",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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,17 +61,72 @@ 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
128
  const isInteractive = Boolean(process.stdin && process.stdin.isTTY);
73
- if (isInteractive && !autoContinue) {
129
+ if (isInteractive && !autoContinue && !verifyDnsBeforeContinue) {
74
130
  return toPromise(challenger.set, challenger)(opts);
75
131
  }
76
132
 
@@ -92,6 +148,8 @@ module.exports.create = function create(config = {}) {
92
148
  const effectiveDelay = isDryRunChallenge
93
149
  ? Math.max(0, dryRunDelay)
94
150
  : propagationDelay;
151
+ const effectiveTimeoutMs = dnsPollTimeoutMs === null ? effectiveDelay : dnsPollTimeoutMs;
152
+ const expectedToken = resolveExpectedToken(opts, ch);
95
153
 
96
154
  log.info(
97
155
  '\tTXT\t' +
@@ -101,6 +159,28 @@ module.exports.create = function create(config = {}) {
101
159
  '\tTTL 60'
102
160
  );
103
161
  log.info('');
162
+ if (verifyDnsBeforeContinue && dnsHost && expectedToken) {
163
+ log.info(
164
+ 'DNS verification enabled. Continuing automatically when TXT appears at ' +
165
+ dnsHost +
166
+ ' (timeout ' +
167
+ effectiveTimeoutMs +
168
+ 'ms, poll ' +
169
+ dnsPollIntervalMs +
170
+ 'ms).'
171
+ );
172
+ const propagated = await waitForDnsTxtPropagation(dnsHost, expectedToken, effectiveTimeoutMs);
173
+ if (propagated) {
174
+ log.info(`DNS TXT detected for ${dnsHost}; continuing ACME flow.`);
175
+ return null;
176
+ }
177
+ log.warn(
178
+ `DNS TXT not detected for ${dnsHost} within ${effectiveTimeoutMs}ms; ` +
179
+ 'continuing anyway (ACME preflight may still fail).'
180
+ );
181
+ return null;
182
+ }
183
+
104
184
  log.info(
105
185
  'Non-interactive mode (or autoContinue) detected. ' +
106
186
  'Set the TXT record now. Continuing automatically in ' +