@abtnode/core 1.15.17 → 1.16.0-beta-8ee536d7
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/api/node.js +67 -69
- package/lib/api/team.js +386 -55
- package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
- package/lib/blocklet/downloader/bundle-downloader.js +272 -0
- package/lib/blocklet/downloader/constants.js +3 -0
- package/lib/blocklet/downloader/resolve-download.js +199 -0
- package/lib/blocklet/extras.js +83 -26
- package/lib/blocklet/hooks.js +18 -65
- package/lib/blocklet/manager/base.js +10 -16
- package/lib/blocklet/manager/disk.js +1680 -1566
- package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/migration.js +30 -52
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +62 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
- package/lib/blocklet/storage/backup/blocklet.js +70 -0
- package/lib/blocklet/storage/backup/blocklets.js +74 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +24 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +240 -0
- package/lib/blocklet/storage/restore/base.js +67 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
- package/lib/blocklet/storage/restore/blocklet.js +56 -0
- package/lib/blocklet/storage/restore/blocklets.js +43 -0
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +156 -0
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/cert.js +206 -0
- package/lib/event.js +237 -64
- package/lib/index.js +191 -83
- package/lib/migrations/1.0.21-update-config.js +1 -1
- package/lib/migrations/1.0.22-max-memory.js +1 -1
- package/lib/migrations/1.0.25.js +1 -1
- package/lib/migrations/1.0.32-update-config.js +1 -1
- package/lib/migrations/1.0.33-blocklets.js +1 -1
- package/lib/migrations/1.5.20-registry.js +15 -0
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
- package/lib/migrations/1.6.4-security.js +59 -0
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
- package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
- package/lib/migrations/1.7.20-blocklet-component.js +41 -0
- package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
- package/lib/migrations/README.md +1 -1
- package/lib/migrations/index.js +6 -2
- package/lib/monitor/blocklet-runtime-monitor.js +200 -0
- package/lib/monitor/get-history-list.js +37 -0
- package/lib/monitor/node-runtime-monitor.js +228 -0
- package/lib/router/helper.js +576 -500
- package/lib/router/index.js +85 -21
- package/lib/router/manager.js +146 -187
- package/lib/states/README.md +36 -1
- package/lib/states/access-key.js +39 -17
- package/lib/states/audit-log.js +462 -0
- package/lib/states/base.js +4 -213
- package/lib/states/blocklet-extras.js +195 -138
- package/lib/states/blocklet.js +371 -110
- package/lib/states/cache.js +8 -6
- package/lib/states/challenge.js +5 -5
- package/lib/states/index.js +19 -36
- package/lib/states/migration.js +4 -4
- package/lib/states/node.js +135 -46
- package/lib/states/notification.js +22 -35
- package/lib/states/session.js +17 -9
- package/lib/states/site.js +50 -25
- package/lib/states/user.js +74 -20
- package/lib/states/webhook.js +10 -6
- package/lib/team/manager.js +124 -7
- package/lib/util/blocklet.js +1223 -246
- package/lib/util/chain.js +1 -1
- package/lib/util/default-node-config.js +5 -23
- package/lib/util/disk-monitor.js +13 -10
- package/lib/util/domain-status.js +84 -15
- package/lib/util/get-accessible-external-node-ip.js +2 -2
- package/lib/util/get-domain-for-blocklet.js +13 -0
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +207 -272
- package/lib/util/ip.js +6 -0
- package/lib/util/maintain.js +233 -0
- package/lib/util/public-to-store.js +85 -0
- package/lib/util/ready.js +1 -1
- package/lib/util/requirement.js +28 -9
- package/lib/util/reset-node.js +22 -7
- package/lib/util/router.js +13 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/store.js +179 -0
- package/lib/util/sysinfo.js +44 -0
- package/lib/util/ua.js +54 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/node.js +25 -12
- package/lib/validators/permission.js +16 -1
- package/lib/validators/role.js +17 -3
- package/lib/validators/router.js +40 -20
- package/lib/validators/trusted-passport.js +1 -0
- package/lib/validators/util.js +22 -5
- package/lib/webhook/index.js +45 -35
- package/lib/webhook/sender/index.js +5 -0
- package/lib/webhook/sender/slack/index.js +1 -1
- package/lib/webhook/sender/wallet/index.js +48 -0
- package/package.json +54 -36
- package/lib/blocklet/registry.js +0 -205
- package/lib/states/https-cert.js +0 -67
- package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
- package/lib/util/service.js +0 -66
- package/lib/util/upgrade.js +0 -178
- /package/lib/{queue.js → util/queue.js} +0 -0
package/lib/router/helper.js
CHANGED
|
@@ -3,63 +3,78 @@
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const tar = require('tar');
|
|
6
|
+
const isUrl = require('is-url');
|
|
6
7
|
const get = require('lodash/get');
|
|
7
8
|
const cloneDeep = require('lodash/cloneDeep');
|
|
9
|
+
const isEqual = require('lodash/isEqual');
|
|
8
10
|
const joinUrl = require('url-join');
|
|
11
|
+
const { replaceSlotToIp, findComponentById, findWebInterface } = require('@blocklet/meta/lib/util');
|
|
9
12
|
const { getProvider } = require('@abtnode/router-provider');
|
|
10
13
|
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
11
|
-
const
|
|
14
|
+
const getTmpDir = require('@abtnode/util/lib/get-tmp-directory');
|
|
12
15
|
const downloadFile = require('@abtnode/util/lib/download-file');
|
|
16
|
+
const { updateBlockletDocument } = require('@abtnode/util/lib/did-document');
|
|
17
|
+
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
18
|
+
const { forEachBlocklet } = require('@blocklet/meta/lib/util');
|
|
13
19
|
const {
|
|
14
20
|
DOMAIN_FOR_DEFAULT_SITE,
|
|
15
21
|
DOMAIN_FOR_IP_SITE_REGEXP,
|
|
16
|
-
ROUTER_PROVIDER_NONE,
|
|
17
22
|
DOMAIN_FOR_INTERNAL_SITE,
|
|
18
23
|
WELLKNOWN_PATH_PREFIX,
|
|
19
|
-
|
|
24
|
+
WELLKNOWN_SERVICE_PATH_PREFIX,
|
|
25
|
+
USER_AVATAR_PATH_PREFIX,
|
|
20
26
|
DOMAIN_FOR_IP_SITE,
|
|
21
27
|
NAME_FOR_WELLKNOWN_SITE,
|
|
22
28
|
DEFAULT_HTTP_PORT,
|
|
23
29
|
DEFAULT_HTTPS_PORT,
|
|
24
|
-
DAY_IN_MS,
|
|
25
|
-
NODE_MODES,
|
|
26
30
|
ROUTING_RULE_TYPES,
|
|
27
31
|
CERTIFICATE_EXPIRES_OFFSET,
|
|
28
|
-
CERTIFICATE_EXPIRES_WARNING_OFFSET,
|
|
29
|
-
DEFAULT_DAEMON_PORT,
|
|
30
32
|
DEFAULT_SERVICE_PATH,
|
|
31
33
|
SLOT_FOR_IP_DNS_SITE,
|
|
32
34
|
BLOCKLET_SITE_GROUP_SUFFIX,
|
|
35
|
+
WELLKNOWN_ACME_CHALLENGE_PREFIX,
|
|
36
|
+
WELLKNOWN_DID_RESOLVER_PREFIX,
|
|
37
|
+
WELLKNOWN_PING_PREFIX,
|
|
38
|
+
LOG_RETAIN_IN_DAYS,
|
|
39
|
+
EVENTS,
|
|
33
40
|
} = require('@abtnode/constant');
|
|
34
41
|
const {
|
|
35
42
|
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
36
43
|
BLOCKLET_INTERFACE_TYPE_WEB,
|
|
44
|
+
BLOCKLET_INTERFACE_PUBLIC,
|
|
37
45
|
BLOCKLET_INTERFACE_WELLKNOWN,
|
|
38
46
|
BLOCKLET_INTERFACE_TYPE_WELLKNOWN,
|
|
39
|
-
|
|
47
|
+
BLOCKLET_CONFIGURABLE_KEY,
|
|
48
|
+
BlockletEvents,
|
|
49
|
+
BLOCKLET_MODES,
|
|
50
|
+
} = require('@blocklet/constant');
|
|
40
51
|
|
|
41
52
|
// eslint-disable-next-line global-require
|
|
42
53
|
const logger = require('@abtnode/logger')(`${require('../../package.json').name}:router:helper`);
|
|
43
54
|
const {
|
|
44
55
|
getProviderFromNodeInfo,
|
|
45
|
-
trimSlash,
|
|
46
|
-
getInterfaceUrl,
|
|
47
|
-
getBlockletHost,
|
|
48
56
|
getHttpsCertInfo,
|
|
49
57
|
findInterfacePortByName,
|
|
50
58
|
getWellknownSitePort,
|
|
59
|
+
getServerDidDomain,
|
|
60
|
+
isGatewayCacheEnabled,
|
|
51
61
|
} = require('../util');
|
|
52
|
-
const {
|
|
53
|
-
const getIpDnsDomainForBlocklet = require('../util/get-ip-dns-domain-for-blocklet');
|
|
62
|
+
const { getIpDnsDomainForBlocklet, getDidDomainForBlocklet } = require('../util/get-domain-for-blocklet');
|
|
54
63
|
const { getFromCache: getAccessibleExternalNodeIp } = require('../util/get-accessible-external-node-ip');
|
|
55
64
|
|
|
56
65
|
const Router = require('./index');
|
|
57
66
|
const states = require('../states');
|
|
67
|
+
const { getBlockletDomainGroupName, getDidFromDomainGroupName } = require('../util/router');
|
|
68
|
+
const { getBlockletKnownAs } = require('../util/blocklet');
|
|
58
69
|
|
|
59
70
|
/**
|
|
60
71
|
* replace 888-888-888-888 with accessible ip for domain
|
|
61
72
|
*/
|
|
62
73
|
const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) => {
|
|
74
|
+
if (!sites) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
let ip;
|
|
64
79
|
const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
|
|
65
80
|
const match = ipRegex.exec(context.hostname);
|
|
@@ -79,7 +94,7 @@ const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) =>
|
|
|
79
94
|
return domain;
|
|
80
95
|
}
|
|
81
96
|
if (domain.value.includes(SLOT_FOR_IP_DNS_SITE) && ip) {
|
|
82
|
-
domain.value = domain.value
|
|
97
|
+
domain.value = replaceSlotToIp(domain.value, ip);
|
|
83
98
|
}
|
|
84
99
|
return domain;
|
|
85
100
|
});
|
|
@@ -96,89 +111,6 @@ const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) =>
|
|
|
96
111
|
});
|
|
97
112
|
};
|
|
98
113
|
|
|
99
|
-
const attachInterfaceUrls = async ({ sites = [], context, node }) => {
|
|
100
|
-
if (!sites) {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
attachRuntimeDomainAliases({ sites, context, node });
|
|
105
|
-
|
|
106
|
-
const getUrl = (rule, domain = '') => {
|
|
107
|
-
const host = getBlockletHost({ domain, context });
|
|
108
|
-
const prefix = trimSlash(rule.from.pathPrefix);
|
|
109
|
-
if (prefix) {
|
|
110
|
-
return `http://${host}/${prefix}/`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return `http://${host}/`;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const blocklets = await states.blocklet.getBlocklets();
|
|
117
|
-
const getInterfaces = (site) =>
|
|
118
|
-
(site.rules || []).map((tmpRule) => {
|
|
119
|
-
const rule = { ...tmpRule };
|
|
120
|
-
|
|
121
|
-
const ruleId = tmpRule.id;
|
|
122
|
-
const baseUrl = getUrl(rule, site.domain);
|
|
123
|
-
|
|
124
|
-
const interfaces = [];
|
|
125
|
-
if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
|
|
126
|
-
const blocklet = blocklets.find((b) => b.meta.did === rule.to.did);
|
|
127
|
-
|
|
128
|
-
if (!blocklet) {
|
|
129
|
-
logger.warn(`can not attach interface urls for non-existing blocklet ${rule.to.did}`);
|
|
130
|
-
rule.interfaces = interfaces;
|
|
131
|
-
return rule;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!getBlockletHost({ domain: site.domain, context })) {
|
|
135
|
-
logger.warn('blocklet host does not exist');
|
|
136
|
-
rule.interfaces = interfaces;
|
|
137
|
-
return rule;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
(blocklet.meta.interfaces || []).forEach((x) => {
|
|
141
|
-
if (x.port && !x.port.external) {
|
|
142
|
-
interfaces.push({
|
|
143
|
-
ruleId,
|
|
144
|
-
type: x.type,
|
|
145
|
-
name: x.name,
|
|
146
|
-
url: getInterfaceUrl({ baseUrl, url: '/', version: blocklet.meta.version }),
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
} else if (rule.to.type === ROUTING_RULE_TYPES.REDIRECT || rule.to.type === ROUTING_RULE_TYPES.NONE) {
|
|
151
|
-
interfaces.push({
|
|
152
|
-
ruleId,
|
|
153
|
-
type: 'web',
|
|
154
|
-
name: ROUTING_RULE_TYPES.REDIRECT,
|
|
155
|
-
url: baseUrl,
|
|
156
|
-
});
|
|
157
|
-
} else {
|
|
158
|
-
interfaces.push({
|
|
159
|
-
ruleId,
|
|
160
|
-
type: 'web',
|
|
161
|
-
name: 'default',
|
|
162
|
-
url: getUrl(rule),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
rule.interfaces = interfaces;
|
|
167
|
-
return rule;
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
if (!Array.isArray(sites)) {
|
|
171
|
-
sites.rules = getInterfaces(sites);
|
|
172
|
-
return sites;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return sites.map((site) => {
|
|
176
|
-
site.rules = getInterfaces(site);
|
|
177
|
-
|
|
178
|
-
return site;
|
|
179
|
-
});
|
|
180
|
-
};
|
|
181
|
-
|
|
182
114
|
const addCorsToSite = (site, rawUrl) => {
|
|
183
115
|
if (!site || !rawUrl) {
|
|
184
116
|
return;
|
|
@@ -193,7 +125,7 @@ const addCorsToSite = (site, rawUrl) => {
|
|
|
193
125
|
site.corsAllowedOrigins.push(url.hostname);
|
|
194
126
|
}
|
|
195
127
|
} catch (err) {
|
|
196
|
-
|
|
128
|
+
console.error('addCorsToSite', err);
|
|
197
129
|
}
|
|
198
130
|
};
|
|
199
131
|
|
|
@@ -222,6 +154,19 @@ const ensureServiceRule = async (sites) => {
|
|
|
222
154
|
return site;
|
|
223
155
|
});
|
|
224
156
|
};
|
|
157
|
+
const ensureRootRule = async (sites) => {
|
|
158
|
+
return sites.map((site) => {
|
|
159
|
+
if (!isBasicSite(site.domain) && !site.rules.some((x) => x.from.pathPrefix === '/')) {
|
|
160
|
+
site.rules.push({
|
|
161
|
+
from: { pathPrefix: '/' },
|
|
162
|
+
to: {
|
|
163
|
+
type: ROUTING_RULE_TYPES.NONE,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return site;
|
|
168
|
+
});
|
|
169
|
+
};
|
|
225
170
|
|
|
226
171
|
const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {}) => {
|
|
227
172
|
const info = await states.node.read();
|
|
@@ -243,6 +188,9 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
|
|
|
243
188
|
site.domain = DOMAIN_FOR_IP_SITE_REGEXP;
|
|
244
189
|
|
|
245
190
|
if (withDefaultCors) {
|
|
191
|
+
// Allow CORS from "Install on ABT Node"
|
|
192
|
+
addCorsToSite(site, info.registerUrl);
|
|
193
|
+
|
|
246
194
|
// Allow CORS from "Web Wallet"
|
|
247
195
|
addCorsToSite(site, info.webWalletUrl);
|
|
248
196
|
}
|
|
@@ -256,6 +204,7 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
|
|
|
256
204
|
* set rule.to.target by interface.path and interface.prefix
|
|
257
205
|
*/
|
|
258
206
|
const ensureLatestInterfaceInfo = async (sites = []) => {
|
|
207
|
+
// FIXME @linchen groupAllInterfaces() may have performance issue
|
|
259
208
|
const interfaces = await states.blocklet.groupAllInterfaces();
|
|
260
209
|
return sites.map((site) => {
|
|
261
210
|
if (!Array.isArray(site.rules)) {
|
|
@@ -263,16 +212,22 @@ const ensureLatestInterfaceInfo = async (sites = []) => {
|
|
|
263
212
|
}
|
|
264
213
|
|
|
265
214
|
site.rules = site.rules.map((rule) => {
|
|
266
|
-
|
|
215
|
+
if (rule.dynamic) {
|
|
216
|
+
return rule;
|
|
217
|
+
}
|
|
218
|
+
// If a rule already has a target and target is WELLKNOWN_SERVICE_PATH_PREFIX, just return
|
|
267
219
|
// Indicates that the rule id generated by the system for auth service
|
|
268
|
-
if (rule.isProtected && rule.to.target ===
|
|
220
|
+
if (rule.isProtected && rule.to.target === WELLKNOWN_SERVICE_PATH_PREFIX) {
|
|
221
|
+
return rule;
|
|
222
|
+
}
|
|
223
|
+
if (rule.isProtected && rule.to.target === joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, USER_AVATAR_PATH_PREFIX)) {
|
|
269
224
|
return rule;
|
|
270
225
|
}
|
|
271
226
|
|
|
272
|
-
const {
|
|
273
|
-
if (interfaces[
|
|
227
|
+
const { componentId, interfaceName } = rule.to;
|
|
228
|
+
if (interfaces[componentId] && interfaces[componentId][interfaceName]) {
|
|
274
229
|
// eslint-disable-next-line no-shadow
|
|
275
|
-
const { path, prefix } = interfaces[
|
|
230
|
+
const { path, prefix } = interfaces[componentId][interfaceName];
|
|
276
231
|
if (prefix === BLOCKLET_DYNAMIC_PATH_PREFIX) {
|
|
277
232
|
rule.to.target = path;
|
|
278
233
|
} else {
|
|
@@ -294,8 +249,8 @@ const ensureWellknownRule = async (sites) => {
|
|
|
294
249
|
for (const site of tempSites) {
|
|
295
250
|
// 不向 default site & ip site & wellknown site 添加 wellknown rule
|
|
296
251
|
if (![DOMAIN_FOR_INTERNAL_SITE, DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE].includes(site.domain)) {
|
|
252
|
+
// add /.well-known for blocklet
|
|
297
253
|
const isExists = site.rules.find((x) => x.from.pathPrefix === WELLKNOWN_PATH_PREFIX);
|
|
298
|
-
|
|
299
254
|
if (!isExists) {
|
|
300
255
|
site.rules.push({
|
|
301
256
|
from: { pathPrefix: WELLKNOWN_PATH_PREFIX },
|
|
@@ -308,40 +263,72 @@ const ensureWellknownRule = async (sites) => {
|
|
|
308
263
|
});
|
|
309
264
|
}
|
|
310
265
|
|
|
311
|
-
// add /.well-known/
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
266
|
+
// add /.well-known/service for blocklet
|
|
267
|
+
const blockletRules = site.rules
|
|
268
|
+
.filter((x) => x.to.type === ROUTING_RULE_TYPES.BLOCKLET)
|
|
269
|
+
// 可能存在挂载的 blocklet 不是自己, 是其他 blocklet
|
|
270
|
+
// 这里默认 pathPrefix 最短的是自己 ( 通常自己在 /, 其他 blocklet 在 /xxxx )
|
|
271
|
+
// 挂载其他 blocklet 的使用方式不推荐, 将来可能会被废弃
|
|
272
|
+
.sort((a, b) => (a.from.pathPrefix.length > b.from.pathPrefix.length ? 1 : -1));
|
|
273
|
+
if (blockletRules.length) {
|
|
274
|
+
// get pathPrefix for blocklet-service
|
|
275
|
+
const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.componentId);
|
|
276
|
+
|
|
277
|
+
const servicePathPrefix = joinUrl(
|
|
278
|
+
// rootBlockletRule?.from?.pathPrefix is for backwards compatibility
|
|
279
|
+
// rootBlockletRule?.from?.groupPathPrefix 在旧的场景中可能不为 '/' (blocklet 只能挂载在 relative prefix 下)
|
|
280
|
+
rootBlockletRule?.from?.groupPathPrefix || rootBlockletRule?.from?.pathPrefix || '/',
|
|
281
|
+
WELLKNOWN_SERVICE_PATH_PREFIX
|
|
282
|
+
);
|
|
320
283
|
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
284
|
+
// requests for /.well-known/service will stay in blocklet-service and never proxy back to blocklet
|
|
285
|
+
// so any rule is ok to be cloned
|
|
286
|
+
if (!site.rules.some((x) => x.from.pathPrefix === servicePathPrefix)) {
|
|
287
|
+
const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
|
|
288
|
+
rule.from.pathPrefix = servicePathPrefix;
|
|
289
|
+
rule.to.target = servicePathPrefix;
|
|
290
|
+
rule.isProtected = true;
|
|
291
|
+
rule.dynamic = true; // mark as dynamic to avoid redundant generated rules
|
|
292
|
+
site.rules.push(rule);
|
|
324
293
|
}
|
|
325
294
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
295
|
+
// Cache user avatar in nginx
|
|
296
|
+
const avatarPathPrefix = joinUrl(servicePathPrefix, USER_AVATAR_PATH_PREFIX);
|
|
297
|
+
if (!site.rules.some((x) => x.from.pathPrefix === avatarPathPrefix)) {
|
|
298
|
+
const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
|
|
299
|
+
rule.from.pathPrefix = avatarPathPrefix;
|
|
300
|
+
rule.to.cacheGroup = 'blockletProxy';
|
|
301
|
+
rule.to.target = avatarPathPrefix;
|
|
302
|
+
rule.isProtected = true;
|
|
303
|
+
rule.dynamic = true; // mark as dynamic to avoid redundant generated rules
|
|
304
|
+
site.rules.push(rule);
|
|
331
305
|
}
|
|
332
|
-
|
|
333
|
-
rule.from.pathPrefix = pathPrefix;
|
|
334
|
-
// let service gateway keep WELLKNOWN_AUTH_PATH_PREFIX prefix
|
|
335
|
-
rule.to.target = WELLKNOWN_AUTH_PATH_PREFIX;
|
|
336
|
-
rule.isProtected = true;
|
|
337
|
-
site.rules.push(rule);
|
|
338
|
-
});
|
|
306
|
+
}
|
|
339
307
|
}
|
|
340
308
|
}
|
|
341
309
|
|
|
342
310
|
return tempSites;
|
|
343
311
|
};
|
|
344
312
|
|
|
313
|
+
const ensureBlockletDid = async (sites) => {
|
|
314
|
+
const info = await states.node.read();
|
|
315
|
+
|
|
316
|
+
return (sites || []).map((site) => {
|
|
317
|
+
if (site.domain === DOMAIN_FOR_INTERNAL_SITE) {
|
|
318
|
+
return site;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if ([DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(site.domain)) {
|
|
322
|
+
site.blockletDid = info.did;
|
|
323
|
+
return site;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
site.blockletDid = getDidFromDomainGroupName(site.domain);
|
|
327
|
+
|
|
328
|
+
return site;
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
|
|
345
332
|
const ensureCorsForWebWallet = async (sites) => {
|
|
346
333
|
const info = await states.node.read();
|
|
347
334
|
for (const site of sites) {
|
|
@@ -353,48 +340,97 @@ const ensureCorsForWebWallet = async (sites) => {
|
|
|
353
340
|
return sites;
|
|
354
341
|
};
|
|
355
342
|
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
343
|
+
const ensureCorsForDidSpace = async (sites = [], blocklets) => {
|
|
344
|
+
return sites.map((site) => {
|
|
345
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
346
|
+
if (blocklet) {
|
|
347
|
+
const endpoint = blocklet.environments.find(
|
|
348
|
+
(x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
|
|
349
|
+
);
|
|
350
|
+
if (endpoint && isUrl(endpoint.value)) {
|
|
351
|
+
addCorsToSite(site, endpoint.value);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return site;
|
|
356
|
+
});
|
|
361
357
|
};
|
|
362
358
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}, {});
|
|
359
|
+
const filterSitesForRemovedBlocklets = async (sites = [], blocklets) => {
|
|
360
|
+
return sites.filter((site) => {
|
|
361
|
+
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
369
364
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
365
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
366
|
+
return !!blocklet;
|
|
367
|
+
});
|
|
368
|
+
};
|
|
375
369
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!blocklet) {
|
|
382
|
-
return;
|
|
370
|
+
const ensureBlockletCache = async (sites = [], blocklets) => {
|
|
371
|
+
return sites
|
|
372
|
+
.map((site) => {
|
|
373
|
+
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
374
|
+
return site;
|
|
383
375
|
}
|
|
384
376
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const interfaceName = rule.to.realInterfaceName || rule.to.interfaceName;
|
|
388
|
-
const { interfaces } = blocklet.meta;
|
|
389
|
-
const _interface = interfaces.find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB && x.name === interfaceName);
|
|
390
|
-
if (_interface) {
|
|
391
|
-
rule.services = rule.services || [];
|
|
392
|
-
rule.services.unshift(...getServicesFromBlockletInterface(_interface, { logError: logger.error }));
|
|
377
|
+
if (site.cacheableGenerated) {
|
|
378
|
+
return site;
|
|
393
379
|
}
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
380
|
|
|
397
|
-
|
|
381
|
+
// For each rule, get component, check cacheable, clone and push new rule
|
|
382
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
383
|
+
const cacheRules = [];
|
|
384
|
+
site.rules
|
|
385
|
+
.filter(
|
|
386
|
+
(x) =>
|
|
387
|
+
x.to.type === ROUTING_RULE_TYPES.BLOCKLET &&
|
|
388
|
+
x.to.interfaceName === BLOCKLET_INTERFACE_PUBLIC &&
|
|
389
|
+
x.from.pathPrefix.startsWith(WELLKNOWN_SERVICE_PATH_PREFIX) === false
|
|
390
|
+
)
|
|
391
|
+
.forEach((rule) => {
|
|
392
|
+
const component = findComponentById(blocklet, rule.to.componentId);
|
|
393
|
+
if (!component) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (component.mode !== BLOCKLET_MODES.PRODUCTION) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const cacheable = get(findWebInterface(component), 'cacheable', []);
|
|
401
|
+
cacheable.forEach((cachePrefix) => {
|
|
402
|
+
const clone = cloneDeep(rule);
|
|
403
|
+
clone.from.pathPrefix = joinUrl(rule.from.pathPrefix, cachePrefix);
|
|
404
|
+
clone.to.cacheGroup = 'blockletProxy';
|
|
405
|
+
clone.to.targetPrefix = cachePrefix;
|
|
406
|
+
clone.dynamic = true; // mark as dynamic to avoid redundant generated rules
|
|
407
|
+
cacheRules.push(clone);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
site.rules = site.rules.concat(cacheRules);
|
|
412
|
+
site.mode = blocklet.mode;
|
|
413
|
+
site.cacheableGenerated = true;
|
|
414
|
+
|
|
415
|
+
return site;
|
|
416
|
+
})
|
|
417
|
+
.filter(Boolean);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
|
|
421
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
422
|
+
|
|
423
|
+
// CAUTION: following steps are very important, please do not change the order
|
|
424
|
+
let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
|
|
425
|
+
result = await ensureBlockletDid(result);
|
|
426
|
+
result = await filterSitesForRemovedBlocklets(sites, blocklets);
|
|
427
|
+
result = await ensureBlockletCache(result, blocklets);
|
|
428
|
+
result = await ensureWellknownRule(result);
|
|
429
|
+
result = await ensureCorsForWebWallet(result);
|
|
430
|
+
result = await ensureCorsForDidSpace(result, blocklets);
|
|
431
|
+
result = await ensureLatestInterfaceInfo(result);
|
|
432
|
+
|
|
433
|
+
return result;
|
|
398
434
|
};
|
|
399
435
|
|
|
400
436
|
const decompressCertificates = async (source, dest) => {
|
|
@@ -403,55 +439,29 @@ const decompressCertificates = async (source, dest) => {
|
|
|
403
439
|
return dest;
|
|
404
440
|
};
|
|
405
441
|
|
|
406
|
-
|
|
442
|
+
const joinCertDownUrl = (baseUrl, name) => joinUrl(baseUrl, '/certs', name);
|
|
443
|
+
|
|
444
|
+
const getIpEchoCertDownloadUrl = (baseUrl) => joinCertDownUrl(baseUrl, 'ip-abtnet-io.tar.gz');
|
|
445
|
+
const getDidDomainCertDownloadUrl = (baseUrl) => joinCertDownUrl(baseUrl, 'did-abtnet-io.tar.gz');
|
|
446
|
+
const getDownloadCertBaseUrl = (info) =>
|
|
447
|
+
process.env.ABT_NODE_WILDCARD_CERT_HOST || get(info, 'routing.wildcardCertHost', '');
|
|
448
|
+
|
|
449
|
+
module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager }) {
|
|
407
450
|
const nodeState = states.node;
|
|
408
451
|
const blockletState = states.blocklet;
|
|
409
452
|
const siteState = states.site;
|
|
410
|
-
const httpsCertState = states.certificate;
|
|
411
453
|
const notification = states.notification;
|
|
412
454
|
|
|
413
455
|
// site level duplication detection
|
|
414
456
|
const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
|
|
415
457
|
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const provider = getProviderFromNodeInfo(info);
|
|
419
|
-
if (provider === ROUTER_PROVIDER_NONE) {
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const https = get(info, 'routing.https', true);
|
|
424
|
-
const dashboardDomain = get(info, 'routing.dashboardDomain', '');
|
|
425
|
-
const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
|
|
426
|
-
if (!https || !dashboardDomain || !certDownloadAddress) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (checkExpire) {
|
|
431
|
-
try {
|
|
432
|
-
const cert = await routerManager.findCertificateByDomain(dashboardDomain);
|
|
433
|
-
if (!cert) {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const now = Date.now();
|
|
438
|
-
const certInfo = getHttpsCertInfo(cert.certificate);
|
|
439
|
-
if (certInfo.validTo - now >= CERTIFICATE_EXPIRES_OFFSET) {
|
|
440
|
-
logger.info('skip dashboard certificate update before not expired');
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
} catch (err) {
|
|
444
|
-
logger.error('failed to check dashboard certificate expiration', { error: err });
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const destFolder = getTmpDirectory(path.join(`certificate-${Date.now()}`));
|
|
458
|
+
const downloadCert = async ({ domain, url }) => {
|
|
459
|
+
const destFolder = getTmpDir(path.join(`certificate-${Date.now()}`));
|
|
450
460
|
try {
|
|
451
461
|
const filename = path.join(destFolder, 'certificate.tar.gz');
|
|
452
462
|
fs.ensureDirSync(destFolder);
|
|
453
463
|
|
|
454
|
-
await downloadFile(
|
|
464
|
+
await downloadFile(url, filename);
|
|
455
465
|
await decompressCertificates(filename, destFolder);
|
|
456
466
|
|
|
457
467
|
const certificateFilePath = path.join(destFolder, 'cert.pem');
|
|
@@ -468,58 +478,186 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
468
478
|
const certificate = fs.readFileSync(certificateFilePath).toString();
|
|
469
479
|
const privateKey = fs.readFileSync(privateKeyFilePath).toString();
|
|
470
480
|
|
|
471
|
-
await
|
|
472
|
-
|
|
481
|
+
await certManager.upsertByDomain({
|
|
482
|
+
domain,
|
|
483
|
+
privateKey,
|
|
484
|
+
certificate,
|
|
485
|
+
isProtected: true,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
logger.info('dashboard certificate updated', { domain });
|
|
473
489
|
} catch (error) {
|
|
474
|
-
logger.error('
|
|
490
|
+
logger.error('update dashboard certificate failed', { error });
|
|
491
|
+
throw error;
|
|
475
492
|
} finally {
|
|
476
493
|
fs.removeSync(destFolder);
|
|
477
494
|
}
|
|
478
495
|
};
|
|
479
496
|
|
|
497
|
+
const updateCert = async (domain, url) => {
|
|
498
|
+
try {
|
|
499
|
+
const cert = await certManager.getByDomain(domain);
|
|
500
|
+
if (!cert) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const now = Date.now();
|
|
505
|
+
const certInfo = getHttpsCertInfo(cert.certificate);
|
|
506
|
+
|
|
507
|
+
if (certInfo.validTo - now >= CERTIFICATE_EXPIRES_OFFSET) {
|
|
508
|
+
logger.info('skip dashboard certificate update before not expired', { domain, url });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
await downloadCert({
|
|
513
|
+
domain,
|
|
514
|
+
url,
|
|
515
|
+
});
|
|
516
|
+
} catch (err) {
|
|
517
|
+
logger.error('failed to check dashboard certificate expiration', { error: err, domain, url });
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const updateDashboardCertificates = async () => {
|
|
522
|
+
const info = await nodeState.read();
|
|
523
|
+
const https = get(info, 'routing.https', true);
|
|
524
|
+
const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
|
|
525
|
+
const didDomain = info.didDomain;
|
|
526
|
+
const certDownloadAddress = getDownloadCertBaseUrl(info);
|
|
527
|
+
if (!https || !certDownloadAddress) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
await updateCert(ipWildcardDomain, getIpEchoCertDownloadUrl(certDownloadAddress));
|
|
532
|
+
await updateCert(`*.${didDomain}`, getDidDomainCertDownloadUrl(certDownloadAddress));
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const ensureWildcardCerts = async () => {
|
|
536
|
+
const info = await nodeState.read();
|
|
537
|
+
const didDomain = info.didDomain;
|
|
538
|
+
const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
|
|
539
|
+
|
|
540
|
+
const ensureDomainCert = async (domain, url) => {
|
|
541
|
+
const cert = await certManager.getByDomain(domain);
|
|
542
|
+
if (!cert || get(cert, 'meta.validTo') <= Date.now()) {
|
|
543
|
+
await downloadCert({
|
|
544
|
+
domain,
|
|
545
|
+
url,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const certBaseUrl = getDownloadCertBaseUrl(info);
|
|
551
|
+
await Promise.all([
|
|
552
|
+
ensureDomainCert(ipWildcardDomain, getIpEchoCertDownloadUrl(certBaseUrl)),
|
|
553
|
+
ensureDomainCert(`*.${didDomain}`, getDidDomainCertDownloadUrl(certBaseUrl)),
|
|
554
|
+
]);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const upsertSiteRule = async ({ site, rule }, context) => {
|
|
558
|
+
const findExistingRule = (prefix) => site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(prefix));
|
|
559
|
+
|
|
560
|
+
const newSiteRule = {
|
|
561
|
+
id: site.id,
|
|
562
|
+
rule,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const existingRule = findExistingRule(get(rule, 'from.pathPrefix'));
|
|
566
|
+
if (!existingRule) {
|
|
567
|
+
await routerManager.addRoutingRule(newSiteRule, context);
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (!isEqual(existingRule.to, rule.to)) {
|
|
572
|
+
newSiteRule.rule.id = existingRule.id;
|
|
573
|
+
newSiteRule.skipProtectedRuleChecking = true;
|
|
574
|
+
await routerManager.updateRoutingRule(newSiteRule, context);
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return false;
|
|
579
|
+
};
|
|
580
|
+
|
|
480
581
|
const addWellknownSite = async (sites, context) => {
|
|
481
582
|
const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
|
|
482
583
|
|
|
483
584
|
try {
|
|
484
|
-
const
|
|
585
|
+
const info = await nodeState.read();
|
|
586
|
+
const proxyTarget = {
|
|
587
|
+
port: info.port,
|
|
588
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
589
|
+
interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
|
|
590
|
+
};
|
|
591
|
+
|
|
485
592
|
const didResolverWellknownRule = {
|
|
486
|
-
|
|
593
|
+
isProtected: true,
|
|
594
|
+
from: { pathPrefix: WELLKNOWN_DID_RESOLVER_PREFIX },
|
|
595
|
+
to: proxyTarget,
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const acmeChallengeWellknownRule = {
|
|
599
|
+
isProtected: true,
|
|
600
|
+
from: { pathPrefix: WELLKNOWN_ACME_CHALLENGE_PREFIX },
|
|
601
|
+
to: proxyTarget,
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const pingWellknownRule = {
|
|
605
|
+
isProtected: true,
|
|
606
|
+
from: { pathPrefix: WELLKNOWN_PING_PREFIX },
|
|
487
607
|
to: {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
608
|
+
type: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
|
|
609
|
+
response: {
|
|
610
|
+
status: 200,
|
|
611
|
+
contentType: 'application/javascript',
|
|
612
|
+
body: "'pong'",
|
|
613
|
+
},
|
|
614
|
+
port: info.port,
|
|
491
615
|
},
|
|
492
616
|
};
|
|
493
617
|
|
|
494
618
|
if (site) {
|
|
495
|
-
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
await routerManager.addRoutingRule(
|
|
619
|
+
const didResolverRuleUpdateRes = await upsertSiteRule(
|
|
500
620
|
{
|
|
501
|
-
|
|
621
|
+
site,
|
|
502
622
|
rule: didResolverWellknownRule,
|
|
503
623
|
},
|
|
504
624
|
context
|
|
505
625
|
);
|
|
506
|
-
|
|
507
|
-
await
|
|
626
|
+
|
|
627
|
+
const acmeRuleUpdateRes = await upsertSiteRule(
|
|
508
628
|
{
|
|
509
|
-
site
|
|
510
|
-
|
|
511
|
-
port: await getWellknownSitePort(),
|
|
512
|
-
name: NAME_FOR_WELLKNOWN_SITE,
|
|
513
|
-
rules: [didResolverWellknownRule],
|
|
514
|
-
isProtected: true,
|
|
515
|
-
},
|
|
516
|
-
skipCheckDynamicBlacklist: true,
|
|
517
|
-
skipValidation: true,
|
|
629
|
+
site,
|
|
630
|
+
rule: acmeChallengeWellknownRule,
|
|
518
631
|
},
|
|
519
632
|
context
|
|
520
633
|
);
|
|
634
|
+
|
|
635
|
+
const pingRuleRes = await upsertSiteRule(
|
|
636
|
+
{
|
|
637
|
+
site,
|
|
638
|
+
rule: pingWellknownRule,
|
|
639
|
+
},
|
|
640
|
+
context
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
return didResolverRuleUpdateRes || acmeRuleUpdateRes || pingRuleRes;
|
|
521
644
|
}
|
|
522
645
|
|
|
646
|
+
await routerManager.addRoutingSite(
|
|
647
|
+
{
|
|
648
|
+
site: {
|
|
649
|
+
domain: DOMAIN_FOR_INTERNAL_SITE,
|
|
650
|
+
port: await getWellknownSitePort(),
|
|
651
|
+
name: NAME_FOR_WELLKNOWN_SITE,
|
|
652
|
+
rules: [didResolverWellknownRule, acmeChallengeWellknownRule, pingWellknownRule],
|
|
653
|
+
isProtected: true,
|
|
654
|
+
},
|
|
655
|
+
skipCheckDynamicBlacklist: true,
|
|
656
|
+
skipValidation: true,
|
|
657
|
+
},
|
|
658
|
+
context
|
|
659
|
+
);
|
|
660
|
+
|
|
523
661
|
return true;
|
|
524
662
|
} catch (err) {
|
|
525
663
|
console.error('add well-known site failed:', err);
|
|
@@ -533,14 +671,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
533
671
|
*
|
|
534
672
|
* @returns {boolean} if routing changed
|
|
535
673
|
*/
|
|
536
|
-
const ensureDashboardRouting = async (context = {}
|
|
674
|
+
const ensureDashboardRouting = async (context = {}) => {
|
|
537
675
|
const info = await nodeState.read();
|
|
538
|
-
|
|
539
|
-
const provider = getProviderFromNodeInfo(info);
|
|
540
|
-
if (provider === ROUTER_PROVIDER_NONE) {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
676
|
const sites = await siteState.getSites();
|
|
545
677
|
let dashboardSite = (sites || []).find((x) => x.domain === DOMAIN_FOR_IP_SITE);
|
|
546
678
|
const updatedResult = [];
|
|
@@ -569,28 +701,26 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
569
701
|
}
|
|
570
702
|
}
|
|
571
703
|
|
|
572
|
-
const
|
|
573
|
-
(dashboardSite.domainAliases || []).find((item) => {
|
|
574
|
-
if (typeof item === 'object') {
|
|
575
|
-
return domainAlias === item.value;
|
|
576
|
-
}
|
|
704
|
+
const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
|
|
577
705
|
|
|
578
|
-
|
|
579
|
-
|
|
706
|
+
const domainAliases = (dashboardSite.domainAliases || []).filter(
|
|
707
|
+
(item) => !item.value.endsWith(ipWildcardDomain) && !item.value.endsWith(info.didDomain)
|
|
708
|
+
);
|
|
580
709
|
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
);
|
|
710
|
+
const didDomain = getServerDidDomain(info);
|
|
711
|
+
const dashboardAliasDomains = [
|
|
712
|
+
{ value: ipWildcardDomain, isProtected: true },
|
|
713
|
+
{ value: didDomain, isProtected: true },
|
|
714
|
+
];
|
|
715
|
+
domainAliases.push(...dashboardAliasDomains);
|
|
588
716
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
717
|
+
try {
|
|
718
|
+
const result = await siteState.update({ _id: dashboardSite.id }, { $set: { domainAliases } });
|
|
719
|
+
|
|
720
|
+
updatedResult.push(result);
|
|
721
|
+
} catch (error) {
|
|
722
|
+
logger.error('add dashboard domain rule failed', { error });
|
|
723
|
+
console.error('Add dashboard domain rule failed:', error);
|
|
594
724
|
}
|
|
595
725
|
|
|
596
726
|
const defaultRule = sites.find((x) => x.domain === DOMAIN_FOR_DEFAULT_SITE);
|
|
@@ -618,18 +748,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
618
748
|
updatedResult.push(wellknownRes);
|
|
619
749
|
}
|
|
620
750
|
|
|
621
|
-
// Download dashboard certificates if not exists
|
|
622
|
-
const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
|
|
623
|
-
if (dashboardDomain && certDownloadAddress) {
|
|
624
|
-
const cert = await routerManager.findCertificateByDomain(dashboardDomain);
|
|
625
|
-
if (!cert) {
|
|
626
|
-
await updateDashboardCertificate({ checkExpire: false });
|
|
627
|
-
if (typeof output === 'function') {
|
|
628
|
-
output('Dashboard HTTPS certificate was downloaded successfully!');
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
751
|
if (updatedResult.length) {
|
|
634
752
|
const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
|
|
635
753
|
logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
|
|
@@ -657,8 +775,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
657
775
|
return `/${str}`.replace(/^\/+/, '/');
|
|
658
776
|
};
|
|
659
777
|
|
|
660
|
-
const domainGroup =
|
|
661
|
-
|
|
778
|
+
const domainGroup = getBlockletDomainGroupName(blocklet.meta.did);
|
|
779
|
+
|
|
662
780
|
const pathPrefix = getPrefix(webInterface.prefix);
|
|
663
781
|
const rule = {
|
|
664
782
|
from: { pathPrefix },
|
|
@@ -672,20 +790,45 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
672
790
|
};
|
|
673
791
|
|
|
674
792
|
const existSite = await states.site.findOne({ domain: domainGroup });
|
|
793
|
+
const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
794
|
+
updateBlockletDocument({
|
|
795
|
+
wallet,
|
|
796
|
+
appPid: blocklet.appPid,
|
|
797
|
+
alsoKnownAs: getBlockletKnownAs(blocklet),
|
|
798
|
+
daemonDidDomain: getServerDidDomain(nodeInfo),
|
|
799
|
+
didRegistryUrl: nodeInfo.didRegistry,
|
|
800
|
+
domain: nodeInfo.didDomain,
|
|
801
|
+
})
|
|
802
|
+
.then(() => {
|
|
803
|
+
logger.info(`updated blocklet ${blocklet.appDid} dns`);
|
|
804
|
+
})
|
|
805
|
+
.catch((err) => {
|
|
806
|
+
logger.error(`update blocklet ${blocklet.appDid} dns failed`, { error: err });
|
|
807
|
+
});
|
|
808
|
+
|
|
675
809
|
if (!existSite) {
|
|
810
|
+
const domainAliases = [];
|
|
811
|
+
|
|
812
|
+
const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
|
|
813
|
+
const ipEchoDnsDomain = getIpDnsDomainForBlocklet(blocklet);
|
|
814
|
+
|
|
815
|
+
// let didDomain in front of ipEchoDnsDomain
|
|
816
|
+
domainAliases.push({ value: didDomain, isProtected: true }, { value: ipEchoDnsDomain, isProtected: true });
|
|
817
|
+
|
|
676
818
|
await routerManager.addRoutingSite(
|
|
677
819
|
{
|
|
678
820
|
site: {
|
|
679
821
|
domain: domainGroup,
|
|
680
|
-
domainAliases
|
|
822
|
+
domainAliases,
|
|
681
823
|
isProtected: true,
|
|
682
824
|
rules: [rule],
|
|
683
825
|
},
|
|
684
826
|
skipCheckDynamicBlacklist: true,
|
|
827
|
+
skipValidation: true,
|
|
685
828
|
},
|
|
686
829
|
context
|
|
687
830
|
);
|
|
688
|
-
logger.info('add routing site', { site:
|
|
831
|
+
logger.info('add routing site', { site: domainGroup });
|
|
689
832
|
|
|
690
833
|
return true;
|
|
691
834
|
}
|
|
@@ -700,13 +843,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
700
843
|
},
|
|
701
844
|
skipProtectedRuleChecking: true,
|
|
702
845
|
});
|
|
703
|
-
logger.info('update routing rule for site', { site:
|
|
846
|
+
logger.info('update routing rule for site', { site: domainGroup });
|
|
704
847
|
} else {
|
|
705
848
|
await routerManager.addRoutingRule({
|
|
706
849
|
id: existSite.id,
|
|
707
850
|
rule,
|
|
708
851
|
});
|
|
709
|
-
logger.info('add routing rule for site', { site:
|
|
852
|
+
logger.info('add routing rule for site', { site: domainGroup });
|
|
710
853
|
}
|
|
711
854
|
|
|
712
855
|
return true;
|
|
@@ -723,130 +866,79 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
723
866
|
return false;
|
|
724
867
|
}
|
|
725
868
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
869
|
+
/**
|
|
870
|
+
* 1. component blocklet 不允许有相同多的 wellknown
|
|
871
|
+
* 1. wellknown 可以访问的路由:
|
|
872
|
+
* /.well-known/xxx
|
|
873
|
+
* /{wellknown owner blocklet}/.well-known/xxx
|
|
874
|
+
*/
|
|
733
875
|
|
|
734
|
-
|
|
735
|
-
const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
|
|
876
|
+
const isWellknownInterface = (x) => x.type === BLOCKLET_INTERFACE_TYPE_WELLKNOWN;
|
|
736
877
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
743
|
-
interfaceName: x.name,
|
|
744
|
-
},
|
|
745
|
-
isProtected: true,
|
|
746
|
-
};
|
|
878
|
+
const handler = async (tmpBlocklet, tmpInterface, mountPoint) => {
|
|
879
|
+
let pathPrefix = normalizePathPrefix(tmpInterface.prefix);
|
|
880
|
+
if (!pathPrefix.startsWith(WELLKNOWN_PATH_PREFIX)) {
|
|
881
|
+
throw new Error(`Wellknown path prefix must start with: ${WELLKNOWN_PATH_PREFIX}`);
|
|
882
|
+
}
|
|
747
883
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
884
|
+
const tmpMountPoint = mountPoint || tmpBlocklet.mountPoint;
|
|
885
|
+
if (tmpMountPoint) {
|
|
886
|
+
pathPrefix = joinUrl(tmpMountPoint, pathPrefix);
|
|
887
|
+
}
|
|
752
888
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
existedRule.to = rule.to;
|
|
889
|
+
const port = findInterfacePortByName(tmpBlocklet, tmpInterface.name);
|
|
890
|
+
const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
|
|
756
891
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
892
|
+
const rule = {
|
|
893
|
+
from: { pathPrefix },
|
|
894
|
+
to: {
|
|
895
|
+
did: tmpBlocklet.meta.did,
|
|
896
|
+
port,
|
|
897
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
898
|
+
interfaceName: tmpInterface.name,
|
|
899
|
+
},
|
|
900
|
+
isProtected: true,
|
|
901
|
+
};
|
|
761
902
|
|
|
762
|
-
|
|
763
|
-
}
|
|
903
|
+
if (!existedRule) {
|
|
904
|
+
await routerManager.addRoutingRule({ id: wellknownSite.id, rule, skipCheckDynamicBlacklist: true }, context);
|
|
905
|
+
return true;
|
|
906
|
+
}
|
|
764
907
|
|
|
765
|
-
|
|
766
|
-
|
|
908
|
+
// 兼容代码,旧的 rule 没有 to.did 字段
|
|
909
|
+
if (port !== existedRule.to.port || existedRule.to.did !== tmpBlocklet.meta.did) {
|
|
910
|
+
existedRule.to = rule.to;
|
|
767
911
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
912
|
+
await routerManager.updateRoutingRule(
|
|
913
|
+
{ id: wellknownSite.id, rule: existedRule, skipProtectedRuleChecking: true },
|
|
914
|
+
context
|
|
915
|
+
);
|
|
771
916
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
*
|
|
775
|
-
* @returns {boolean} if routing changed
|
|
776
|
-
*/
|
|
777
|
-
const _ensureBlockletRulesForDashboardSite = async (blocklet, sites, nodeInfo, context = {}) => {
|
|
778
|
-
// blocklet level duplication detection
|
|
779
|
-
const findRulesByInterface = (site, value) =>
|
|
780
|
-
site.rules.filter((x) => x.isProtected && x.to.did === blocklet.meta.did && get(x, 'to.interfaceName') === value);
|
|
781
|
-
|
|
782
|
-
const ipSite = sites.find((x) => x.domain === DOMAIN_FOR_IP_SITE);
|
|
783
|
-
if (!ipSite) {
|
|
784
|
-
logger.warn(`Routing rule for dashboard not found, abort on ensure rules for blocklet ${blocklet.meta.did}`);
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
787
919
|
|
|
788
|
-
// If we have rule for legacy path prefix, just return
|
|
789
|
-
const pathPrefixLegacy = normalizePathPrefix(`/${nodeInfo.routing.adminPath}/${blocklet.meta.name}`);
|
|
790
|
-
if (hasRuleByPrefix(ipSite, pathPrefixLegacy)) {
|
|
791
920
|
return false;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const removedRuleIds = [];
|
|
795
|
-
const changes = await Promise.all(
|
|
796
|
-
blocklet.meta.interfaces.map(async (x) => {
|
|
797
|
-
let changed = false; // if state db was mutated
|
|
798
|
-
let pathPrefix = '';
|
|
799
|
-
if (x.prefix === BLOCKLET_DYNAMIC_PATH_PREFIX) {
|
|
800
|
-
// pathPrefix for dynamic path: `/{adminPath}/{blockletName}/{interfaceName}`
|
|
801
|
-
pathPrefix = normalizePathPrefix(`/${pathPrefixLegacy}/${x.name}`);
|
|
802
|
-
} else {
|
|
803
|
-
// pathPrefix for fixed path: `/{interfacePrefix}`
|
|
804
|
-
pathPrefix = normalizePathPrefix(x.prefix);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Remove if we have rule with same interface
|
|
808
|
-
const exists = findRulesByInterface(ipSite, x.name);
|
|
809
|
-
if (exists.length) {
|
|
810
|
-
// Note: we need to keep a list of removed rules since we might remove multiple rules in the loop
|
|
811
|
-
exists.forEach((e) => removedRuleIds.push(e.id));
|
|
812
|
-
await states.site.update(
|
|
813
|
-
{ _id: ipSite.id },
|
|
814
|
-
{ $set: { rules: ipSite.rules.filter((r) => removedRuleIds.includes(r.id) === false) } }
|
|
815
|
-
);
|
|
816
|
-
changed = true;
|
|
817
|
-
} else if (hasRuleByPrefix(ipSite, pathPrefix)) {
|
|
818
|
-
// Skip same pathPrefix rules
|
|
819
|
-
return changed;
|
|
820
|
-
}
|
|
921
|
+
};
|
|
821
922
|
|
|
822
|
-
|
|
823
|
-
if (x.path === '/' && x.prefix === '/') {
|
|
824
|
-
return changed;
|
|
825
|
-
}
|
|
923
|
+
const tasks = [];
|
|
826
924
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
type: ROUTING_RULE_TYPES.BLOCKLET,
|
|
834
|
-
port: findInterfacePortByName(blocklet, x.name),
|
|
835
|
-
did: blocklet.meta.did, // root blocklet did
|
|
836
|
-
interfaceName: x.name, // root blocklet interface
|
|
837
|
-
},
|
|
838
|
-
isProtected: true,
|
|
839
|
-
},
|
|
840
|
-
skipCheckDynamicBlacklist: true,
|
|
841
|
-
},
|
|
842
|
-
context
|
|
843
|
-
);
|
|
844
|
-
changed = true;
|
|
925
|
+
forEachBlocklet(
|
|
926
|
+
blocklet,
|
|
927
|
+
(b) => {
|
|
928
|
+
(b.meta.interfaces || []).forEach((item) => {
|
|
929
|
+
if (isWellknownInterface(item)) {
|
|
930
|
+
tasks.push(handler(b, item));
|
|
845
931
|
|
|
846
|
-
|
|
847
|
-
|
|
932
|
+
if (!b.mountPoint || b.mountPoint !== '/') {
|
|
933
|
+
tasks.push(handler(b, item, '/')); // 在站点的根路由下挂载一个
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
},
|
|
938
|
+
{ sync: true }
|
|
848
939
|
);
|
|
849
940
|
|
|
941
|
+
const changes = await Promise.all(tasks);
|
|
850
942
|
return changes.some(Boolean);
|
|
851
943
|
};
|
|
852
944
|
|
|
@@ -873,12 +965,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
873
965
|
*/
|
|
874
966
|
const ensureBlockletRouting = async (blocklet, context = {}) => {
|
|
875
967
|
const nodeInfo = await nodeState.read();
|
|
876
|
-
|
|
877
|
-
const provider = getProviderFromNodeInfo(nodeInfo);
|
|
878
|
-
if (provider === ROUTER_PROVIDER_NONE) {
|
|
879
|
-
return false;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
968
|
const hasWebInterface = (blocklet.meta.interfaces || []).some((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
|
|
883
969
|
if (!hasWebInterface) {
|
|
884
970
|
return false;
|
|
@@ -891,14 +977,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
891
977
|
_ensureBlockletSites(blocklet, sites, nodeInfo, context),
|
|
892
978
|
];
|
|
893
979
|
|
|
894
|
-
if (nodeInfo.mode === NODE_MODES.DEBUG || ['e2e', 'test'].includes(process.env.NODE_ENV)) {
|
|
895
|
-
logger.info('Add blocklet endpoint in dashboard site', {
|
|
896
|
-
nodeMode: nodeInfo.mode,
|
|
897
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
898
|
-
});
|
|
899
|
-
tasks.push(_ensureBlockletRulesForDashboardSite(blocklet, sites, nodeInfo, context));
|
|
900
|
-
}
|
|
901
|
-
|
|
902
980
|
const changes = await Promise.all(tasks);
|
|
903
981
|
|
|
904
982
|
return changes.some(Boolean);
|
|
@@ -978,12 +1056,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
978
1056
|
* @returns {boolean} if routing changed
|
|
979
1057
|
*/
|
|
980
1058
|
const ensureBlockletRoutingForUpgrade = async (blocklet, context = {}) => {
|
|
981
|
-
const nodeInfo = await nodeState.read();
|
|
982
|
-
const provider = getProviderFromNodeInfo(nodeInfo);
|
|
983
|
-
if (provider === ROUTER_PROVIDER_NONE) {
|
|
984
|
-
return false;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
1059
|
await routerManager.deleteRoutingRulesItemByDid(
|
|
988
1060
|
{ did: blocklet.meta.did, ruleFilter: (x) => x.isProtected },
|
|
989
1061
|
context
|
|
@@ -1004,7 +1076,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1004
1076
|
const nodeInfo = await nodeState.read();
|
|
1005
1077
|
|
|
1006
1078
|
const ruleChanged = await routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
|
|
1007
|
-
|
|
1079
|
+
let siteChanged;
|
|
1080
|
+
if (!context.keepRouting) {
|
|
1081
|
+
siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
|
|
1082
|
+
}
|
|
1008
1083
|
|
|
1009
1084
|
return ruleChanged || siteChanged;
|
|
1010
1085
|
};
|
|
@@ -1028,21 +1103,21 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1028
1103
|
return result;
|
|
1029
1104
|
}
|
|
1030
1105
|
|
|
1031
|
-
async function
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1106
|
+
async function resetSiteByDid(did, { refreshRouterProvider = true } = {}) {
|
|
1107
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
1108
|
+
await removeBlockletRouting(blocklet);
|
|
1109
|
+
await ensureBlockletRouting(blocklet);
|
|
1110
|
+
if (refreshRouterProvider) {
|
|
1111
|
+
const hash = await takeRoutingSnapshot({ message: `Reset blocklet ${did}`, dryRun: false });
|
|
1112
|
+
logger.info('reset blocklet routing rules', { did, hash });
|
|
1113
|
+
}
|
|
1036
1114
|
}
|
|
1037
1115
|
|
|
1038
|
-
const
|
|
1116
|
+
const providers = {}; // we need to keep reference for different router instances
|
|
1039
1117
|
const handleRouting = async (nodeInfo) => {
|
|
1040
1118
|
const providerName = get(nodeInfo, 'routing.provider', null);
|
|
1041
1119
|
const httpsEnabled = get(nodeInfo, 'routing.https', true);
|
|
1042
1120
|
logger.debug('handle routing', { providerName, httpsEnabled });
|
|
1043
|
-
if (providerName === ROUTER_PROVIDER_NONE) {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
1121
|
|
|
1047
1122
|
const Provider = getProvider(providerName);
|
|
1048
1123
|
const checkResult = await Provider.check({ configDir: dataDirs.router });
|
|
@@ -1050,27 +1125,25 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1050
1125
|
throw new Error(`${providerName} pre-check failed, ${checkResult.error}`);
|
|
1051
1126
|
}
|
|
1052
1127
|
|
|
1053
|
-
if (
|
|
1054
|
-
await
|
|
1128
|
+
if (providers[providerName]) {
|
|
1129
|
+
await providers[providerName].reload();
|
|
1055
1130
|
} else {
|
|
1056
|
-
|
|
1131
|
+
providers[providerName] = new Router({
|
|
1057
1132
|
provider: new Provider({
|
|
1058
|
-
|
|
1133
|
+
configDir: path.join(dataDirs.router, providerName),
|
|
1059
1134
|
httpPort: nodeInfo.routing.httpPort || DEFAULT_HTTP_PORT,
|
|
1060
1135
|
httpsPort: nodeInfo.routing.httpsPort || DEFAULT_HTTPS_PORT,
|
|
1061
|
-
|
|
1136
|
+
cacheEnabled: isGatewayCacheEnabled(nodeInfo),
|
|
1062
1137
|
}),
|
|
1063
1138
|
getRoutingParams: async () => {
|
|
1064
1139
|
try {
|
|
1065
1140
|
const info = await nodeState.read();
|
|
1066
1141
|
let { sites } = await readRoutingSites();
|
|
1067
1142
|
sites = await ensureLatestInfo(sites);
|
|
1068
|
-
sites = await ensureAuthService(sites, blockletManager);
|
|
1069
1143
|
sites = await ensureServiceRule(sites);
|
|
1144
|
+
sites = await ensureRootRule(sites);
|
|
1070
1145
|
|
|
1071
|
-
const certificates = httpsEnabled
|
|
1072
|
-
? await httpsCertState.find({ type: providerName }, { domain: 1, certificate: 1, privateKey: 1 })
|
|
1073
|
-
: [];
|
|
1146
|
+
const certificates = httpsEnabled ? await certManager.getAllNormal() : [];
|
|
1074
1147
|
|
|
1075
1148
|
return {
|
|
1076
1149
|
sites,
|
|
@@ -1093,11 +1166,17 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1093
1166
|
},
|
|
1094
1167
|
});
|
|
1095
1168
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1169
|
+
[
|
|
1170
|
+
BlockletEvents.certIssued,
|
|
1171
|
+
EVENTS.CERT_ADDED,
|
|
1172
|
+
EVENTS.CERT_REMOVED,
|
|
1173
|
+
EVENTS.CERT_ISSUED,
|
|
1174
|
+
EVENTS.CERT_UPDATED,
|
|
1175
|
+
].forEach((event) => {
|
|
1176
|
+
certManager.on(event, () => providers[providerName].reload());
|
|
1177
|
+
});
|
|
1099
1178
|
|
|
1100
|
-
await
|
|
1179
|
+
await providers[providerName].start();
|
|
1101
1180
|
}
|
|
1102
1181
|
};
|
|
1103
1182
|
|
|
@@ -1105,8 +1184,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1105
1184
|
const info = await nodeState.read();
|
|
1106
1185
|
const providerName = get(info, 'routing.provider', null);
|
|
1107
1186
|
|
|
1108
|
-
if (providerName &&
|
|
1109
|
-
await
|
|
1187
|
+
if (providerName && providers[providerName] && typeof providers[providerName].rotateLogs === 'function') {
|
|
1188
|
+
await providers[providerName].rotateLogs({ retain: LOG_RETAIN_IN_DAYS });
|
|
1110
1189
|
}
|
|
1111
1190
|
};
|
|
1112
1191
|
|
|
@@ -1135,33 +1214,31 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1135
1214
|
await handleRouting(result);
|
|
1136
1215
|
|
|
1137
1216
|
if (newProvider !== info.routing.provider) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
logger.info(`take routing snapshot on switch engine: ${newProvider}`, { ensureBlockletResults });
|
|
1154
|
-
}
|
|
1217
|
+
// Ensure we have system sites for daemon
|
|
1218
|
+
await ensureDashboardRouting(context);
|
|
1219
|
+
|
|
1220
|
+
// Ensure we have system rules for blocklets
|
|
1221
|
+
const blocklets = await blockletState.getBlocklets();
|
|
1222
|
+
const ensureBlocklet = async (x) => {
|
|
1223
|
+
const blocklet = await blockletManager.getBlocklet(x.meta.did);
|
|
1224
|
+
return ensureBlockletRouting(blocklet, context);
|
|
1225
|
+
};
|
|
1226
|
+
const ensureBlockletResults = await Promise.all(blocklets.map((x) => ensureBlocklet(x)));
|
|
1227
|
+
|
|
1228
|
+
// We need to take snapshot after system rules ensured
|
|
1229
|
+
if (ensureBlockletResults.filter(Boolean).length) {
|
|
1230
|
+
await takeRoutingSnapshot({ message: `Switch routing engine to ${newProvider}`, dryRun: false }, context);
|
|
1231
|
+
logger.info(`take routing snapshot on switch engine: ${newProvider}`, { ensureBlockletResults });
|
|
1155
1232
|
}
|
|
1156
1233
|
|
|
1157
1234
|
const Provider = getProvider(info.routing.provider);
|
|
1158
1235
|
if (Provider) {
|
|
1159
1236
|
try {
|
|
1160
1237
|
const providerInstance = new Provider({
|
|
1161
|
-
|
|
1238
|
+
configDir: path.join(dataDirs.router, info.routing.provider),
|
|
1162
1239
|
httpPort: info.routing.httpPort || DEFAULT_HTTP_PORT,
|
|
1163
1240
|
httpsPort: info.routing.httpsPort || DEFAULT_HTTPS_PORT,
|
|
1164
|
-
|
|
1241
|
+
cacheEnabled: isGatewayCacheEnabled(info),
|
|
1165
1242
|
});
|
|
1166
1243
|
await providerInstance.stop();
|
|
1167
1244
|
logger.info('original router stopped:', { provider: info.routing.provider });
|
|
@@ -1240,7 +1317,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1240
1317
|
return sites;
|
|
1241
1318
|
}
|
|
1242
1319
|
|
|
1243
|
-
return
|
|
1320
|
+
return attachRuntimeDomainAliases({
|
|
1244
1321
|
sites: await ensureLatestInfo(sites, { withDefaultCors }),
|
|
1245
1322
|
context,
|
|
1246
1323
|
node: nodeState,
|
|
@@ -1249,7 +1326,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1249
1326
|
|
|
1250
1327
|
const getSnapshotSites = async ({ hash }, context = {}, { withDefaultCors = true } = {}) => {
|
|
1251
1328
|
const sites = await routingSnapshot.readSnapshotSites(hash);
|
|
1252
|
-
return
|
|
1329
|
+
return attachRuntimeDomainAliases({
|
|
1253
1330
|
sites: await ensureLatestInfo(sites, { withDefaultCors }),
|
|
1254
1331
|
context,
|
|
1255
1332
|
node: nodeState,
|
|
@@ -1257,65 +1334,56 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1257
1334
|
};
|
|
1258
1335
|
|
|
1259
1336
|
const getCertificates = async () => {
|
|
1260
|
-
const certificates = await
|
|
1337
|
+
const certificates = await certManager.getAll();
|
|
1261
1338
|
const sites = await getSitesFromSnapshot();
|
|
1339
|
+
|
|
1340
|
+
const isMatch = (cert, domain) =>
|
|
1341
|
+
domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
|
|
1342
|
+
|
|
1262
1343
|
return certificates.map((cert) => {
|
|
1263
1344
|
cert.matchedSites = [];
|
|
1264
1345
|
sites.forEach((site) => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1346
|
+
const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
|
|
1347
|
+
domains.forEach((domain) => {
|
|
1348
|
+
if (isMatch(cert, domain)) {
|
|
1349
|
+
cert.matchedSites.push({ id: site.id, domain });
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1272
1352
|
});
|
|
1273
1353
|
|
|
1274
1354
|
return cert;
|
|
1275
1355
|
});
|
|
1276
1356
|
};
|
|
1277
1357
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1358
|
+
/**
|
|
1359
|
+
* proxy to routerManager and do takeRoutingSnapshot
|
|
1360
|
+
*/
|
|
1361
|
+
const _proxyToRouterManager =
|
|
1362
|
+
(fnName) =>
|
|
1363
|
+
async (...args) => {
|
|
1364
|
+
const res = await routerManager[fnName](...args);
|
|
1280
1365
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1366
|
+
takeRoutingSnapshot({ message: fnName, dryRun: false }).catch((error) => {
|
|
1367
|
+
logger.error('failed to takeRoutingSnapshot', { error });
|
|
1368
|
+
});
|
|
1283
1369
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
const certificates = await getCertificates();
|
|
1288
|
-
for (let i = 0; i < certificates.length; i++) {
|
|
1289
|
-
const cert = certificates[i];
|
|
1290
|
-
const alreadyExpired = now >= cert.validTo;
|
|
1291
|
-
const aboutToExpire = cert.validTo - now > 0 && cert.validTo - now < CERTIFICATE_EXPIRES_WARNING_OFFSET;
|
|
1292
|
-
|
|
1293
|
-
if (alreadyExpired) {
|
|
1294
|
-
logger.info('send certificate expire notification', { domain: cert.domain });
|
|
1295
|
-
notification.create({
|
|
1296
|
-
title: 'SSL Certificate Expired',
|
|
1297
|
-
description: `Your SSL certificate for domain ${cert.domain} has expired, please update it in Blocklet Server`,
|
|
1298
|
-
severity: 'error',
|
|
1299
|
-
entityType: 'certificate',
|
|
1300
|
-
entityId: cert._id, // eslint-disable-line no-underscore-dangle
|
|
1301
|
-
});
|
|
1302
|
-
} else if (aboutToExpire) {
|
|
1303
|
-
logger.info('send certificate about-expire notification', { domain: cert.domain });
|
|
1304
|
-
const expireInDays = Math.ceil((cert.validTo - now) / DAY_IN_MS);
|
|
1305
|
-
notification.create({
|
|
1306
|
-
title: 'SSL Certificate Expire Warning',
|
|
1307
|
-
description: `Your SSL certificate for domain ${
|
|
1308
|
-
cert.domain
|
|
1309
|
-
} will expire in ${expireInDays} days (on ${new Date(
|
|
1310
|
-
cert.validTo
|
|
1311
|
-
).toLocaleString()}), please remember to update it in Blocklet Server`,
|
|
1312
|
-
severity: 'warning',
|
|
1313
|
-
entityType: 'certificate',
|
|
1314
|
-
entityId: cert._id, // eslint-disable-line no-underscore-dangle
|
|
1315
|
-
});
|
|
1370
|
+
const { teamDid: did } = args[0] || {};
|
|
1371
|
+
if (did) {
|
|
1372
|
+
routerManager.emit(BlockletEvents.updated, { meta: { did } });
|
|
1316
1373
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1374
|
+
|
|
1375
|
+
return res;
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
const addRoutingSite = _proxyToRouterManager('addRoutingSite');
|
|
1379
|
+
const updateRoutingSite = _proxyToRouterManager('updateRoutingSite');
|
|
1380
|
+
const deleteRoutingSite = _proxyToRouterManager('deleteRoutingSite');
|
|
1381
|
+
const addRoutingRule = _proxyToRouterManager('addRoutingRule');
|
|
1382
|
+
const updateRoutingRule = _proxyToRouterManager('updateRoutingRule');
|
|
1383
|
+
const deleteRoutingRule = _proxyToRouterManager('deleteRoutingRule');
|
|
1384
|
+
// FIXME: should verify domain owner before added
|
|
1385
|
+
const addDomainAlias = _proxyToRouterManager('addDomainAlias');
|
|
1386
|
+
const deleteDomainAlias = _proxyToRouterManager('deleteDomainAlias');
|
|
1319
1387
|
|
|
1320
1388
|
return {
|
|
1321
1389
|
ensureDashboardRouting,
|
|
@@ -1323,39 +1391,47 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1323
1391
|
ensureBlockletRoutingForUpgrade,
|
|
1324
1392
|
removeBlockletRouting,
|
|
1325
1393
|
handleRouting,
|
|
1326
|
-
|
|
1394
|
+
resetSiteByDid,
|
|
1327
1395
|
updateNodeRouting,
|
|
1328
1396
|
takeRoutingSnapshot,
|
|
1329
1397
|
getRoutingSites,
|
|
1330
1398
|
getSnapshotSites,
|
|
1331
|
-
getCertificates,
|
|
1332
1399
|
getSitesFromSnapshot,
|
|
1333
|
-
|
|
1400
|
+
getCertificates,
|
|
1401
|
+
ensureWildcardCerts,
|
|
1402
|
+
addWellknownSite,
|
|
1403
|
+
upsertSiteRule,
|
|
1404
|
+
getRouterProvider: (name) => providers[name],
|
|
1334
1405
|
|
|
1335
1406
|
getRoutingCrons: () => [
|
|
1336
1407
|
{
|
|
1337
1408
|
name: 'update-dashboard-certificate',
|
|
1338
1409
|
time: '0 1 */6 * * *', // refetch on 0:00, 6:00, etc.
|
|
1339
|
-
fn: () =>
|
|
1410
|
+
fn: () => updateDashboardCertificates(),
|
|
1340
1411
|
options: { runOnInit: true },
|
|
1341
1412
|
},
|
|
1342
|
-
{
|
|
1343
|
-
name: 'check-certificate-expiration',
|
|
1344
|
-
time: '0 0 9 * * *', // check on 09:00 every day
|
|
1345
|
-
fn: checkCertificatesExpiration,
|
|
1346
|
-
options: { runOnInit: false },
|
|
1347
|
-
},
|
|
1348
1413
|
{
|
|
1349
1414
|
name: 'rotate-log-files',
|
|
1350
|
-
time: '
|
|
1415
|
+
time: '1 0 0 * * *', // rotate at 00:00:01 every day
|
|
1351
1416
|
fn: rotateRouterLog,
|
|
1352
1417
|
options: { runOnInit: false },
|
|
1353
1418
|
},
|
|
1354
1419
|
],
|
|
1420
|
+
|
|
1421
|
+
addRoutingSite,
|
|
1422
|
+
deleteRoutingSite,
|
|
1423
|
+
updateRoutingSite,
|
|
1424
|
+
addRoutingRule,
|
|
1425
|
+
updateRoutingRule,
|
|
1426
|
+
deleteRoutingRule,
|
|
1427
|
+
addDomainAlias,
|
|
1428
|
+
deleteDomainAlias,
|
|
1429
|
+
|
|
1430
|
+
isGatewayCacheEnabled,
|
|
1355
1431
|
};
|
|
1356
1432
|
};
|
|
1357
1433
|
|
|
1358
|
-
module.exports.
|
|
1434
|
+
module.exports.attachRuntimeDomainAliases = attachRuntimeDomainAliases;
|
|
1359
1435
|
module.exports.ensureLatestNodeInfo = ensureLatestNodeInfo;
|
|
1360
1436
|
module.exports.ensureLatestInterfaceInfo = ensureLatestInterfaceInfo;
|
|
1361
1437
|
module.exports.ensureLatestInfo = ensureLatestInfo;
|