@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.
- package/lib/nginx/index.js +72 -9
- package/lib/util.js +39 -4
- package/package.json +8 -8
package/lib/nginx/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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-
|
|
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-
|
|
34
|
-
"@abtnode/db-cache": "1.17.8-beta-
|
|
35
|
-
"@abtnode/logger": "1.17.8-beta-
|
|
36
|
-
"@abtnode/router-templates": "1.17.8-beta-
|
|
37
|
-
"@abtnode/util": "1.17.8-beta-
|
|
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.
|
|
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": "
|
|
63
|
+
"gitHead": "241254785bda907be2296228869b4fc9c1679a6b"
|
|
64
64
|
}
|