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.
@@ -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
+ };