roster-server 2.1.2 → 2.1.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/index.js +50 -11
- package/package.json +1 -1
- package/test/roster-server.test.js +35 -1
package/index.js
CHANGED
|
@@ -47,6 +47,45 @@ function hostMatchesWildcard(host, pattern) {
|
|
|
47
47
|
return h.endsWith('.' + suffix) && h.length > suffix.length;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function wildcardSubjectForHost(host) {
|
|
51
|
+
const normalized = (host || '').trim().toLowerCase();
|
|
52
|
+
const labels = normalized.split('.').filter(Boolean);
|
|
53
|
+
if (labels.length < 3) return null;
|
|
54
|
+
return `*.${labels.slice(1).join('.')}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function certDirCandidatesForSubject(subject) {
|
|
58
|
+
const normalized = (subject || '').trim().toLowerCase();
|
|
59
|
+
if (!normalized) return [];
|
|
60
|
+
if (!normalized.startsWith('*.')) return [normalized];
|
|
61
|
+
const zone = wildcardRoot(normalized);
|
|
62
|
+
if (!zone) return [normalized];
|
|
63
|
+
// greenlock-store-fs may persist wildcard subjects under _wildcard_.<zone>
|
|
64
|
+
return [normalized, `_wildcard_.${zone}`];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildCertLookupCandidates(servername) {
|
|
68
|
+
const normalized = (servername || '').trim().toLowerCase();
|
|
69
|
+
if (!normalized) return [];
|
|
70
|
+
|
|
71
|
+
const subjects = [normalized];
|
|
72
|
+
const wildcardSubject = wildcardSubjectForHost(normalized);
|
|
73
|
+
if (wildcardSubject) subjects.push(wildcardSubject);
|
|
74
|
+
const zoneSubject = wildcardRoot(normalized) || (wildcardSubject ? wildcardRoot(wildcardSubject) : null);
|
|
75
|
+
if (zoneSubject) subjects.push(zoneSubject);
|
|
76
|
+
|
|
77
|
+
const candidates = [];
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
for (const subject of subjects) {
|
|
80
|
+
for (const certDir of certDirCandidatesForSubject(subject)) {
|
|
81
|
+
if (seen.has(certDir)) continue;
|
|
82
|
+
seen.add(certDir);
|
|
83
|
+
candidates.push(certDir);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return candidates;
|
|
87
|
+
}
|
|
88
|
+
|
|
50
89
|
// Virtual Server that completely isolates applications
|
|
51
90
|
class VirtualServer extends EventEmitter {
|
|
52
91
|
constructor(domain) {
|
|
@@ -777,16 +816,15 @@ class Roster {
|
|
|
777
816
|
}
|
|
778
817
|
return null;
|
|
779
818
|
};
|
|
780
|
-
const zoneSubjectForHost = (servername) => {
|
|
781
|
-
const host = normalizeHostInput(servername).trim().toLowerCase();
|
|
782
|
-
const labels = host.split('.').filter(Boolean);
|
|
783
|
-
if (labels.length < 3) return null;
|
|
784
|
-
return labels.slice(1).join('.');
|
|
785
|
-
};
|
|
786
819
|
const resolvePemsForServername = (servername) => {
|
|
787
820
|
const host = normalizeHostInput(servername).trim().toLowerCase();
|
|
788
821
|
if (!host) return null;
|
|
789
|
-
|
|
822
|
+
const candidates = buildCertLookupCandidates(host);
|
|
823
|
+
for (const candidate of candidates) {
|
|
824
|
+
const pems = loadCert(candidate);
|
|
825
|
+
if (pems) return pems;
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
790
828
|
};
|
|
791
829
|
|
|
792
830
|
if (portNum === this.defaultPort) {
|
|
@@ -799,8 +837,7 @@ class Roster {
|
|
|
799
837
|
if (isBunRuntime) {
|
|
800
838
|
const primaryDomain = Object.keys(portData.virtualServers)[0];
|
|
801
839
|
// Greenlock stores certs by subject (e.g. tagnu.com), not by wildcard (*.tagnu.com)
|
|
802
|
-
const
|
|
803
|
-
const defaultPems = resolvePemsForServername(certSubject);
|
|
840
|
+
const defaultPems = resolvePemsForServername(primaryDomain);
|
|
804
841
|
|
|
805
842
|
if (defaultPems) {
|
|
806
843
|
httpsServer = https.createServer({
|
|
@@ -818,7 +855,7 @@ class Roster {
|
|
|
818
855
|
}, dispatcher);
|
|
819
856
|
log.warn(`⚠️ Bun runtime detected: using file-based TLS with SNI for ${primaryDomain} on port ${portNum}`);
|
|
820
857
|
} else {
|
|
821
|
-
log.warn(`⚠️ Bun runtime detected but cert files missing for ${
|
|
858
|
+
log.warn(`⚠️ Bun runtime detected but cert files missing for ${primaryDomain}; falling back to Greenlock HTTPS server`);
|
|
822
859
|
httpsServer = glx.httpsServer(tlsOpts, dispatcher);
|
|
823
860
|
}
|
|
824
861
|
} else {
|
|
@@ -885,4 +922,6 @@ class Roster {
|
|
|
885
922
|
|
|
886
923
|
module.exports = Roster;
|
|
887
924
|
module.exports.wildcardRoot = wildcardRoot;
|
|
888
|
-
module.exports.hostMatchesWildcard = hostMatchesWildcard;
|
|
925
|
+
module.exports.hostMatchesWildcard = hostMatchesWildcard;
|
|
926
|
+
module.exports.wildcardSubjectForHost = wildcardSubjectForHost;
|
|
927
|
+
module.exports.buildCertLookupCandidates = buildCertLookupCandidates;
|
package/package.json
CHANGED
|
@@ -7,7 +7,12 @@ const fs = require('fs');
|
|
|
7
7
|
const http = require('http');
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const Roster = require('../index.js');
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
wildcardRoot,
|
|
12
|
+
hostMatchesWildcard,
|
|
13
|
+
wildcardSubjectForHost,
|
|
14
|
+
buildCertLookupCandidates
|
|
15
|
+
} = require('../index.js');
|
|
11
16
|
|
|
12
17
|
function closePortServers(roster) {
|
|
13
18
|
if (roster.portServers && typeof roster.portServers === 'object') {
|
|
@@ -75,6 +80,35 @@ describe('hostMatchesWildcard', () => {
|
|
|
75
80
|
});
|
|
76
81
|
});
|
|
77
82
|
|
|
83
|
+
describe('wildcardSubjectForHost', () => {
|
|
84
|
+
it('returns wildcard subject for subdomain hosts', () => {
|
|
85
|
+
assert.strictEqual(wildcardSubjectForHost('admin.tagnu.com'), '*.tagnu.com');
|
|
86
|
+
assert.strictEqual(wildcardSubjectForHost('api.eu.example.com'), '*.eu.example.com');
|
|
87
|
+
});
|
|
88
|
+
it('returns null for apex hosts', () => {
|
|
89
|
+
assert.strictEqual(wildcardSubjectForHost('tagnu.com'), null);
|
|
90
|
+
assert.strictEqual(wildcardSubjectForHost('localhost'), null);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('buildCertLookupCandidates', () => {
|
|
95
|
+
it('prioritizes wildcard cert for subdomains and includes apex fallback', () => {
|
|
96
|
+
assert.deepStrictEqual(
|
|
97
|
+
buildCertLookupCandidates('admin.tagnu.com'),
|
|
98
|
+
['admin.tagnu.com', '*.tagnu.com', '_wildcard_.tagnu.com', 'tagnu.com']
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
it('includes wildcard storage path candidates when wildcard subject is provided', () => {
|
|
102
|
+
assert.deepStrictEqual(
|
|
103
|
+
buildCertLookupCandidates('*.tagnu.com'),
|
|
104
|
+
['*.tagnu.com', '_wildcard_.tagnu.com', 'tagnu.com']
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
it('returns only apex subject for apex hosts', () => {
|
|
108
|
+
assert.deepStrictEqual(buildCertLookupCandidates('tagnu.com'), ['tagnu.com']);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
78
112
|
describe('Roster', () => {
|
|
79
113
|
describe('parseDomainWithPort', () => {
|
|
80
114
|
it('parses *.example.com with default port', () => {
|