@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035
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 +1679 -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 +572 -497
- 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 +194 -138
- package/lib/states/blocklet.js +361 -104
- 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
|
}
|
|
@@ -263,9 +211,15 @@ const ensureLatestInterfaceInfo = async (sites = []) => {
|
|
|
263
211
|
}
|
|
264
212
|
|
|
265
213
|
site.rules = site.rules.map((rule) => {
|
|
266
|
-
|
|
214
|
+
if (rule.dynamic) {
|
|
215
|
+
return rule;
|
|
216
|
+
}
|
|
217
|
+
// If a rule already has a target and target is WELLKNOWN_SERVICE_PATH_PREFIX, just return
|
|
267
218
|
// Indicates that the rule id generated by the system for auth service
|
|
268
|
-
if (rule.isProtected && rule.to.target ===
|
|
219
|
+
if (rule.isProtected && rule.to.target === WELLKNOWN_SERVICE_PATH_PREFIX) {
|
|
220
|
+
return rule;
|
|
221
|
+
}
|
|
222
|
+
if (rule.isProtected && rule.to.target === joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, USER_AVATAR_PATH_PREFIX)) {
|
|
269
223
|
return rule;
|
|
270
224
|
}
|
|
271
225
|
|
|
@@ -294,8 +248,8 @@ const ensureWellknownRule = async (sites) => {
|
|
|
294
248
|
for (const site of tempSites) {
|
|
295
249
|
// 不向 default site & ip site & wellknown site 添加 wellknown rule
|
|
296
250
|
if (![DOMAIN_FOR_INTERNAL_SITE, DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE].includes(site.domain)) {
|
|
251
|
+
// add /.well-known for blocklet
|
|
297
252
|
const isExists = site.rules.find((x) => x.from.pathPrefix === WELLKNOWN_PATH_PREFIX);
|
|
298
|
-
|
|
299
253
|
if (!isExists) {
|
|
300
254
|
site.rules.push({
|
|
301
255
|
from: { pathPrefix: WELLKNOWN_PATH_PREFIX },
|
|
@@ -308,40 +262,72 @@ const ensureWellknownRule = async (sites) => {
|
|
|
308
262
|
});
|
|
309
263
|
}
|
|
310
264
|
|
|
311
|
-
// add /.well-known/
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
265
|
+
// add /.well-known/service for blocklet
|
|
266
|
+
const blockletRules = site.rules
|
|
267
|
+
.filter((x) => x.to.type === ROUTING_RULE_TYPES.BLOCKLET)
|
|
268
|
+
// 可能存在挂载的 blocklet 不是自己, 是其他 blocklet
|
|
269
|
+
// 这里默认 pathPrefix 最短的是自己 ( 通常自己在 /, 其他 blocklet 在 /xxxx )
|
|
270
|
+
// 挂载其他 blocklet 的使用方式不推荐, 将来可能会被废弃
|
|
271
|
+
.sort((a, b) => (a.from.pathPrefix.length > b.from.pathPrefix.length ? 1 : -1));
|
|
272
|
+
if (blockletRules.length) {
|
|
273
|
+
// get pathPrefix for blocklet-service
|
|
274
|
+
const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.componentId);
|
|
275
|
+
|
|
276
|
+
const servicePathPrefix = joinUrl(
|
|
277
|
+
// rootBlockletRule?.from?.pathPrefix is for backwards compatibility
|
|
278
|
+
// rootBlockletRule?.from?.groupPathPrefix 在旧的场景中可能不为 '/' (blocklet 只能挂载在 relative prefix 下)
|
|
279
|
+
rootBlockletRule?.from?.groupPathPrefix || rootBlockletRule?.from?.pathPrefix || '/',
|
|
280
|
+
WELLKNOWN_SERVICE_PATH_PREFIX
|
|
281
|
+
);
|
|
320
282
|
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
283
|
+
// requests for /.well-known/service will stay in blocklet-service and never proxy back to blocklet
|
|
284
|
+
// so any rule is ok to be cloned
|
|
285
|
+
if (!site.rules.some((x) => x.from.pathPrefix === servicePathPrefix)) {
|
|
286
|
+
const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
|
|
287
|
+
rule.from.pathPrefix = servicePathPrefix;
|
|
288
|
+
rule.to.target = servicePathPrefix;
|
|
289
|
+
rule.isProtected = true;
|
|
290
|
+
rule.dynamic = true; // mark as dynamic to avoid redundant generated rules
|
|
291
|
+
site.rules.push(rule);
|
|
324
292
|
}
|
|
325
293
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
294
|
+
// Cache user avatar in nginx
|
|
295
|
+
const avatarPathPrefix = joinUrl(servicePathPrefix, USER_AVATAR_PATH_PREFIX);
|
|
296
|
+
if (!site.rules.some((x) => x.from.pathPrefix === avatarPathPrefix)) {
|
|
297
|
+
const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
|
|
298
|
+
rule.from.pathPrefix = avatarPathPrefix;
|
|
299
|
+
rule.to.cacheGroup = 'blockletProxy';
|
|
300
|
+
rule.to.target = avatarPathPrefix;
|
|
301
|
+
rule.isProtected = true;
|
|
302
|
+
rule.dynamic = true; // mark as dynamic to avoid redundant generated rules
|
|
303
|
+
site.rules.push(rule);
|
|
331
304
|
}
|
|
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
|
-
});
|
|
305
|
+
}
|
|
339
306
|
}
|
|
340
307
|
}
|
|
341
308
|
|
|
342
309
|
return tempSites;
|
|
343
310
|
};
|
|
344
311
|
|
|
312
|
+
const ensureBlockletDid = async (sites) => {
|
|
313
|
+
const info = await states.node.read();
|
|
314
|
+
|
|
315
|
+
return (sites || []).map((site) => {
|
|
316
|
+
if (site.domain === DOMAIN_FOR_INTERNAL_SITE) {
|
|
317
|
+
return site;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if ([DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(site.domain)) {
|
|
321
|
+
site.blockletDid = info.did;
|
|
322
|
+
return site;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
site.blockletDid = getDidFromDomainGroupName(site.domain);
|
|
326
|
+
|
|
327
|
+
return site;
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
|
|
345
331
|
const ensureCorsForWebWallet = async (sites) => {
|
|
346
332
|
const info = await states.node.read();
|
|
347
333
|
for (const site of sites) {
|
|
@@ -353,48 +339,97 @@ const ensureCorsForWebWallet = async (sites) => {
|
|
|
353
339
|
return sites;
|
|
354
340
|
};
|
|
355
341
|
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
342
|
+
const ensureCorsForDidSpace = async (sites = [], blocklets) => {
|
|
343
|
+
return sites.map((site) => {
|
|
344
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
345
|
+
if (blocklet) {
|
|
346
|
+
const endpoint = blocklet.environments.find(
|
|
347
|
+
(x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
|
|
348
|
+
);
|
|
349
|
+
if (endpoint && isUrl(endpoint.value)) {
|
|
350
|
+
addCorsToSite(site, endpoint.value);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return site;
|
|
355
|
+
});
|
|
361
356
|
};
|
|
362
357
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}, {});
|
|
358
|
+
const filterSitesForRemovedBlocklets = async (sites = [], blocklets) => {
|
|
359
|
+
return sites.filter((site) => {
|
|
360
|
+
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
369
363
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
364
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
365
|
+
return !!blocklet;
|
|
366
|
+
});
|
|
367
|
+
};
|
|
375
368
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!blocklet) {
|
|
382
|
-
return;
|
|
369
|
+
const ensureBlockletCache = async (sites = [], blocklets) => {
|
|
370
|
+
return sites
|
|
371
|
+
.map((site) => {
|
|
372
|
+
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
373
|
+
return site;
|
|
383
374
|
}
|
|
384
375
|
|
|
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 }));
|
|
376
|
+
if (site.cacheableGenerated) {
|
|
377
|
+
return site;
|
|
393
378
|
}
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
379
|
|
|
397
|
-
|
|
380
|
+
// For each rule, get component, check cacheable, clone and push new rule
|
|
381
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
382
|
+
const cacheRules = [];
|
|
383
|
+
site.rules
|
|
384
|
+
.filter(
|
|
385
|
+
(x) =>
|
|
386
|
+
x.to.type === ROUTING_RULE_TYPES.BLOCKLET &&
|
|
387
|
+
x.to.interfaceName === BLOCKLET_INTERFACE_PUBLIC &&
|
|
388
|
+
x.from.pathPrefix.startsWith(WELLKNOWN_SERVICE_PATH_PREFIX) === false
|
|
389
|
+
)
|
|
390
|
+
.forEach((rule) => {
|
|
391
|
+
const component = findComponentById(blocklet, rule.to.componentId);
|
|
392
|
+
if (!component) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (component.mode !== BLOCKLET_MODES.PRODUCTION) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const cacheable = get(findWebInterface(component), 'cacheable', []);
|
|
400
|
+
cacheable.forEach((cachePrefix) => {
|
|
401
|
+
const clone = cloneDeep(rule);
|
|
402
|
+
clone.from.pathPrefix = joinUrl(rule.from.pathPrefix, cachePrefix);
|
|
403
|
+
clone.to.cacheGroup = 'blockletProxy';
|
|
404
|
+
clone.to.targetPrefix = cachePrefix;
|
|
405
|
+
clone.dynamic = true; // mark as dynamic to avoid redundant generated rules
|
|
406
|
+
cacheRules.push(clone);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
site.rules = site.rules.concat(cacheRules);
|
|
411
|
+
site.mode = blocklet.mode;
|
|
412
|
+
site.cacheableGenerated = true;
|
|
413
|
+
|
|
414
|
+
return site;
|
|
415
|
+
})
|
|
416
|
+
.filter(Boolean);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
|
|
420
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
421
|
+
|
|
422
|
+
// CAUTION: following steps are very important, please do not change the order
|
|
423
|
+
let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
|
|
424
|
+
result = await ensureBlockletDid(result);
|
|
425
|
+
result = await filterSitesForRemovedBlocklets(sites, blocklets);
|
|
426
|
+
result = await ensureBlockletCache(result, blocklets);
|
|
427
|
+
result = await ensureWellknownRule(result);
|
|
428
|
+
result = await ensureCorsForWebWallet(result);
|
|
429
|
+
result = await ensureCorsForDidSpace(result, blocklets);
|
|
430
|
+
result = await ensureLatestInterfaceInfo(result);
|
|
431
|
+
|
|
432
|
+
return result;
|
|
398
433
|
};
|
|
399
434
|
|
|
400
435
|
const decompressCertificates = async (source, dest) => {
|
|
@@ -403,55 +438,29 @@ const decompressCertificates = async (source, dest) => {
|
|
|
403
438
|
return dest;
|
|
404
439
|
};
|
|
405
440
|
|
|
406
|
-
|
|
441
|
+
const joinCertDownUrl = (baseUrl, name) => joinUrl(baseUrl, '/certs', name);
|
|
442
|
+
|
|
443
|
+
const getIpEchoCertDownloadUrl = (baseUrl) => joinCertDownUrl(baseUrl, 'ip-abtnet-io.tar.gz');
|
|
444
|
+
const getDidDomainCertDownloadUrl = (baseUrl) => joinCertDownUrl(baseUrl, 'did-abtnet-io.tar.gz');
|
|
445
|
+
const getDownloadCertBaseUrl = (info) =>
|
|
446
|
+
process.env.ABT_NODE_WILDCARD_CERT_HOST || get(info, 'routing.wildcardCertHost', '');
|
|
447
|
+
|
|
448
|
+
module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager }) {
|
|
407
449
|
const nodeState = states.node;
|
|
408
450
|
const blockletState = states.blocklet;
|
|
409
451
|
const siteState = states.site;
|
|
410
|
-
const httpsCertState = states.certificate;
|
|
411
452
|
const notification = states.notification;
|
|
412
453
|
|
|
413
454
|
// site level duplication detection
|
|
414
455
|
const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
|
|
415
456
|
|
|
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()}`));
|
|
457
|
+
const downloadCert = async ({ domain, url }) => {
|
|
458
|
+
const destFolder = getTmpDir(path.join(`certificate-${Date.now()}`));
|
|
450
459
|
try {
|
|
451
460
|
const filename = path.join(destFolder, 'certificate.tar.gz');
|
|
452
461
|
fs.ensureDirSync(destFolder);
|
|
453
462
|
|
|
454
|
-
await downloadFile(
|
|
463
|
+
await downloadFile(url, filename);
|
|
455
464
|
await decompressCertificates(filename, destFolder);
|
|
456
465
|
|
|
457
466
|
const certificateFilePath = path.join(destFolder, 'cert.pem');
|
|
@@ -468,58 +477,186 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
468
477
|
const certificate = fs.readFileSync(certificateFilePath).toString();
|
|
469
478
|
const privateKey = fs.readFileSync(privateKeyFilePath).toString();
|
|
470
479
|
|
|
471
|
-
await
|
|
472
|
-
|
|
480
|
+
await certManager.upsertByDomain({
|
|
481
|
+
domain,
|
|
482
|
+
privateKey,
|
|
483
|
+
certificate,
|
|
484
|
+
isProtected: true,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
logger.info('dashboard certificate updated', { domain });
|
|
473
488
|
} catch (error) {
|
|
474
|
-
logger.error('
|
|
489
|
+
logger.error('update dashboard certificate failed', { error });
|
|
490
|
+
throw error;
|
|
475
491
|
} finally {
|
|
476
492
|
fs.removeSync(destFolder);
|
|
477
493
|
}
|
|
478
494
|
};
|
|
479
495
|
|
|
496
|
+
const updateCert = async (domain, url) => {
|
|
497
|
+
try {
|
|
498
|
+
const cert = await certManager.getByDomain(domain);
|
|
499
|
+
if (!cert) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const now = Date.now();
|
|
504
|
+
const certInfo = getHttpsCertInfo(cert.certificate);
|
|
505
|
+
|
|
506
|
+
if (certInfo.validTo - now >= CERTIFICATE_EXPIRES_OFFSET) {
|
|
507
|
+
logger.info('skip dashboard certificate update before not expired', { domain, url });
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
await downloadCert({
|
|
512
|
+
domain,
|
|
513
|
+
url,
|
|
514
|
+
});
|
|
515
|
+
} catch (err) {
|
|
516
|
+
logger.error('failed to check dashboard certificate expiration', { error: err, domain, url });
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const updateDashboardCertificates = async () => {
|
|
521
|
+
const info = await nodeState.read();
|
|
522
|
+
const https = get(info, 'routing.https', true);
|
|
523
|
+
const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
|
|
524
|
+
const didDomain = info.didDomain;
|
|
525
|
+
const certDownloadAddress = getDownloadCertBaseUrl(info);
|
|
526
|
+
if (!https || !certDownloadAddress) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
await updateCert(ipWildcardDomain, getIpEchoCertDownloadUrl(certDownloadAddress));
|
|
531
|
+
await updateCert(`*.${didDomain}`, getDidDomainCertDownloadUrl(certDownloadAddress));
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const ensureWildcardCerts = async () => {
|
|
535
|
+
const info = await nodeState.read();
|
|
536
|
+
const didDomain = info.didDomain;
|
|
537
|
+
const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
|
|
538
|
+
|
|
539
|
+
const ensureDomainCert = async (domain, url) => {
|
|
540
|
+
const cert = await certManager.getByDomain(domain);
|
|
541
|
+
if (!cert || get(cert, 'meta.validTo') <= Date.now()) {
|
|
542
|
+
await downloadCert({
|
|
543
|
+
domain,
|
|
544
|
+
url,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const certBaseUrl = getDownloadCertBaseUrl(info);
|
|
550
|
+
await Promise.all([
|
|
551
|
+
ensureDomainCert(ipWildcardDomain, getIpEchoCertDownloadUrl(certBaseUrl)),
|
|
552
|
+
ensureDomainCert(`*.${didDomain}`, getDidDomainCertDownloadUrl(certBaseUrl)),
|
|
553
|
+
]);
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const upsertSiteRule = async ({ site, rule }, context) => {
|
|
557
|
+
const findExistingRule = (prefix) => site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(prefix));
|
|
558
|
+
|
|
559
|
+
const newSiteRule = {
|
|
560
|
+
id: site.id,
|
|
561
|
+
rule,
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const existingRule = findExistingRule(get(rule, 'from.pathPrefix'));
|
|
565
|
+
if (!existingRule) {
|
|
566
|
+
await routerManager.addRoutingRule(newSiteRule, context);
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (!isEqual(existingRule.to, rule.to)) {
|
|
571
|
+
newSiteRule.rule.id = existingRule.id;
|
|
572
|
+
newSiteRule.skipProtectedRuleChecking = true;
|
|
573
|
+
await routerManager.updateRoutingRule(newSiteRule, context);
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return false;
|
|
578
|
+
};
|
|
579
|
+
|
|
480
580
|
const addWellknownSite = async (sites, context) => {
|
|
481
581
|
const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
|
|
482
582
|
|
|
483
583
|
try {
|
|
484
|
-
const
|
|
584
|
+
const info = await nodeState.read();
|
|
585
|
+
const proxyTarget = {
|
|
586
|
+
port: info.port,
|
|
587
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
588
|
+
interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
|
|
589
|
+
};
|
|
590
|
+
|
|
485
591
|
const didResolverWellknownRule = {
|
|
486
|
-
|
|
592
|
+
isProtected: true,
|
|
593
|
+
from: { pathPrefix: WELLKNOWN_DID_RESOLVER_PREFIX },
|
|
594
|
+
to: proxyTarget,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const acmeChallengeWellknownRule = {
|
|
598
|
+
isProtected: true,
|
|
599
|
+
from: { pathPrefix: WELLKNOWN_ACME_CHALLENGE_PREFIX },
|
|
600
|
+
to: proxyTarget,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const pingWellknownRule = {
|
|
604
|
+
isProtected: true,
|
|
605
|
+
from: { pathPrefix: WELLKNOWN_PING_PREFIX },
|
|
487
606
|
to: {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
607
|
+
type: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
|
|
608
|
+
response: {
|
|
609
|
+
status: 200,
|
|
610
|
+
contentType: 'application/javascript',
|
|
611
|
+
body: "'pong'",
|
|
612
|
+
},
|
|
613
|
+
port: info.port,
|
|
491
614
|
},
|
|
492
615
|
};
|
|
493
616
|
|
|
494
617
|
if (site) {
|
|
495
|
-
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
await routerManager.addRoutingRule(
|
|
618
|
+
const didResolverRuleUpdateRes = await upsertSiteRule(
|
|
500
619
|
{
|
|
501
|
-
|
|
620
|
+
site,
|
|
502
621
|
rule: didResolverWellknownRule,
|
|
503
622
|
},
|
|
504
623
|
context
|
|
505
624
|
);
|
|
506
|
-
|
|
507
|
-
await
|
|
625
|
+
|
|
626
|
+
const acmeRuleUpdateRes = await upsertSiteRule(
|
|
508
627
|
{
|
|
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,
|
|
628
|
+
site,
|
|
629
|
+
rule: acmeChallengeWellknownRule,
|
|
518
630
|
},
|
|
519
631
|
context
|
|
520
632
|
);
|
|
633
|
+
|
|
634
|
+
const pingRuleRes = await upsertSiteRule(
|
|
635
|
+
{
|
|
636
|
+
site,
|
|
637
|
+
rule: pingWellknownRule,
|
|
638
|
+
},
|
|
639
|
+
context
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
return didResolverRuleUpdateRes || acmeRuleUpdateRes || pingRuleRes;
|
|
521
643
|
}
|
|
522
644
|
|
|
645
|
+
await routerManager.addRoutingSite(
|
|
646
|
+
{
|
|
647
|
+
site: {
|
|
648
|
+
domain: DOMAIN_FOR_INTERNAL_SITE,
|
|
649
|
+
port: await getWellknownSitePort(),
|
|
650
|
+
name: NAME_FOR_WELLKNOWN_SITE,
|
|
651
|
+
rules: [didResolverWellknownRule, acmeChallengeWellknownRule, pingWellknownRule],
|
|
652
|
+
isProtected: true,
|
|
653
|
+
},
|
|
654
|
+
skipCheckDynamicBlacklist: true,
|
|
655
|
+
skipValidation: true,
|
|
656
|
+
},
|
|
657
|
+
context
|
|
658
|
+
);
|
|
659
|
+
|
|
523
660
|
return true;
|
|
524
661
|
} catch (err) {
|
|
525
662
|
console.error('add well-known site failed:', err);
|
|
@@ -533,14 +670,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
533
670
|
*
|
|
534
671
|
* @returns {boolean} if routing changed
|
|
535
672
|
*/
|
|
536
|
-
const ensureDashboardRouting = async (context = {}
|
|
673
|
+
const ensureDashboardRouting = async (context = {}) => {
|
|
537
674
|
const info = await nodeState.read();
|
|
538
|
-
|
|
539
|
-
const provider = getProviderFromNodeInfo(info);
|
|
540
|
-
if (provider === ROUTER_PROVIDER_NONE) {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
675
|
const sites = await siteState.getSites();
|
|
545
676
|
let dashboardSite = (sites || []).find((x) => x.domain === DOMAIN_FOR_IP_SITE);
|
|
546
677
|
const updatedResult = [];
|
|
@@ -569,28 +700,26 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
569
700
|
}
|
|
570
701
|
}
|
|
571
702
|
|
|
572
|
-
const
|
|
573
|
-
(dashboardSite.domainAliases || []).find((item) => {
|
|
574
|
-
if (typeof item === 'object') {
|
|
575
|
-
return domainAlias === item.value;
|
|
576
|
-
}
|
|
703
|
+
const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
|
|
577
704
|
|
|
578
|
-
|
|
579
|
-
|
|
705
|
+
const domainAliases = (dashboardSite.domainAliases || []).filter(
|
|
706
|
+
(item) => !item.value.endsWith(ipWildcardDomain) && !item.value.endsWith(info.didDomain)
|
|
707
|
+
);
|
|
580
708
|
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
);
|
|
709
|
+
const didDomain = getServerDidDomain(info);
|
|
710
|
+
const dashboardAliasDomains = [
|
|
711
|
+
{ value: ipWildcardDomain, isProtected: true },
|
|
712
|
+
{ value: didDomain, isProtected: true },
|
|
713
|
+
];
|
|
714
|
+
domainAliases.push(...dashboardAliasDomains);
|
|
588
715
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
716
|
+
try {
|
|
717
|
+
const result = await siteState.update({ _id: dashboardSite.id }, { $set: { domainAliases } });
|
|
718
|
+
|
|
719
|
+
updatedResult.push(result);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
logger.error('add dashboard domain rule failed', { error });
|
|
722
|
+
console.error('Add dashboard domain rule failed:', error);
|
|
594
723
|
}
|
|
595
724
|
|
|
596
725
|
const defaultRule = sites.find((x) => x.domain === DOMAIN_FOR_DEFAULT_SITE);
|
|
@@ -618,18 +747,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
618
747
|
updatedResult.push(wellknownRes);
|
|
619
748
|
}
|
|
620
749
|
|
|
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
750
|
if (updatedResult.length) {
|
|
634
751
|
const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
|
|
635
752
|
logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
|
|
@@ -657,8 +774,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
657
774
|
return `/${str}`.replace(/^\/+/, '/');
|
|
658
775
|
};
|
|
659
776
|
|
|
660
|
-
const domainGroup =
|
|
661
|
-
|
|
777
|
+
const domainGroup = getBlockletDomainGroupName(blocklet.meta.did);
|
|
778
|
+
|
|
662
779
|
const pathPrefix = getPrefix(webInterface.prefix);
|
|
663
780
|
const rule = {
|
|
664
781
|
from: { pathPrefix },
|
|
@@ -672,20 +789,45 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
672
789
|
};
|
|
673
790
|
|
|
674
791
|
const existSite = await states.site.findOne({ domain: domainGroup });
|
|
792
|
+
const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
793
|
+
updateBlockletDocument({
|
|
794
|
+
wallet,
|
|
795
|
+
appPid: blocklet.appPid,
|
|
796
|
+
alsoKnownAs: getBlockletKnownAs(blocklet),
|
|
797
|
+
daemonDidDomain: getServerDidDomain(nodeInfo),
|
|
798
|
+
didRegistryUrl: nodeInfo.didRegistry,
|
|
799
|
+
domain: nodeInfo.didDomain,
|
|
800
|
+
})
|
|
801
|
+
.then(() => {
|
|
802
|
+
logger.info(`updated blocklet ${blocklet.appDid} dns`);
|
|
803
|
+
})
|
|
804
|
+
.catch((err) => {
|
|
805
|
+
logger.error(`update blocklet ${blocklet.appDid} dns failed`, { error: err });
|
|
806
|
+
});
|
|
807
|
+
|
|
675
808
|
if (!existSite) {
|
|
809
|
+
const domainAliases = [];
|
|
810
|
+
|
|
811
|
+
const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
|
|
812
|
+
const ipEchoDnsDomain = getIpDnsDomainForBlocklet(blocklet);
|
|
813
|
+
|
|
814
|
+
// let didDomain in front of ipEchoDnsDomain
|
|
815
|
+
domainAliases.push({ value: didDomain, isProtected: true }, { value: ipEchoDnsDomain, isProtected: true });
|
|
816
|
+
|
|
676
817
|
await routerManager.addRoutingSite(
|
|
677
818
|
{
|
|
678
819
|
site: {
|
|
679
820
|
domain: domainGroup,
|
|
680
|
-
domainAliases
|
|
821
|
+
domainAliases,
|
|
681
822
|
isProtected: true,
|
|
682
823
|
rules: [rule],
|
|
683
824
|
},
|
|
684
825
|
skipCheckDynamicBlacklist: true,
|
|
826
|
+
skipValidation: true,
|
|
685
827
|
},
|
|
686
828
|
context
|
|
687
829
|
);
|
|
688
|
-
logger.info('add routing site', { site:
|
|
830
|
+
logger.info('add routing site', { site: domainGroup });
|
|
689
831
|
|
|
690
832
|
return true;
|
|
691
833
|
}
|
|
@@ -700,13 +842,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
700
842
|
},
|
|
701
843
|
skipProtectedRuleChecking: true,
|
|
702
844
|
});
|
|
703
|
-
logger.info('update routing rule for site', { site:
|
|
845
|
+
logger.info('update routing rule for site', { site: domainGroup });
|
|
704
846
|
} else {
|
|
705
847
|
await routerManager.addRoutingRule({
|
|
706
848
|
id: existSite.id,
|
|
707
849
|
rule,
|
|
708
850
|
});
|
|
709
|
-
logger.info('add routing rule for site', { site:
|
|
851
|
+
logger.info('add routing rule for site', { site: domainGroup });
|
|
710
852
|
}
|
|
711
853
|
|
|
712
854
|
return true;
|
|
@@ -723,130 +865,79 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
723
865
|
return false;
|
|
724
866
|
}
|
|
725
867
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
868
|
+
/**
|
|
869
|
+
* 1. component blocklet 不允许有相同多的 wellknown
|
|
870
|
+
* 1. wellknown 可以访问的路由:
|
|
871
|
+
* /.well-known/xxx
|
|
872
|
+
* /{wellknown owner blocklet}/.well-known/xxx
|
|
873
|
+
*/
|
|
733
874
|
|
|
734
|
-
|
|
735
|
-
const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
|
|
875
|
+
const isWellknownInterface = (x) => x.type === BLOCKLET_INTERFACE_TYPE_WELLKNOWN;
|
|
736
876
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
743
|
-
interfaceName: x.name,
|
|
744
|
-
},
|
|
745
|
-
isProtected: true,
|
|
746
|
-
};
|
|
877
|
+
const handler = async (tmpBlocklet, tmpInterface, mountPoint) => {
|
|
878
|
+
let pathPrefix = normalizePathPrefix(tmpInterface.prefix);
|
|
879
|
+
if (!pathPrefix.startsWith(WELLKNOWN_PATH_PREFIX)) {
|
|
880
|
+
throw new Error(`Wellknown path prefix must start with: ${WELLKNOWN_PATH_PREFIX}`);
|
|
881
|
+
}
|
|
747
882
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
883
|
+
const tmpMountPoint = mountPoint || tmpBlocklet.mountPoint;
|
|
884
|
+
if (tmpMountPoint) {
|
|
885
|
+
pathPrefix = joinUrl(tmpMountPoint, pathPrefix);
|
|
886
|
+
}
|
|
752
887
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
existedRule.to = rule.to;
|
|
888
|
+
const port = findInterfacePortByName(tmpBlocklet, tmpInterface.name);
|
|
889
|
+
const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
|
|
756
890
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
891
|
+
const rule = {
|
|
892
|
+
from: { pathPrefix },
|
|
893
|
+
to: {
|
|
894
|
+
did: tmpBlocklet.meta.did,
|
|
895
|
+
port,
|
|
896
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
897
|
+
interfaceName: tmpInterface.name,
|
|
898
|
+
},
|
|
899
|
+
isProtected: true,
|
|
900
|
+
};
|
|
761
901
|
|
|
762
|
-
|
|
763
|
-
}
|
|
902
|
+
if (!existedRule) {
|
|
903
|
+
await routerManager.addRoutingRule({ id: wellknownSite.id, rule, skipCheckDynamicBlacklist: true }, context);
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
764
906
|
|
|
765
|
-
|
|
766
|
-
|
|
907
|
+
// 兼容代码,旧的 rule 没有 to.did 字段
|
|
908
|
+
if (port !== existedRule.to.port || existedRule.to.did !== tmpBlocklet.meta.did) {
|
|
909
|
+
existedRule.to = rule.to;
|
|
767
910
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
911
|
+
await routerManager.updateRoutingRule(
|
|
912
|
+
{ id: wellknownSite.id, rule: existedRule, skipProtectedRuleChecking: true },
|
|
913
|
+
context
|
|
914
|
+
);
|
|
771
915
|
|
|
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
|
-
}
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
787
918
|
|
|
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
919
|
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
|
-
}
|
|
920
|
+
};
|
|
821
921
|
|
|
822
|
-
|
|
823
|
-
if (x.path === '/' && x.prefix === '/') {
|
|
824
|
-
return changed;
|
|
825
|
-
}
|
|
922
|
+
const tasks = [];
|
|
826
923
|
|
|
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;
|
|
924
|
+
forEachBlocklet(
|
|
925
|
+
blocklet,
|
|
926
|
+
(b) => {
|
|
927
|
+
(b.meta.interfaces || []).forEach((item) => {
|
|
928
|
+
if (isWellknownInterface(item)) {
|
|
929
|
+
tasks.push(handler(b, item));
|
|
845
930
|
|
|
846
|
-
|
|
847
|
-
|
|
931
|
+
if (!b.mountPoint || b.mountPoint !== '/') {
|
|
932
|
+
tasks.push(handler(b, item, '/')); // 在站点的根路由下挂载一个
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
},
|
|
937
|
+
{ sync: true }
|
|
848
938
|
);
|
|
849
939
|
|
|
940
|
+
const changes = await Promise.all(tasks);
|
|
850
941
|
return changes.some(Boolean);
|
|
851
942
|
};
|
|
852
943
|
|
|
@@ -873,12 +964,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
873
964
|
*/
|
|
874
965
|
const ensureBlockletRouting = async (blocklet, context = {}) => {
|
|
875
966
|
const nodeInfo = await nodeState.read();
|
|
876
|
-
|
|
877
|
-
const provider = getProviderFromNodeInfo(nodeInfo);
|
|
878
|
-
if (provider === ROUTER_PROVIDER_NONE) {
|
|
879
|
-
return false;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
967
|
const hasWebInterface = (blocklet.meta.interfaces || []).some((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
|
|
883
968
|
if (!hasWebInterface) {
|
|
884
969
|
return false;
|
|
@@ -891,14 +976,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
891
976
|
_ensureBlockletSites(blocklet, sites, nodeInfo, context),
|
|
892
977
|
];
|
|
893
978
|
|
|
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
979
|
const changes = await Promise.all(tasks);
|
|
903
980
|
|
|
904
981
|
return changes.some(Boolean);
|
|
@@ -978,12 +1055,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
978
1055
|
* @returns {boolean} if routing changed
|
|
979
1056
|
*/
|
|
980
1057
|
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
1058
|
await routerManager.deleteRoutingRulesItemByDid(
|
|
988
1059
|
{ did: blocklet.meta.did, ruleFilter: (x) => x.isProtected },
|
|
989
1060
|
context
|
|
@@ -1004,7 +1075,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1004
1075
|
const nodeInfo = await nodeState.read();
|
|
1005
1076
|
|
|
1006
1077
|
const ruleChanged = await routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
|
|
1007
|
-
|
|
1078
|
+
let siteChanged;
|
|
1079
|
+
if (!context.keepRouting) {
|
|
1080
|
+
siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
|
|
1081
|
+
}
|
|
1008
1082
|
|
|
1009
1083
|
return ruleChanged || siteChanged;
|
|
1010
1084
|
};
|
|
@@ -1028,21 +1102,21 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1028
1102
|
return result;
|
|
1029
1103
|
}
|
|
1030
1104
|
|
|
1031
|
-
async function
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1105
|
+
async function resetSiteByDid(did, { refreshRouterProvider = true } = {}) {
|
|
1106
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
1107
|
+
await removeBlockletRouting(blocklet);
|
|
1108
|
+
await ensureBlockletRouting(blocklet);
|
|
1109
|
+
if (refreshRouterProvider) {
|
|
1110
|
+
const hash = await takeRoutingSnapshot({ message: `Reset blocklet ${did}`, dryRun: false });
|
|
1111
|
+
logger.info('reset blocklet routing rules', { did, hash });
|
|
1112
|
+
}
|
|
1036
1113
|
}
|
|
1037
1114
|
|
|
1038
|
-
const
|
|
1115
|
+
const providers = {}; // we need to keep reference for different router instances
|
|
1039
1116
|
const handleRouting = async (nodeInfo) => {
|
|
1040
1117
|
const providerName = get(nodeInfo, 'routing.provider', null);
|
|
1041
1118
|
const httpsEnabled = get(nodeInfo, 'routing.https', true);
|
|
1042
1119
|
logger.debug('handle routing', { providerName, httpsEnabled });
|
|
1043
|
-
if (providerName === ROUTER_PROVIDER_NONE) {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
1120
|
|
|
1047
1121
|
const Provider = getProvider(providerName);
|
|
1048
1122
|
const checkResult = await Provider.check({ configDir: dataDirs.router });
|
|
@@ -1050,27 +1124,25 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1050
1124
|
throw new Error(`${providerName} pre-check failed, ${checkResult.error}`);
|
|
1051
1125
|
}
|
|
1052
1126
|
|
|
1053
|
-
if (
|
|
1054
|
-
await
|
|
1127
|
+
if (providers[providerName]) {
|
|
1128
|
+
await providers[providerName].reload();
|
|
1055
1129
|
} else {
|
|
1056
|
-
|
|
1130
|
+
providers[providerName] = new Router({
|
|
1057
1131
|
provider: new Provider({
|
|
1058
|
-
|
|
1132
|
+
configDir: path.join(dataDirs.router, providerName),
|
|
1059
1133
|
httpPort: nodeInfo.routing.httpPort || DEFAULT_HTTP_PORT,
|
|
1060
1134
|
httpsPort: nodeInfo.routing.httpsPort || DEFAULT_HTTPS_PORT,
|
|
1061
|
-
|
|
1135
|
+
cacheEnabled: isGatewayCacheEnabled(nodeInfo),
|
|
1062
1136
|
}),
|
|
1063
1137
|
getRoutingParams: async () => {
|
|
1064
1138
|
try {
|
|
1065
1139
|
const info = await nodeState.read();
|
|
1066
1140
|
let { sites } = await readRoutingSites();
|
|
1067
1141
|
sites = await ensureLatestInfo(sites);
|
|
1068
|
-
sites = await ensureAuthService(sites, blockletManager);
|
|
1069
1142
|
sites = await ensureServiceRule(sites);
|
|
1143
|
+
sites = await ensureRootRule(sites);
|
|
1070
1144
|
|
|
1071
|
-
const certificates = httpsEnabled
|
|
1072
|
-
? await httpsCertState.find({ type: providerName }, { domain: 1, certificate: 1, privateKey: 1 })
|
|
1073
|
-
: [];
|
|
1145
|
+
const certificates = httpsEnabled ? await certManager.getAllNormal() : [];
|
|
1074
1146
|
|
|
1075
1147
|
return {
|
|
1076
1148
|
sites,
|
|
@@ -1093,11 +1165,17 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1093
1165
|
},
|
|
1094
1166
|
});
|
|
1095
1167
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1168
|
+
[
|
|
1169
|
+
BlockletEvents.certIssued,
|
|
1170
|
+
EVENTS.CERT_ADDED,
|
|
1171
|
+
EVENTS.CERT_REMOVED,
|
|
1172
|
+
EVENTS.CERT_ISSUED,
|
|
1173
|
+
EVENTS.CERT_UPDATED,
|
|
1174
|
+
].forEach((event) => {
|
|
1175
|
+
certManager.on(event, () => providers[providerName].reload());
|
|
1176
|
+
});
|
|
1099
1177
|
|
|
1100
|
-
await
|
|
1178
|
+
await providers[providerName].start();
|
|
1101
1179
|
}
|
|
1102
1180
|
};
|
|
1103
1181
|
|
|
@@ -1105,8 +1183,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1105
1183
|
const info = await nodeState.read();
|
|
1106
1184
|
const providerName = get(info, 'routing.provider', null);
|
|
1107
1185
|
|
|
1108
|
-
if (providerName &&
|
|
1109
|
-
await
|
|
1186
|
+
if (providerName && providers[providerName] && typeof providers[providerName].rotateLogs === 'function') {
|
|
1187
|
+
await providers[providerName].rotateLogs({ retain: LOG_RETAIN_IN_DAYS });
|
|
1110
1188
|
}
|
|
1111
1189
|
};
|
|
1112
1190
|
|
|
@@ -1135,33 +1213,31 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1135
1213
|
await handleRouting(result);
|
|
1136
1214
|
|
|
1137
1215
|
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
|
-
}
|
|
1216
|
+
// Ensure we have system sites for daemon
|
|
1217
|
+
await ensureDashboardRouting(context);
|
|
1218
|
+
|
|
1219
|
+
// Ensure we have system rules for blocklets
|
|
1220
|
+
const blocklets = await blockletState.getBlocklets();
|
|
1221
|
+
const ensureBlocklet = async (x) => {
|
|
1222
|
+
const blocklet = await blockletManager.getBlocklet(x.meta.did);
|
|
1223
|
+
return ensureBlockletRouting(blocklet, context);
|
|
1224
|
+
};
|
|
1225
|
+
const ensureBlockletResults = await Promise.all(blocklets.map((x) => ensureBlocklet(x)));
|
|
1226
|
+
|
|
1227
|
+
// We need to take snapshot after system rules ensured
|
|
1228
|
+
if (ensureBlockletResults.filter(Boolean).length) {
|
|
1229
|
+
await takeRoutingSnapshot({ message: `Switch routing engine to ${newProvider}`, dryRun: false }, context);
|
|
1230
|
+
logger.info(`take routing snapshot on switch engine: ${newProvider}`, { ensureBlockletResults });
|
|
1155
1231
|
}
|
|
1156
1232
|
|
|
1157
1233
|
const Provider = getProvider(info.routing.provider);
|
|
1158
1234
|
if (Provider) {
|
|
1159
1235
|
try {
|
|
1160
1236
|
const providerInstance = new Provider({
|
|
1161
|
-
|
|
1237
|
+
configDir: path.join(dataDirs.router, info.routing.provider),
|
|
1162
1238
|
httpPort: info.routing.httpPort || DEFAULT_HTTP_PORT,
|
|
1163
1239
|
httpsPort: info.routing.httpsPort || DEFAULT_HTTPS_PORT,
|
|
1164
|
-
|
|
1240
|
+
cacheEnabled: isGatewayCacheEnabled(info),
|
|
1165
1241
|
});
|
|
1166
1242
|
await providerInstance.stop();
|
|
1167
1243
|
logger.info('original router stopped:', { provider: info.routing.provider });
|
|
@@ -1240,7 +1316,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1240
1316
|
return sites;
|
|
1241
1317
|
}
|
|
1242
1318
|
|
|
1243
|
-
return
|
|
1319
|
+
return attachRuntimeDomainAliases({
|
|
1244
1320
|
sites: await ensureLatestInfo(sites, { withDefaultCors }),
|
|
1245
1321
|
context,
|
|
1246
1322
|
node: nodeState,
|
|
@@ -1249,7 +1325,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1249
1325
|
|
|
1250
1326
|
const getSnapshotSites = async ({ hash }, context = {}, { withDefaultCors = true } = {}) => {
|
|
1251
1327
|
const sites = await routingSnapshot.readSnapshotSites(hash);
|
|
1252
|
-
return
|
|
1328
|
+
return attachRuntimeDomainAliases({
|
|
1253
1329
|
sites: await ensureLatestInfo(sites, { withDefaultCors }),
|
|
1254
1330
|
context,
|
|
1255
1331
|
node: nodeState,
|
|
@@ -1257,65 +1333,56 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1257
1333
|
};
|
|
1258
1334
|
|
|
1259
1335
|
const getCertificates = async () => {
|
|
1260
|
-
const certificates = await
|
|
1336
|
+
const certificates = await certManager.getAll();
|
|
1261
1337
|
const sites = await getSitesFromSnapshot();
|
|
1338
|
+
|
|
1339
|
+
const isMatch = (cert, domain) =>
|
|
1340
|
+
domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
|
|
1341
|
+
|
|
1262
1342
|
return certificates.map((cert) => {
|
|
1263
1343
|
cert.matchedSites = [];
|
|
1264
1344
|
sites.forEach((site) => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1345
|
+
const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
|
|
1346
|
+
domains.forEach((domain) => {
|
|
1347
|
+
if (isMatch(cert, domain)) {
|
|
1348
|
+
cert.matchedSites.push({ id: site.id, domain });
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1272
1351
|
});
|
|
1273
1352
|
|
|
1274
1353
|
return cert;
|
|
1275
1354
|
});
|
|
1276
1355
|
};
|
|
1277
1356
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1357
|
+
/**
|
|
1358
|
+
* proxy to routerManager and do takeRoutingSnapshot
|
|
1359
|
+
*/
|
|
1360
|
+
const _proxyToRouterManager =
|
|
1361
|
+
(fnName) =>
|
|
1362
|
+
async (...args) => {
|
|
1363
|
+
const res = await routerManager[fnName](...args);
|
|
1280
1364
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1365
|
+
takeRoutingSnapshot({ message: fnName, dryRun: false }).catch((error) => {
|
|
1366
|
+
logger.error('failed to takeRoutingSnapshot', { error });
|
|
1367
|
+
});
|
|
1283
1368
|
|
|
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
|
-
});
|
|
1369
|
+
const { teamDid: did } = args[0] || {};
|
|
1370
|
+
if (did) {
|
|
1371
|
+
routerManager.emit(BlockletEvents.updated, { meta: { did } });
|
|
1316
1372
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1373
|
+
|
|
1374
|
+
return res;
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
const addRoutingSite = _proxyToRouterManager('addRoutingSite');
|
|
1378
|
+
const updateRoutingSite = _proxyToRouterManager('updateRoutingSite');
|
|
1379
|
+
const deleteRoutingSite = _proxyToRouterManager('deleteRoutingSite');
|
|
1380
|
+
const addRoutingRule = _proxyToRouterManager('addRoutingRule');
|
|
1381
|
+
const updateRoutingRule = _proxyToRouterManager('updateRoutingRule');
|
|
1382
|
+
const deleteRoutingRule = _proxyToRouterManager('deleteRoutingRule');
|
|
1383
|
+
// FIXME: should verify domain owner before added
|
|
1384
|
+
const addDomainAlias = _proxyToRouterManager('addDomainAlias');
|
|
1385
|
+
const deleteDomainAlias = _proxyToRouterManager('deleteDomainAlias');
|
|
1319
1386
|
|
|
1320
1387
|
return {
|
|
1321
1388
|
ensureDashboardRouting,
|
|
@@ -1323,39 +1390,47 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1323
1390
|
ensureBlockletRoutingForUpgrade,
|
|
1324
1391
|
removeBlockletRouting,
|
|
1325
1392
|
handleRouting,
|
|
1326
|
-
|
|
1393
|
+
resetSiteByDid,
|
|
1327
1394
|
updateNodeRouting,
|
|
1328
1395
|
takeRoutingSnapshot,
|
|
1329
1396
|
getRoutingSites,
|
|
1330
1397
|
getSnapshotSites,
|
|
1331
|
-
getCertificates,
|
|
1332
1398
|
getSitesFromSnapshot,
|
|
1333
|
-
|
|
1399
|
+
getCertificates,
|
|
1400
|
+
ensureWildcardCerts,
|
|
1401
|
+
addWellknownSite,
|
|
1402
|
+
upsertSiteRule,
|
|
1403
|
+
getRouterProvider: (name) => providers[name],
|
|
1334
1404
|
|
|
1335
1405
|
getRoutingCrons: () => [
|
|
1336
1406
|
{
|
|
1337
1407
|
name: 'update-dashboard-certificate',
|
|
1338
1408
|
time: '0 1 */6 * * *', // refetch on 0:00, 6:00, etc.
|
|
1339
|
-
fn: () =>
|
|
1409
|
+
fn: () => updateDashboardCertificates(),
|
|
1340
1410
|
options: { runOnInit: true },
|
|
1341
1411
|
},
|
|
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
1412
|
{
|
|
1349
1413
|
name: 'rotate-log-files',
|
|
1350
|
-
time: '
|
|
1414
|
+
time: '1 0 0 * * *', // rotate at 00:00:01 every day
|
|
1351
1415
|
fn: rotateRouterLog,
|
|
1352
1416
|
options: { runOnInit: false },
|
|
1353
1417
|
},
|
|
1354
1418
|
],
|
|
1419
|
+
|
|
1420
|
+
addRoutingSite,
|
|
1421
|
+
deleteRoutingSite,
|
|
1422
|
+
updateRoutingSite,
|
|
1423
|
+
addRoutingRule,
|
|
1424
|
+
updateRoutingRule,
|
|
1425
|
+
deleteRoutingRule,
|
|
1426
|
+
addDomainAlias,
|
|
1427
|
+
deleteDomainAlias,
|
|
1428
|
+
|
|
1429
|
+
isGatewayCacheEnabled,
|
|
1355
1430
|
};
|
|
1356
1431
|
};
|
|
1357
1432
|
|
|
1358
|
-
module.exports.
|
|
1433
|
+
module.exports.attachRuntimeDomainAliases = attachRuntimeDomainAliases;
|
|
1359
1434
|
module.exports.ensureLatestNodeInfo = ensureLatestNodeInfo;
|
|
1360
1435
|
module.exports.ensureLatestInterfaceInfo = ensureLatestInterfaceInfo;
|
|
1361
1436
|
module.exports.ensureLatestInfo = ensureLatestInfo;
|