@abtnode/router-provider 1.17.8-beta-20260119-102944-6ba93a16 → 1.17.8-beta-20260125-093329-64b43854

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.
@@ -76,7 +76,9 @@ const {
76
76
  const REQUIRED_MODULES = [{ configName: 'with-stream', moduleBinaryName: 'ngx_stream_module.so' }];
77
77
 
78
78
  // convert wildcard domain and ipDnsDomain template to regex
79
- const parseServerName = (domain) => {
79
+ const parseServerName = (domain, options = {}) => {
80
+ const { captureSubdomain = false } = options;
81
+
80
82
  // ipDnsDomain template
81
83
  if (domain.includes(SLOT_FOR_IP_DNS_SITE)) {
82
84
  const name = domain.replace(/\./g, '\\.').replace(SLOT_FOR_IP_DNS_SITE, '\\d+-\\d+-\\d+-\\d+');
@@ -86,9 +88,17 @@ const parseServerName = (domain) => {
86
88
  // wildcard domain
87
89
  if (domain.startsWith('*.')) {
88
90
  const name = domain.replace('*', '').replace(/\./g, '\\.');
91
+
92
+ // For sub-service, capture subdomain as Nginx variable
93
+ if (captureSubdomain) {
94
+ return `~^(?<subdomain>.+)${name}$`;
95
+ }
96
+
97
+ // Default wildcard match (existing behavior)
89
98
  return `~.+${name}$`;
90
99
  }
91
100
 
101
+ // Single domain - return as is
92
102
  return domain;
93
103
  };
94
104
 
@@ -305,10 +315,13 @@ class NginxProvider extends BaseProvider {
305
315
  // Process system sites in main conf
306
316
  // eslint-disable-next-line no-restricted-syntax
307
317
  for (const site of systemSites) {
308
- const { domain, port, rules, blockletDid } = site;
318
+ const { domain, port, rules, blockletDid, type } = site;
309
319
  const certificate = findCertificate(certificates, domain);
310
320
 
311
- const parsedServerName = parseServerName(domain);
321
+ // For sub-service sites, enable subdomain capture in server_name
322
+ const parsedServerName = parseServerName(domain, {
323
+ captureSubdomain: type === ROUTING_RULE_TYPES.SUB_SERVICE,
324
+ });
312
325
  if (!parsedServerName) {
313
326
  logger.warn('invalid site, empty server name:', { site: JSON.stringify(site), domain, parsedServerName });
314
327
  // eslint-disable-next-line no-continue
@@ -526,9 +539,13 @@ class NginxProvider extends BaseProvider {
526
539
  try {
527
540
  // eslint-disable-next-line no-restricted-syntax
528
541
  for (const site of sites) {
529
- const { domain, rules, port, serviceType } = site;
542
+ const { domain, rules, port, serviceType, type } = site;
530
543
  const certificate = findCertificate(certificates, domain);
531
- const parsedServerName = parseServerName(domain);
544
+
545
+ // For sub-service sites, enable subdomain capture in server_name
546
+ const parsedServerName = parseServerName(domain, {
547
+ captureSubdomain: type === ROUTING_RULE_TYPES.SUB_SERVICE,
548
+ });
532
549
 
533
550
  if (!parsedServerName) {
534
551
  logger.warn('invalid site, empty server name:', { site: JSON.stringify(site), domain, parsedServerName });
@@ -772,6 +789,12 @@ class NginxProvider extends BaseProvider {
772
789
  return;
773
790
  }
774
791
 
792
+ // Check for sub-service type (dynamic subdomain static serving)
793
+ if (type === ROUTING_RULE_TYPES.SUB_SERVICE) {
794
+ this._addSubServiceLocation(args);
795
+ return;
796
+ }
797
+
775
798
  // Check for static serving (public static blocklets served directly by Nginx)
776
799
  if (type === ROUTING_RULE_TYPES.BLOCKLET && args.staticRoot) {
777
800
  this._addStaticLocation(args);
@@ -967,6 +990,39 @@ class NginxProvider extends BaseProvider {
967
990
  this._addSecurityHeaders(location, serviceType);
968
991
  }
969
992
 
993
+ _addSubServiceLocation({ server, prefix, staticRoot, serverName, commonHeaders, serviceType }) {
994
+ // Check if this is a wildcard domain (starts with *.)
995
+ // For wildcard domains: use $subdomain variable from server_name regex capture
996
+ // For single domains: serve files directly from staticRoot
997
+ const isWildcardDomain = serverName && serverName.includes('(?<subdomain>.+)');
998
+
999
+ if (isWildcardDomain) {
1000
+ // For wildcard sub-service, use root with $subdomain variable
1001
+ // The $subdomain variable comes from server_name ~^(?<subdomain>.+)\.domain$
1002
+ server._add('root', `${staticRoot}/$subdomain`);
1003
+ } else {
1004
+ // For single domain sub-service, serve files directly from staticRoot
1005
+ server._add('root', staticRoot);
1006
+ }
1007
+
1008
+ server._add('location', prefix);
1009
+ const location = this._getLastLocation(server);
1010
+
1011
+ // SPA fallback: try exact file, directory with index, then /index.html, finally 404
1012
+ location._add('try_files', '$uri $uri/ /index.html =404');
1013
+
1014
+ // Cache control for static assets
1015
+ location._add('expires', '30d');
1016
+ location._add('add_header', 'Cache-Control "public, max-age=2592000"');
1017
+
1018
+ // Security headers
1019
+ location._add('add_header', 'X-Content-Type-Options "nosniff"');
1020
+
1021
+ // Add common response headers
1022
+ this._addCommonResHeaders(location, commonHeaders);
1023
+ this._addSecurityHeaders(location, serviceType);
1024
+ }
1025
+
970
1026
  _addRedirectTypeLocation({ server, url, redirectCode, prefix, suffix, serviceType }) {
971
1027
  const cleanUrl = trimEndSlash(url);
972
1028
  server._add('location', `${concatPath(prefix, suffix)}`);
@@ -1070,7 +1126,7 @@ class NginxProvider extends BaseProvider {
1070
1126
  location._addVerbatimBlock('if ($query_string)', 'set $abt_query_string "?$query_string";');
1071
1127
  }
1072
1128
 
1073
- _addDefaultLocations({ server, daemonPort, serverName }) {
1129
+ _addDefaultLocations({ server, daemonPort, serverName, skipDefaultRoot = false }) {
1074
1130
  if (!server) {
1075
1131
  throw new Error('server is required');
1076
1132
  }
@@ -1079,7 +1135,10 @@ class NginxProvider extends BaseProvider {
1079
1135
  throw new Error('daemonPort is required');
1080
1136
  }
1081
1137
 
1082
- server._add('root', this.getRelativeConfigDir(this.wwwDir));
1138
+ // For sub-service sites, skip default root as it will be set with $subdomain variable
1139
+ if (!skipDefaultRoot) {
1140
+ server._add('root', this.getRelativeConfigDir(this.wwwDir));
1141
+ }
1083
1142
  server._addVerbatimBlock('if ($access_blocked)', 'return 403;');
1084
1143
 
1085
1144
  this._addHostBlockWhitelistServer({ server, serverName });
@@ -1362,7 +1421,9 @@ class NginxProvider extends BaseProvider {
1362
1421
 
1363
1422
  _addHttpServer({ locations = [], serverName, conf, port, daemonPort, commonHeaders, blockletDid, serviceType }) {
1364
1423
  const httpServerUnit = this._addHttpServerUnit({ conf, serverName, port });
1365
- this._addDefaultLocations({ server: httpServerUnit, daemonPort, serverName });
1424
+ // Skip default root for subService sites
1425
+ const skipDefaultRoot = locations.some((x) => x.type === ROUTING_RULE_TYPES.SUB_SERVICE);
1426
+ this._addDefaultLocations({ server: httpServerUnit, daemonPort, serverName, skipDefaultRoot });
1366
1427
  // eslint-disable-next-line max-len
1367
1428
  locations.forEach((x) => this._addReverseProxy({ server: httpServerUnit, ...x, serverName, commonHeaders, blockletDid, serviceType })); // prettier-ignore
1368
1429
  }
@@ -1385,7 +1446,9 @@ class NginxProvider extends BaseProvider {
1385
1446
  const httpServerUnit = this._addHttpServerUnit({ conf, serverName });
1386
1447
  httpServerUnit._add('return', '307 https://$host$request_uri'); // redirect to https if has https
1387
1448
 
1388
- this._addDefaultLocations({ server: httpsServerUnit, daemonPort, serverName });
1449
+ // Check if any location is SUB_SERVICE type, if so skip default root
1450
+ const hasSubService = locations.some((x) => x.type === ROUTING_RULE_TYPES.SUB_SERVICE);
1451
+ this._addDefaultLocations({ server: httpsServerUnit, daemonPort, serverName, skipDefaultRoot: hasSubService });
1389
1452
  // eslint-disable-next-line max-len
1390
1453
  locations.forEach((x) => this._addReverseProxy({ server: httpsServerUnit, ...x, serverName, commonHeaders, blockletDid, serviceType })); // prettier-ignore
1391
1454
  }
package/lib/util.js CHANGED
@@ -23,10 +23,44 @@ const decideHttpPort = (port) => port || process.env.ABT_NODE_HTTP_PORT || DEFAU
23
23
  const decideHttpsPort = (port) => port || process.env.ABT_NODE_HTTPS_PORT || DEFAULT_HTTPS_PORT;
24
24
 
25
25
  const findCertificate = (certs, domain) => {
26
- const results = certs
27
- .filter((cert) => [cert.domain, ...(cert.sans || [])].some((d) => checkDomainMatch(d, domain)))
28
- .sort((d1) => (d1.domain[0] === '*' ? 1 : -1)); // will first match a.b.com, then *.b.com
29
- return results[0];
26
+ const lowerDomain = domain.toLowerCase();
27
+
28
+ // First try exact match (highest priority)
29
+ // Note: Domain matching should be case-insensitive per DNS standards
30
+ for (const cert of certs) {
31
+ const certDomains = [cert.domain, ...(cert.sans || [])].map((d) => d.toLowerCase());
32
+ if (certDomains.includes(lowerDomain)) {
33
+ return cert;
34
+ }
35
+ }
36
+
37
+ // Then try wildcard match
38
+ // Note: wildcard cert *.example.com should match foo.example.com
39
+ // but should NOT match example.com itself (per SSL/TLS standards)
40
+ const domainParts = domain.split('.');
41
+
42
+ for (const cert of certs) {
43
+ const certDomains = [cert.domain, ...(cert.sans || [])];
44
+
45
+ for (const certDomain of certDomains) {
46
+ // Skip wildcard certs that would incorrectly match the base domain
47
+ // e.g., *.staging.myvibe.so should not match staging.myvibe.so
48
+ if (certDomain.startsWith('*.')) {
49
+ const certBaseParts = certDomain.substring(2).split('.');
50
+ // If domain has same or fewer parts than cert base, it's the base domain or shorter
51
+ // Wildcard should only match domains with more parts (subdomains)
52
+ if (domainParts.length <= certBaseParts.length) {
53
+ continue; // eslint-disable-line no-continue
54
+ }
55
+ }
56
+
57
+ if (checkDomainMatch(certDomain, domain)) {
58
+ return cert;
59
+ }
60
+ }
61
+ }
62
+
63
+ return undefined;
30
64
  };
31
65
 
32
66
  const trimEndSlash = (str) => {
@@ -64,6 +98,7 @@ const formatRoutingTable = (routingTable) => {
64
98
  if (!sites[domain]) {
65
99
  sites[domain] = {
66
100
  domain,
101
+ type: site.type,
67
102
  blockletDid: site.blockletDid,
68
103
  rules: [],
69
104
  port: site.port,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abtnode/router-provider",
3
- "version": "1.17.8-beta-20260119-102944-6ba93a16",
3
+ "version": "1.17.8-beta-20260125-093329-64b43854",
4
4
  "description": "Routing engine implementations for abt node",
5
5
  "author": "polunzh <polunzh@gmail.com>",
6
6
  "homepage": "https://github.com/ArcBlock/blocklet-server#readme",
@@ -30,14 +30,14 @@
30
30
  "url": "https://github.com/ArcBlock/blocklet-server/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@abtnode/constant": "1.17.8-beta-20260119-102944-6ba93a16",
34
- "@abtnode/db-cache": "1.17.8-beta-20260119-102944-6ba93a16",
35
- "@abtnode/logger": "1.17.8-beta-20260119-102944-6ba93a16",
36
- "@abtnode/router-templates": "1.17.8-beta-20260119-102944-6ba93a16",
37
- "@abtnode/util": "1.17.8-beta-20260119-102944-6ba93a16",
33
+ "@abtnode/constant": "1.17.8-beta-20260125-093329-64b43854",
34
+ "@abtnode/db-cache": "1.17.8-beta-20260125-093329-64b43854",
35
+ "@abtnode/logger": "1.17.8-beta-20260125-093329-64b43854",
36
+ "@abtnode/router-templates": "1.17.8-beta-20260125-093329-64b43854",
37
+ "@abtnode/util": "1.17.8-beta-20260125-093329-64b43854",
38
38
  "@arcblock/http-proxy": "^1.19.1",
39
39
  "@arcblock/is-valid-domain": "^1.0.5",
40
- "@ocap/util": "^1.28.5",
40
+ "@ocap/util": "^1.28.6",
41
41
  "axios": "^1.7.9",
42
42
  "debug": "^4.4.1",
43
43
  "fast-glob": "^3.3.2",
@@ -60,5 +60,5 @@
60
60
  "bluebird": "^3.7.2",
61
61
  "fs-extra": "^11.2.0"
62
62
  },
63
- "gitHead": "52d0430ade8bad2dbc91251b6183d152116fee0f"
63
+ "gitHead": "241254785bda907be2296228869b4fc9c1679a6b"
64
64
  }