roster-server 2.0.6 → 2.1.1
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/README.md +43 -9
- package/demo/wildcard-example.js +31 -0
- package/index.js +254 -92
- package/package.json +3 -2
- package/skills/roster-server/SKILL.md +12 -4
- package/test/roster-server.test.js +483 -0
- package/vendor/acme-dns-01-cli-wrapper.js +161 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const legacyCli = require('acme-dns-01-cli');
|
|
4
|
+
|
|
5
|
+
function toPromise(fn, context) {
|
|
6
|
+
if (typeof fn !== 'function') {
|
|
7
|
+
return async function () {
|
|
8
|
+
return null;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return async function (opts) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
let done = false;
|
|
15
|
+
const finish = (err, result) => {
|
|
16
|
+
if (done) return;
|
|
17
|
+
done = true;
|
|
18
|
+
if (err) reject(err);
|
|
19
|
+
else resolve(result);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Legacy callback style
|
|
24
|
+
if (fn.length >= 2) {
|
|
25
|
+
fn.call(context, opts, finish);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Promise or sync style
|
|
30
|
+
Promise.resolve(fn.call(context, opts)).then(
|
|
31
|
+
(result) => finish(null, result),
|
|
32
|
+
finish
|
|
33
|
+
);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
finish(err);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports.create = function create(config = {}) {
|
|
42
|
+
const challenger = legacyCli.create(config);
|
|
43
|
+
const propagationDelay = Number.isFinite(config.propagationDelay)
|
|
44
|
+
? config.propagationDelay
|
|
45
|
+
: 120000;
|
|
46
|
+
const envAutoContinue = process.env.ROSTER_DNS_AUTO_CONTINUE;
|
|
47
|
+
const parseAutoContinue = (value, fallback) => {
|
|
48
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
49
|
+
const normalized = String(value).trim().toLowerCase();
|
|
50
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
51
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
52
|
+
return fallback;
|
|
53
|
+
};
|
|
54
|
+
const autoContinue = config.autoContinue !== undefined
|
|
55
|
+
? parseAutoContinue(config.autoContinue, false)
|
|
56
|
+
: parseAutoContinue(envAutoContinue, false);
|
|
57
|
+
const dryRunDelay = Number.isFinite(config.dryRunDelay)
|
|
58
|
+
? config.dryRunDelay
|
|
59
|
+
: Number.isFinite(Number(process.env.ROSTER_DNS_DRYRUN_DELAY_MS))
|
|
60
|
+
? Number(process.env.ROSTER_DNS_DRYRUN_DELAY_MS)
|
|
61
|
+
: propagationDelay;
|
|
62
|
+
|
|
63
|
+
function sleep(ms) {
|
|
64
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const presentedByHost = new Map();
|
|
68
|
+
|
|
69
|
+
async function setChallenge(opts) {
|
|
70
|
+
const isInteractive = Boolean(process.stdin && process.stdin.isTTY);
|
|
71
|
+
if (isInteractive && !autoContinue) {
|
|
72
|
+
return toPromise(challenger.set, challenger)(opts);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ch = opts?.challenge || {};
|
|
76
|
+
console.info('');
|
|
77
|
+
console.info("[ACME dns-01 '" + (ch.altname || opts?.altname || 'unknown') + "' CHALLENGE]");
|
|
78
|
+
console.info("You're about to receive the following DNS query:");
|
|
79
|
+
console.info('');
|
|
80
|
+
const dnsHost = String(ch.dnsHost || '');
|
|
81
|
+
const dnsAuth = ch.dnsAuthorization || opts?.dnsAuthorization || null;
|
|
82
|
+
if (dnsHost && dnsAuth) {
|
|
83
|
+
presentedByHost.set(dnsHost, dnsAuth);
|
|
84
|
+
}
|
|
85
|
+
const isDryRunChallenge = dnsHost.includes('_greenlock-dryrun-');
|
|
86
|
+
const effectiveDelay = isDryRunChallenge
|
|
87
|
+
? Math.max(0, dryRunDelay)
|
|
88
|
+
: propagationDelay;
|
|
89
|
+
|
|
90
|
+
console.info(
|
|
91
|
+
'\tTXT\t' +
|
|
92
|
+
(ch.dnsHost || '_acme-challenge.<domain>') +
|
|
93
|
+
'\t' +
|
|
94
|
+
(ch.dnsAuthorization || '<dns-authorization-token>') +
|
|
95
|
+
'\tTTL 60'
|
|
96
|
+
);
|
|
97
|
+
console.info('');
|
|
98
|
+
console.info(
|
|
99
|
+
'Non-interactive mode (or autoContinue) detected. ' +
|
|
100
|
+
'Set the TXT record now. Continuing automatically in ' +
|
|
101
|
+
effectiveDelay +
|
|
102
|
+
'ms...'
|
|
103
|
+
);
|
|
104
|
+
await sleep(effectiveDelay);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function getChallenge(opts) {
|
|
109
|
+
const ch = opts?.challenge || {};
|
|
110
|
+
const dnsHost = String(ch.dnsHost || opts?.dnsHost || '');
|
|
111
|
+
const dnsAuthorization =
|
|
112
|
+
ch.dnsAuthorization ||
|
|
113
|
+
opts?.dnsAuthorization ||
|
|
114
|
+
(dnsHost ? presentedByHost.get(dnsHost) : null);
|
|
115
|
+
|
|
116
|
+
if (!dnsAuthorization) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
...(typeof ch === 'object' ? ch : {}),
|
|
122
|
+
dnsAuthorization
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const wrapped = {
|
|
127
|
+
propagationDelay,
|
|
128
|
+
set: setChallenge,
|
|
129
|
+
remove: toPromise(challenger.remove, challenger),
|
|
130
|
+
get: getChallenge,
|
|
131
|
+
zones: async (opts) => {
|
|
132
|
+
const dnsHost =
|
|
133
|
+
opts?.dnsHost ||
|
|
134
|
+
opts?.challenge?.dnsHost ||
|
|
135
|
+
opts?.challenge?.altname ||
|
|
136
|
+
opts?.altname;
|
|
137
|
+
|
|
138
|
+
if (!dnsHost || typeof dnsHost !== 'string') {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Best-effort root zone extraction for legacy/manual flow.
|
|
143
|
+
const zone = dnsHost
|
|
144
|
+
.replace(/^_acme-challenge\./, '')
|
|
145
|
+
.replace(/^_greenlock-[^.]+\./, '')
|
|
146
|
+
.replace(/\.$/, '');
|
|
147
|
+
|
|
148
|
+
return zone ? [zone] : [];
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (typeof challenger.init === 'function') {
|
|
153
|
+
wrapped.init = toPromise(challenger.init, challenger);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (challenger.options && typeof challenger.options === 'object') {
|
|
157
|
+
wrapped.options = { ...challenger.options };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return wrapped;
|
|
161
|
+
};
|