roster-server 2.1.1 → 2.1.4
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 +63 -12
- 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) {
|
|
@@ -754,8 +793,18 @@ class Roster {
|
|
|
754
793
|
const dispatcher = createDispatcher(portData);
|
|
755
794
|
const upgradeHandler = createUpgradeHandler(portData);
|
|
756
795
|
const greenlockStorePath = this.greenlockStorePath;
|
|
796
|
+
const normalizeHostInput = (value) => {
|
|
797
|
+
if (typeof value === 'string') return value;
|
|
798
|
+
if (!value || typeof value !== 'object') return '';
|
|
799
|
+
if (typeof value.servername === 'string') return value.servername;
|
|
800
|
+
if (typeof value.hostname === 'string') return value.hostname;
|
|
801
|
+
if (typeof value.subject === 'string') return value.subject;
|
|
802
|
+
return '';
|
|
803
|
+
};
|
|
757
804
|
const loadCert = (subjectDir) => {
|
|
758
|
-
const
|
|
805
|
+
const normalizedSubject = normalizeHostInput(subjectDir).trim().toLowerCase();
|
|
806
|
+
if (!normalizedSubject) return null;
|
|
807
|
+
const certPath = path.join(greenlockStorePath, 'live', normalizedSubject);
|
|
759
808
|
const keyPath = path.join(certPath, 'privkey.pem');
|
|
760
809
|
const certFilePath = path.join(certPath, 'cert.pem');
|
|
761
810
|
const chainPath = path.join(certPath, 'chain.pem');
|
|
@@ -767,14 +816,15 @@ class Roster {
|
|
|
767
816
|
}
|
|
768
817
|
return null;
|
|
769
818
|
};
|
|
770
|
-
const zoneSubjectForHost = (servername) => {
|
|
771
|
-
const labels = String(servername || '').split('.').filter(Boolean);
|
|
772
|
-
if (labels.length < 3) return null;
|
|
773
|
-
return labels.slice(1).join('.');
|
|
774
|
-
};
|
|
775
819
|
const resolvePemsForServername = (servername) => {
|
|
776
|
-
|
|
777
|
-
|
|
820
|
+
const host = normalizeHostInput(servername).trim().toLowerCase();
|
|
821
|
+
if (!host) return null;
|
|
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;
|
|
778
828
|
};
|
|
779
829
|
|
|
780
830
|
if (portNum === this.defaultPort) {
|
|
@@ -787,8 +837,7 @@ class Roster {
|
|
|
787
837
|
if (isBunRuntime) {
|
|
788
838
|
const primaryDomain = Object.keys(portData.virtualServers)[0];
|
|
789
839
|
// Greenlock stores certs by subject (e.g. tagnu.com), not by wildcard (*.tagnu.com)
|
|
790
|
-
const
|
|
791
|
-
const defaultPems = resolvePemsForServername(certSubject);
|
|
840
|
+
const defaultPems = resolvePemsForServername(primaryDomain);
|
|
792
841
|
|
|
793
842
|
if (defaultPems) {
|
|
794
843
|
httpsServer = https.createServer({
|
|
@@ -806,7 +855,7 @@ class Roster {
|
|
|
806
855
|
}, dispatcher);
|
|
807
856
|
log.warn(`⚠️ Bun runtime detected: using file-based TLS with SNI for ${primaryDomain} on port ${portNum}`);
|
|
808
857
|
} else {
|
|
809
|
-
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`);
|
|
810
859
|
httpsServer = glx.httpsServer(tlsOpts, dispatcher);
|
|
811
860
|
}
|
|
812
861
|
} else {
|
|
@@ -873,4 +922,6 @@ class Roster {
|
|
|
873
922
|
|
|
874
923
|
module.exports = Roster;
|
|
875
924
|
module.exports.wildcardRoot = wildcardRoot;
|
|
876
|
-
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', () => {
|