roster-server 2.2.4 → 2.2.6
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
|
@@ -28,16 +28,16 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
28
28
|
const calls = [];
|
|
29
29
|
global.fetch = async (url, options = {}) => {
|
|
30
30
|
calls.push({ url, options });
|
|
31
|
-
if (url.endsWith('/domains
|
|
32
|
-
return { ok: true, status: 200, json: async () => ({}) };
|
|
31
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
32
|
+
return { ok: true, status: 200, json: async () => ({ data: [{ id: 42, domain: 'example.com' }] }) };
|
|
33
33
|
}
|
|
34
|
-
if (url.endsWith('/domains/
|
|
34
|
+
if (url.endsWith('/domains/42/records?page_size=500')) {
|
|
35
35
|
return { ok: true, status: 200, json: async () => ({ data: [] }) };
|
|
36
36
|
}
|
|
37
|
-
if (url.endsWith('/domains/
|
|
37
|
+
if (url.endsWith('/domains/42/records') && options.method === 'POST') {
|
|
38
38
|
return { ok: true, status: 200, json: async () => ({ id: 321 }) };
|
|
39
39
|
}
|
|
40
|
-
if (url.endsWith('/domains/
|
|
40
|
+
if (url.endsWith('/domains/42/records/321') && options.method === 'DELETE') {
|
|
41
41
|
return { ok: true, status: 204, json: async () => ({}) };
|
|
42
42
|
}
|
|
43
43
|
return { ok: false, status: 404, statusText: 'not mocked', text: async () => 'not mocked' };
|
|
@@ -55,8 +55,8 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
55
55
|
await challenger.set(opts);
|
|
56
56
|
await challenger.remove(opts);
|
|
57
57
|
|
|
58
|
-
assert.ok(calls.some((c) => c.url.endsWith('/domains/
|
|
59
|
-
assert.ok(calls.some((c) => c.url.endsWith('/domains/
|
|
58
|
+
assert.ok(calls.some((c) => c.url.endsWith('/domains/42/records') && c.options.method === 'POST'));
|
|
59
|
+
assert.ok(calls.some((c) => c.url.endsWith('/domains/42/records/321') && c.options.method === 'DELETE'));
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('uses LINODE_API_KEY from environment', async () => {
|
|
@@ -64,10 +64,10 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
64
64
|
const calls = [];
|
|
65
65
|
global.fetch = async (url, options = {}) => {
|
|
66
66
|
calls.push({ url, options });
|
|
67
|
-
if (url.endsWith('/domains
|
|
68
|
-
return { ok: true, status: 200, json: async () => ({}) };
|
|
67
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
68
|
+
return { ok: true, status: 200, json: async () => ({ data: [{ id: 42, domain: 'example.com' }] }) };
|
|
69
69
|
}
|
|
70
|
-
if (url.endsWith('/domains/
|
|
70
|
+
if (url.endsWith('/domains/42/records?page_size=500')) {
|
|
71
71
|
return { ok: true, status: 200, json: async () => ({ data: [{ id: 111, type: 'TXT', name: '_acme-challenge', target: 'test-token' }] }) };
|
|
72
72
|
}
|
|
73
73
|
return { ok: true, status: 204, json: async () => ({}) };
|
|
@@ -90,19 +90,16 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
90
90
|
const calls = [];
|
|
91
91
|
global.fetch = async (url, options = {}) => {
|
|
92
92
|
calls.push({ url, options });
|
|
93
|
-
if (url.endsWith('/domains
|
|
94
|
-
return { ok:
|
|
93
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
94
|
+
return { ok: true, status: 200, json: async () => ({ data: [{ id: 99, domain: 'example.com' }] }) };
|
|
95
95
|
}
|
|
96
|
-
if (url.endsWith('/domains/
|
|
97
|
-
return { ok: true, status: 200, json: async () => ({}) };
|
|
98
|
-
}
|
|
99
|
-
if (url.endsWith('/domains/example.com/records?page_size=500')) {
|
|
96
|
+
if (url.endsWith('/domains/99/records?page_size=500')) {
|
|
100
97
|
return { ok: true, status: 200, json: async () => ({ data: [] }) };
|
|
101
98
|
}
|
|
102
|
-
if (url.endsWith('/domains/
|
|
99
|
+
if (url.endsWith('/domains/99/records') && options.method === 'POST') {
|
|
103
100
|
return { ok: true, status: 200, json: async () => ({ id: 654 }) };
|
|
104
101
|
}
|
|
105
|
-
if (url.endsWith('/domains/
|
|
102
|
+
if (url.endsWith('/domains/99/records/654') && options.method === 'DELETE') {
|
|
106
103
|
return { ok: true, status: 204, json: async () => ({}) };
|
|
107
104
|
}
|
|
108
105
|
return { ok: false, status: 404, statusText: 'not mocked', text: async () => 'not mocked' };
|
|
@@ -127,12 +124,57 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
127
124
|
await challenger.set(opts);
|
|
128
125
|
await challenger.remove(opts);
|
|
129
126
|
|
|
130
|
-
const postCall = calls.find((c) => c.url.endsWith('/domains/
|
|
127
|
+
const postCall = calls.find((c) => c.url.endsWith('/domains/99/records') && c.options.method === 'POST');
|
|
131
128
|
assert.ok(postCall, 'expected TXT create call on parent zone');
|
|
132
129
|
const payload = JSON.parse(postCall.options.body);
|
|
133
130
|
assert.strictEqual(payload.name, '_acme-challenge.sub');
|
|
134
131
|
});
|
|
135
132
|
|
|
133
|
+
it('prefers apex zone over www zone for www challenges', async () => {
|
|
134
|
+
const calls = [];
|
|
135
|
+
global.fetch = async (url, options = {}) => {
|
|
136
|
+
calls.push({ url, options });
|
|
137
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
status: 200,
|
|
141
|
+
json: async () => ({
|
|
142
|
+
data: [
|
|
143
|
+
{ id: 777, domain: 'www.tagnu.com' },
|
|
144
|
+
{ id: 888, domain: 'tagnu.com' }
|
|
145
|
+
]
|
|
146
|
+
})
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (url.endsWith('/domains/888/records?page_size=500')) {
|
|
150
|
+
return { ok: true, status: 200, json: async () => ({ data: [] }) };
|
|
151
|
+
}
|
|
152
|
+
if (url.endsWith('/domains/888/records') && options.method === 'POST') {
|
|
153
|
+
return { ok: true, status: 200, json: async () => ({ id: 333 }) };
|
|
154
|
+
}
|
|
155
|
+
return { ok: true, status: 204, json: async () => ({}) };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const challenger = wrapper.create({
|
|
159
|
+
provider: 'linode',
|
|
160
|
+
linodeApiKey: 'fake-token',
|
|
161
|
+
verifyDnsBeforeContinue: false,
|
|
162
|
+
propagationDelay: 0,
|
|
163
|
+
dryRunDelay: 0
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await challenger.set({
|
|
167
|
+
challenge: {
|
|
168
|
+
altname: 'www.tagnu.com',
|
|
169
|
+
dnsHost: '_greenlock-dryrun-abc.www.tagnu.com',
|
|
170
|
+
dnsAuthorization: 'www-token'
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
assert.ok(calls.some((c) => c.url.endsWith('/domains/888/records') && c.options.method === 'POST'));
|
|
175
|
+
assert.ok(!calls.some((c) => c.url.endsWith('/domains/777/records') && c.options.method === 'POST'));
|
|
176
|
+
});
|
|
177
|
+
|
|
136
178
|
it('falls back to manual when provider mode has no API key (default)', async () => {
|
|
137
179
|
const prevLinode = process.env.LINODE_API_KEY;
|
|
138
180
|
delete process.env.LINODE_API_KEY;
|
|
@@ -179,13 +221,13 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
179
221
|
const calls = [];
|
|
180
222
|
global.fetch = async (url, options = {}) => {
|
|
181
223
|
calls.push({ url, options });
|
|
182
|
-
if (url.endsWith('/domains
|
|
183
|
-
return { ok: true, status: 200, json: async () => ({}) };
|
|
224
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
225
|
+
return { ok: true, status: 200, json: async () => ({ data: [{ id: 42, domain: 'example.com' }] }) };
|
|
184
226
|
}
|
|
185
|
-
if (url.endsWith('/domains/
|
|
227
|
+
if (url.endsWith('/domains/42/records?page_size=500')) {
|
|
186
228
|
return { ok: true, status: 200, json: async () => ({ data: [] }) };
|
|
187
229
|
}
|
|
188
|
-
if (url.endsWith('/domains/
|
|
230
|
+
if (url.endsWith('/domains/42/records') && options.method === 'POST') {
|
|
189
231
|
return { ok: true, status: 200, json: async () => ({ id: 222 }) };
|
|
190
232
|
}
|
|
191
233
|
return { ok: true, status: 204, json: async () => ({}) };
|
|
@@ -201,7 +243,7 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
201
243
|
});
|
|
202
244
|
|
|
203
245
|
await challenger.set(buildChallengeOpts());
|
|
204
|
-
const postCall = calls.find((c) => c.url.endsWith('/domains/
|
|
246
|
+
const postCall = calls.find((c) => c.url.endsWith('/domains/42/records') && c.options.method === 'POST');
|
|
205
247
|
assert.ok(postCall, 'expected TXT create call');
|
|
206
248
|
const payload = JSON.parse(postCall.options.body);
|
|
207
249
|
assert.strictEqual(payload.ttl_sec, 300);
|
|
@@ -212,10 +254,10 @@ describe('acme-dns-01-cli-wrapper automatic Linode DNS', () => {
|
|
|
212
254
|
const calls = [];
|
|
213
255
|
global.fetch = async (url, options = {}) => {
|
|
214
256
|
calls.push({ url, options });
|
|
215
|
-
if (url.endsWith('/domains
|
|
216
|
-
return { ok: true, status: 200, json: async () => ({}) };
|
|
257
|
+
if (url.endsWith('/domains?page_size=500')) {
|
|
258
|
+
return { ok: true, status: 200, json: async () => ({ data: [{ id: 42, domain: 'example.com' }] }) };
|
|
217
259
|
}
|
|
218
|
-
if (url.endsWith('/domains/
|
|
260
|
+
if (url.endsWith('/domains/42/records?page_size=500')) {
|
|
219
261
|
return { ok: true, status: 200, json: async () => ({ data: [{ id: 111, type: 'TXT', name: '_acme-challenge', target: 'test-token' }] }) };
|
|
220
262
|
}
|
|
221
263
|
return { ok: true, status: 204, json: async () => ({}) };
|
|
@@ -199,6 +199,7 @@ module.exports.create = function create(config = {}) {
|
|
|
199
199
|
const presentedByHost = new Map();
|
|
200
200
|
const presentedByAltname = new Map();
|
|
201
201
|
const linodeTxtRecordsByHost = new Map();
|
|
202
|
+
const linodeZoneCache = new Map();
|
|
202
203
|
|
|
203
204
|
function buildZoneCandidates({ dnsHost, altname }) {
|
|
204
205
|
const candidates = new Set();
|
|
@@ -211,6 +212,14 @@ module.exports.create = function create(config = {}) {
|
|
|
211
212
|
}
|
|
212
213
|
};
|
|
213
214
|
|
|
215
|
+
// Prefer apex zone when validating www.<domain> challenges so records are
|
|
216
|
+
// created in the commonly delegated parent zone (e.g. tagnu.com).
|
|
217
|
+
if (altname) {
|
|
218
|
+
const normalizedAltname = String(altname).replace(/^\*\./, '').replace(/\.$/, '').toLowerCase();
|
|
219
|
+
if (normalizedAltname.startsWith('www.')) {
|
|
220
|
+
add(normalizedAltname.slice(4));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
214
223
|
if (dnsHost) {
|
|
215
224
|
const normalizedDnsHost = String(dnsHost).replace(/^_acme-challenge\./, '').replace(/^_greenlock-[^.]+\./, '');
|
|
216
225
|
add(normalizedDnsHost);
|
|
@@ -260,22 +269,40 @@ module.exports.create = function create(config = {}) {
|
|
|
260
269
|
return response.json();
|
|
261
270
|
}
|
|
262
271
|
|
|
272
|
+
async function resolveLinodeZone(zone) {
|
|
273
|
+
const normalizedZone = String(zone || '').trim().toLowerCase();
|
|
274
|
+
if (!normalizedZone) return null;
|
|
275
|
+
if (linodeZoneCache.has(normalizedZone)) return linodeZoneCache.get(normalizedZone);
|
|
276
|
+
|
|
277
|
+
const domainsResult = await linodeRequest('/domains?page_size=500', 'GET');
|
|
278
|
+
const domains = Array.isArray(domainsResult?.data) ? domainsResult.data : [];
|
|
279
|
+
const matched = domains.find((entry) => String(entry?.domain || '').trim().toLowerCase() === normalizedZone);
|
|
280
|
+
if (!matched?.id) return null;
|
|
281
|
+
|
|
282
|
+
const zoneInfo = { id: matched.id, domain: String(matched.domain || normalizedZone) };
|
|
283
|
+
linodeZoneCache.set(normalizedZone, zoneInfo);
|
|
284
|
+
return zoneInfo;
|
|
285
|
+
}
|
|
286
|
+
|
|
263
287
|
async function linodeUpsertTxtRecord(dnsHost, dnsAuthorization, altname) {
|
|
264
288
|
const zoneCandidates = buildZoneCandidates({ dnsHost, altname });
|
|
265
289
|
let lastError = null;
|
|
266
290
|
|
|
267
291
|
for (const zone of zoneCandidates) {
|
|
292
|
+
let zoneInfo = null;
|
|
268
293
|
try {
|
|
269
|
-
await
|
|
294
|
+
zoneInfo = await resolveLinodeZone(zone);
|
|
270
295
|
} catch (error) {
|
|
271
296
|
lastError = error;
|
|
272
297
|
continue;
|
|
273
298
|
}
|
|
299
|
+
if (!zoneInfo?.id) continue;
|
|
274
300
|
|
|
275
301
|
const recordName = linodeRecordNameForHost(dnsHost, zone);
|
|
276
302
|
if (!recordName && dnsHost !== zone) continue;
|
|
277
303
|
|
|
278
|
-
const
|
|
304
|
+
const zoneId = zoneInfo.id;
|
|
305
|
+
const recordsResult = await linodeRequest(`/domains/${zoneId}/records?page_size=500`, 'GET');
|
|
279
306
|
const existing = Array.isArray(recordsResult?.data) ? recordsResult.data : [];
|
|
280
307
|
const sameRecord = existing.find((record) =>
|
|
281
308
|
record?.type === 'TXT'
|
|
@@ -284,11 +311,11 @@ module.exports.create = function create(config = {}) {
|
|
|
284
311
|
);
|
|
285
312
|
|
|
286
313
|
if (sameRecord && sameRecord.id) {
|
|
287
|
-
linodeTxtRecordsByHost.set(dnsHost, { zone, id: sameRecord.id });
|
|
288
|
-
return { zone, id: sameRecord.id, reused: true };
|
|
314
|
+
linodeTxtRecordsByHost.set(dnsHost, { zone, zoneId, id: sameRecord.id });
|
|
315
|
+
return { zone, zoneId, id: sameRecord.id, reused: true };
|
|
289
316
|
}
|
|
290
317
|
|
|
291
|
-
const created = await linodeRequest(`/domains/${
|
|
318
|
+
const created = await linodeRequest(`/domains/${zoneId}/records`, 'POST', {
|
|
292
319
|
type: 'TXT',
|
|
293
320
|
name: recordName,
|
|
294
321
|
target: dnsAuthorization,
|
|
@@ -296,8 +323,8 @@ module.exports.create = function create(config = {}) {
|
|
|
296
323
|
});
|
|
297
324
|
|
|
298
325
|
if (created?.id) {
|
|
299
|
-
linodeTxtRecordsByHost.set(dnsHost, { zone, id: created.id });
|
|
300
|
-
return { zone, id: created.id, reused: false };
|
|
326
|
+
linodeTxtRecordsByHost.set(dnsHost, { zone, zoneId, id: created.id });
|
|
327
|
+
return { zone, zoneId, id: created.id, reused: false };
|
|
301
328
|
}
|
|
302
329
|
}
|
|
303
330
|
|
|
@@ -307,8 +334,8 @@ module.exports.create = function create(config = {}) {
|
|
|
307
334
|
|
|
308
335
|
async function linodeRemoveTxtRecord(dnsHost) {
|
|
309
336
|
const stored = linodeTxtRecordsByHost.get(dnsHost);
|
|
310
|
-
if (!stored?.
|
|
311
|
-
await linodeRequest(`/domains/${
|
|
337
|
+
if (!stored?.zoneId || !stored?.id) return false;
|
|
338
|
+
await linodeRequest(`/domains/${stored.zoneId}/records/${stored.id}`, 'DELETE');
|
|
312
339
|
linodeTxtRecordsByHost.delete(dnsHost);
|
|
313
340
|
return true;
|
|
314
341
|
}
|