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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "2.1.22",
3
+ "version": "2.1.24",
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,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 ' +