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 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
- return loadCert(host) || loadCert(zoneSubjectForHost(host));
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 certSubject = primaryDomain.startsWith('*.') ? wildcardRoot(primaryDomain) : primaryDomain;
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 ${certSubject} (${primaryDomain}); falling back to Greenlock HTTPS server`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "2.1.2",
3
+ "version": "2.1.6",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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 { wildcardRoot, hostMatchesWildcard } = require('../index.js');
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', () => {