@abtnode/core 1.7.10 → 1.7.13
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 +7 -1
- package/lib/api/team.js +2 -2
- package/lib/blocklet/manager/disk.js +141 -81
- package/lib/blocklet/registry.js +6 -2
- package/lib/event.js +5 -0
- package/lib/index.js +39 -7
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/router/index.js +7 -0
- package/lib/states/README.md +31 -1
- package/lib/states/audit-log.js +382 -0
- package/lib/states/blocklet.js +9 -7
- package/lib/states/index.js +3 -0
- package/lib/states/node.js +8 -0
- package/lib/util/blocklet.js +102 -43
- package/lib/util/index.js +35 -0
- package/lib/util/ip.js +6 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/ua.js +54 -0
- package/lib/util/upgrade.js +3 -15
- package/lib/validators/node.js +13 -0
- package/package.json +21 -20
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const md5 = require('@abtnode/util/lib/md5');
|
|
4
|
+
const formatContext = require('@abtnode/util/lib/format-context');
|
|
4
5
|
const Cron = require('@abtnode/cron');
|
|
5
6
|
|
|
6
7
|
const logger = require('@abtnode/logger')('@abtnode/core');
|
|
@@ -36,7 +37,7 @@ const createEvents = require('./event');
|
|
|
36
37
|
const pm2Events = require('./blocklet/manager/pm2-events');
|
|
37
38
|
const { createStateReadyQueue, createStateReadyHandler } = require('./util/ready');
|
|
38
39
|
const { getSysInfo, SysInfoEmitter } = require('./util/sysinfo');
|
|
39
|
-
const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem } = require('./util');
|
|
40
|
+
const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem, getStateCrons } = require('./util');
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* @param {object} options
|
|
@@ -243,9 +244,9 @@ function ABTNode(options) {
|
|
|
243
244
|
getNodeInfo: nodeAPI.getInfo.bind(nodeAPI),
|
|
244
245
|
getNodeEnv: nodeAPI.getEnv.bind(nodeAPI),
|
|
245
246
|
updateNodeInfo: nodeAPI.updateNodeInfo.bind(nodeAPI),
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
addBlockletStore: nodeAPI.addRegistry.bind(nodeAPI),
|
|
248
|
+
deleteBlockletStore: nodeAPI.deleteRegistry.bind(nodeAPI),
|
|
249
|
+
selectBlockletStore: nodeAPI.selectRegistry.bind(nodeAPI),
|
|
249
250
|
cleanupDirtyUpgradeState: states.node.cleanupDirtyUpgradeState.bind(states.node),
|
|
250
251
|
addNodeOwner: states.node.addNodeOwner.bind(states.node),
|
|
251
252
|
updateNodeRouting,
|
|
@@ -253,6 +254,9 @@ function ABTNode(options) {
|
|
|
253
254
|
resetNode: (params, context) =>
|
|
254
255
|
resetNode({ params, context, blockletManager, routerManager, takeRoutingSnapshot, teamManager, certManager }),
|
|
255
256
|
|
|
257
|
+
// Gateway
|
|
258
|
+
updateGateway: nodeAPI.updateGateway.bind(nodeAPI),
|
|
259
|
+
|
|
256
260
|
// Team && Access control
|
|
257
261
|
|
|
258
262
|
// Invitation
|
|
@@ -314,6 +318,10 @@ function ABTNode(options) {
|
|
|
314
318
|
readNotifications: states.notification.read.bind(states.notification),
|
|
315
319
|
unreadNotifications: states.notification.unread.bind(states.notification),
|
|
316
320
|
|
|
321
|
+
// AuditLog
|
|
322
|
+
createAuditLog: (params) => states.auditLog.create(params, instance),
|
|
323
|
+
getAuditLogs: states.auditLog.find.bind(states.auditLog),
|
|
324
|
+
|
|
317
325
|
// Routing
|
|
318
326
|
routerManager,
|
|
319
327
|
addRoutingSite: routerManager.addRoutingSite.bind(routerManager),
|
|
@@ -339,7 +347,7 @@ function ABTNode(options) {
|
|
|
339
347
|
handleRouting,
|
|
340
348
|
|
|
341
349
|
certManager,
|
|
342
|
-
|
|
350
|
+
updateCertificate: certManager.update.bind(certManager),
|
|
343
351
|
getCertificates,
|
|
344
352
|
addCertificate: certManager.add.bind(certManager),
|
|
345
353
|
deleteCertificate: certManager.remove.bind(certManager),
|
|
@@ -397,6 +405,7 @@ function ABTNode(options) {
|
|
|
397
405
|
...getRoutingCrons(),
|
|
398
406
|
...blockletManager.getCrons(),
|
|
399
407
|
DiskMonitor.getCron(),
|
|
408
|
+
...getStateCrons(states),
|
|
400
409
|
],
|
|
401
410
|
onError: (error, name) => {
|
|
402
411
|
states.notification.create({
|
|
@@ -410,22 +419,45 @@ function ABTNode(options) {
|
|
|
410
419
|
});
|
|
411
420
|
};
|
|
412
421
|
|
|
422
|
+
const createCLILog = (action) => {
|
|
423
|
+
instance
|
|
424
|
+
.createAuditLog(
|
|
425
|
+
{
|
|
426
|
+
action,
|
|
427
|
+
args: {},
|
|
428
|
+
context: formatContext({
|
|
429
|
+
user: { fullName: 'CLI', role: 'self', did: options.nodeDid },
|
|
430
|
+
headers: { 'user-agent': 'CLI' },
|
|
431
|
+
}),
|
|
432
|
+
result: null,
|
|
433
|
+
},
|
|
434
|
+
instance
|
|
435
|
+
)
|
|
436
|
+
.catch(console.error);
|
|
437
|
+
};
|
|
438
|
+
|
|
413
439
|
if (options.daemon) {
|
|
414
|
-
|
|
440
|
+
// 启动证书服务
|
|
441
|
+
certManager
|
|
442
|
+
.start()
|
|
443
|
+
.then(() => logger.info('start certificate manager service successfully'))
|
|
444
|
+
.catch((error) => logger.error('start certificate manager service failed', { error }));
|
|
415
445
|
|
|
416
446
|
if (process.env.NODE_ENV === 'development') {
|
|
417
447
|
initCron();
|
|
418
448
|
} else {
|
|
419
449
|
// We should only respond to pm2 events when node is alive
|
|
420
|
-
events.on(EVENTS.NODE_STARTED,
|
|
450
|
+
events.on(EVENTS.NODE_STARTED, () => {
|
|
421
451
|
pm2Events.resume();
|
|
422
452
|
initCron();
|
|
453
|
+
createCLILog('startServer');
|
|
423
454
|
});
|
|
424
455
|
}
|
|
425
456
|
}
|
|
426
457
|
|
|
427
458
|
events.on(EVENTS.NODE_STOPPED, () => {
|
|
428
459
|
pm2Events.pause();
|
|
460
|
+
createCLILog('stopServer');
|
|
429
461
|
});
|
|
430
462
|
|
|
431
463
|
return Object.assign(events, {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* eslint-disable no-continue */
|
|
2
|
+
/* eslint-disable no-await-in-loop */
|
|
3
|
+
/* eslint-disable no-underscore-dangle */
|
|
4
|
+
|
|
5
|
+
module.exports = async ({ states, printInfo }) => {
|
|
6
|
+
printInfo('Try to update blocklet server to 1.7.12...');
|
|
7
|
+
|
|
8
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
9
|
+
|
|
10
|
+
for (const blocklet of blocklets) {
|
|
11
|
+
let changed = false;
|
|
12
|
+
|
|
13
|
+
if (!blocklet.meta.bundleDid) {
|
|
14
|
+
blocklet.meta.bundleDid = blocklet.meta.did;
|
|
15
|
+
blocklet.meta.bundleName = blocklet.meta.name;
|
|
16
|
+
changed = true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
(blocklet.children || []).forEach((child) => {
|
|
20
|
+
if (!child.meta.bundleDid) {
|
|
21
|
+
child.meta.bundleDid = child.meta.did;
|
|
22
|
+
child.meta.bundleName = child.meta.name;
|
|
23
|
+
changed = true;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (changed) {
|
|
28
|
+
await states.blocklet.updateBlocklet(blocklet.meta.did, { meta: blocklet.meta, children: blocklet.children });
|
|
29
|
+
printInfo(`Blocklet meta in blocklet.db updated: ${blocklet.meta.did}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const blockletExtras = await states.blockletExtras.find({});
|
|
34
|
+
|
|
35
|
+
for (const extra of blockletExtras) {
|
|
36
|
+
let changed = false;
|
|
37
|
+
const children = await states.blockletExtras.getSettings(extra.did, 'children', []);
|
|
38
|
+
(children || []).forEach((child) => {
|
|
39
|
+
if (!child.meta.bundleDid) {
|
|
40
|
+
child.meta.bundleDid = child.meta.did;
|
|
41
|
+
child.meta.bundleName = child.meta.name;
|
|
42
|
+
changed = true;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (changed) {
|
|
47
|
+
await states.blockletExtras.setSettings(extra.did, { children });
|
|
48
|
+
printInfo(`Blocklet dynamic component meta in blocklet_extra.db updated: ${extra.did}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
package/lib/router/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
DEFAULT_IP_DOMAIN,
|
|
9
9
|
BLOCKLET_PROXY_PATH_PREFIX,
|
|
10
10
|
BLOCKLET_SITE_GROUP_SUFFIX,
|
|
11
|
+
GATEWAY_REQ_LIMIT,
|
|
11
12
|
} = require('@abtnode/constant');
|
|
12
13
|
const { BLOCKLET_UI_INTERFACES } = require('@blocklet/meta/lib/constants');
|
|
13
14
|
const logger = require('@abtnode/logger')('@abtnode/core:router');
|
|
@@ -107,12 +108,18 @@ class Router {
|
|
|
107
108
|
logger.info('updateRoutingTable routingTable:', { routingTable: this.routingTable });
|
|
108
109
|
logger.info('updateRoutingTable certificates:', { certificates: certificates.map((item) => item.domain) });
|
|
109
110
|
|
|
111
|
+
const requestLimit = nodeInfo.routing.requestLimit || { enable: false, rate: GATEWAY_REQ_LIMIT.min };
|
|
112
|
+
if (requestLimit.enabled) {
|
|
113
|
+
requestLimit.maxInstantRate = requestLimit.rate >= 20 ? 20 : requestLimit.rate + 20;
|
|
114
|
+
}
|
|
115
|
+
|
|
110
116
|
await this.provider.update({
|
|
111
117
|
routingTable: this.routingTable,
|
|
112
118
|
certificates,
|
|
113
119
|
commonHeaders: headers,
|
|
114
120
|
services,
|
|
115
121
|
nodeInfo: pick(nodeInfo, ['name', 'version', 'port', 'mode', 'enableWelcomePage', 'routing']),
|
|
122
|
+
requestLimit,
|
|
116
123
|
});
|
|
117
124
|
}
|
|
118
125
|
|
package/lib/states/README.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
1
|
# State DB
|
|
2
2
|
|
|
3
|
-
All
|
|
3
|
+
All Blocklet Server states are managed by files in this folder.
|
|
4
|
+
|
|
5
|
+
## blocklet
|
|
6
|
+
|
|
7
|
+
- `meta` meta of bundle _defined in @blocklet/meta/schema_
|
|
8
|
+
- `did`, `name`: component id, default from source meta, can be changed before install
|
|
9
|
+
- `title`, `description`: component info, default from source meta, can be changed before install
|
|
10
|
+
- `bundleDdid`, `bundleName`: bundle id, copy from source meta
|
|
11
|
+
- `version`: component version
|
|
12
|
+
- `price`: used for charging
|
|
13
|
+
- `logo`: component logo
|
|
14
|
+
- `logoUrl`: component logo (from store)
|
|
15
|
+
- `interfaces`: for resolving: runtime port; service config; web base prefix; api base prefix
|
|
16
|
+
- `environments`: component environment
|
|
17
|
+
- `scripts`: for hook of component lifecycle
|
|
18
|
+
- `engine`, `timeout`, `group`, `main`: related to process startup
|
|
19
|
+
- `dist`: bundle source
|
|
20
|
+
- `source`: type of bundle source
|
|
21
|
+
- `deployedFrom`: information of bundle source
|
|
22
|
+
- `sourceUrl`: url of bundle source
|
|
23
|
+
- `appDid`: app instance id _app only_
|
|
24
|
+
- `status`, `startedAt`, `installedAt`, `stoppedAt` app status
|
|
25
|
+
- `deletedAt` _component only_
|
|
26
|
+
- `mode` app mode
|
|
27
|
+
- `ports` runtime port resolved from meta
|
|
28
|
+
- `<ENV_NAME>`: `<port number>`
|
|
29
|
+
- `environments[]`: env variables generated by server, e.g. BLOCKLET_APP_SK, BLOCKLET_APP_DID
|
|
30
|
+
- `<key>`: `<value>`
|
|
31
|
+
- `children`: component of app, same structure as parent, can be nested
|
|
32
|
+
- `mountPoint` mount path of web site. _component only_
|
|
33
|
+
- `dynamic` is dynamically added. _component only_
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/* eslint-disable no-async-promise-executor */
|
|
2
|
+
const pick = require('lodash/pick');
|
|
3
|
+
const get = require('lodash/get');
|
|
4
|
+
const { getDisplayName } = require('@blocklet/meta/lib/util');
|
|
5
|
+
const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
|
|
6
|
+
const logger = require('@abtnode/logger')('@abtnode/core:states:audit-log');
|
|
7
|
+
|
|
8
|
+
const BaseState = require('./base');
|
|
9
|
+
|
|
10
|
+
const { parse } = require('../util/ua');
|
|
11
|
+
const { lookup } = require('../util/ip');
|
|
12
|
+
|
|
13
|
+
const getServerInfo = (info) => `[${info.name}](${info.routing.adminPath}/settings/about)`;
|
|
14
|
+
const getBlockletInfo = (blocklet, info) => `[${getDisplayName(blocklet)} v${blocklet.meta.version}](${info.routing.adminPath}/blocklets/${blocklet.meta.did})`; // prettier-ignore
|
|
15
|
+
const expandTeam = async (teamDid, info, node) => {
|
|
16
|
+
if (!teamDid) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (teamDid === info.did) {
|
|
21
|
+
return getServerInfo(info);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { blocklet } = node.states;
|
|
25
|
+
const doc = await blocklet.getBlocklet(teamDid);
|
|
26
|
+
return doc ? getBlockletInfo(doc, info) : '';
|
|
27
|
+
};
|
|
28
|
+
const expandSite = async (siteId, info, node) => {
|
|
29
|
+
if (!siteId) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { blocklet, site } = node.states;
|
|
34
|
+
const doc = await site.findOne({ _id: siteId });
|
|
35
|
+
if (!doc) {
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (doc.domain === '*') {
|
|
40
|
+
return 'default site';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (doc.domain === '') {
|
|
44
|
+
return getServerInfo(info);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (doc.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
48
|
+
const tmp = await blocklet.getBlocklet(doc.domain.replace(BLOCKLET_SITE_GROUP_SUFFIX, ''));
|
|
49
|
+
return tmp ? getBlockletInfo(tmp, info) : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return '';
|
|
53
|
+
};
|
|
54
|
+
const expandUser = async (teamDid, userDid, passportId, info, node) => {
|
|
55
|
+
if (!teamDid || !userDid) {
|
|
56
|
+
return ['', ''];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const user = await node.getUser({ teamDid, user: { did: userDid } });
|
|
60
|
+
if (!user) {
|
|
61
|
+
return ['', ''];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const passport = user.passports.find((x) => x.id === passportId);
|
|
65
|
+
|
|
66
|
+
if (teamDid === info.did) {
|
|
67
|
+
return [`[${user.fullName}](${info.routing.adminPath}/team/members)`, passport ? passport.name : ''];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [`[${user.fullName}](${info.routing.adminPath}/blocklets/${teamDid}/members)`, passport ? passport.name : ''];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create log content in markdown format
|
|
75
|
+
*
|
|
76
|
+
* @param {string} action - GraphQL query/mutation name
|
|
77
|
+
* @param {object} args - GraphQL arguments
|
|
78
|
+
* @param {object} context - request context: user, ip, user-agent, etc.
|
|
79
|
+
* @param {object} result - GraphQL resolve result
|
|
80
|
+
* @param {object} info - server info
|
|
81
|
+
* @param {object} node - server instance
|
|
82
|
+
* @return {string} the generated markdown source
|
|
83
|
+
*/
|
|
84
|
+
const getLogContent = async (action, args, context, result, info, node) => {
|
|
85
|
+
const [team, site, [user, passport]] = await Promise.all([
|
|
86
|
+
expandTeam(args.teamDid, info, node),
|
|
87
|
+
expandSite(args.id, info, node),
|
|
88
|
+
expandUser(args.teamDid, args.userDid || get(args, 'user.did') || args.ownerDid, args.passportId, info, node),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
switch (action) {
|
|
92
|
+
// blocklets
|
|
93
|
+
case 'installBlocklet':
|
|
94
|
+
return `installed blocklet ${getBlockletInfo(result, info)} from ${result.deployedFrom}`;
|
|
95
|
+
case 'startBlocklet':
|
|
96
|
+
return `started blocklet ${getBlockletInfo(result, info)}`;
|
|
97
|
+
case 'restartBlocklet':
|
|
98
|
+
return `restarted blocklet ${getBlockletInfo(result, info)}`;
|
|
99
|
+
case 'reloadBlocklet':
|
|
100
|
+
return `reloaded blocklet ${getBlockletInfo(result, info)}`;
|
|
101
|
+
case 'stopBlocklet':
|
|
102
|
+
return `stopped blocklet ${getBlockletInfo(result, info)}`;
|
|
103
|
+
case 'resetBlocklet':
|
|
104
|
+
return `reset blocklet ${getBlockletInfo(result, info)} data and config`;
|
|
105
|
+
case 'deleteBlocklet':
|
|
106
|
+
return `removed blocklet ${getBlockletInfo(result, info)} ${args.keepData ? 'but kept its data and config' : 'and its data and config'}`; // prettier-ignore
|
|
107
|
+
case 'installComponent':
|
|
108
|
+
return `added component from ${args.file ? 'Upload' : args.url} at **${args.mountPoint}** to ${getBlockletInfo(result, info)}`; // prettier-ignore
|
|
109
|
+
case 'deleteComponent':
|
|
110
|
+
return `removed component ${args.did} from blocklet ${getBlockletInfo(result, info)}`;
|
|
111
|
+
case 'configBlocklet':
|
|
112
|
+
return `updated following ${args.childDid ? `child ${args.childDid}` : ''} config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x => `- ${x.key}: ${x.value}\n`)}`; // prettier-ignore
|
|
113
|
+
case 'upgradeBlocklet':
|
|
114
|
+
return `upgraded blocklet ${getBlockletInfo(result, info)} to v${result.meta.version}`;
|
|
115
|
+
case 'updateChildBlocklets':
|
|
116
|
+
return `upgraded components for blocklet ${getBlockletInfo(result, info)}`;
|
|
117
|
+
|
|
118
|
+
// store
|
|
119
|
+
case 'addBlockletStore':
|
|
120
|
+
return `added blocklet store ${args.url}`;
|
|
121
|
+
case 'deleteBlockletStore':
|
|
122
|
+
return `removed blocklet store ${args.url}`;
|
|
123
|
+
case 'selectBlockletStore':
|
|
124
|
+
return `selected blocklet store ${args.url}`;
|
|
125
|
+
|
|
126
|
+
// teams: members/passports
|
|
127
|
+
case 'addUser':
|
|
128
|
+
if (args.passport) {
|
|
129
|
+
return `${args.reason} and received **${args.passport.name}** passport from ${team}`;
|
|
130
|
+
}
|
|
131
|
+
return `joined team ${team} by ${args.reason}`;
|
|
132
|
+
case 'updateUser':
|
|
133
|
+
return `${args.reason} and received **${args.passport.name}** passport from ${team}`;
|
|
134
|
+
case 'configWhoCanAccess':
|
|
135
|
+
return `updated access control policy to **${args.value}** for ${team} when ${args.reason}`;
|
|
136
|
+
case 'configPassportIssuance':
|
|
137
|
+
return `${args.enabled ? 'enabled' : 'disabled'} passport issuance for ${team}`;
|
|
138
|
+
case 'createPassportIssuance':
|
|
139
|
+
return `issued **${args.name}** passport to ${user} for ${team}, issuance id: ${result.id}`;
|
|
140
|
+
case 'processPassportIssuance':
|
|
141
|
+
return `claimed passport **${args.name}** from ${team}, issuance id: ${args.sessionId}`;
|
|
142
|
+
case 'revokeUserPassport':
|
|
143
|
+
return `revoked **${passport}** passport of user ${user} for ${team}`;
|
|
144
|
+
case 'enableUserPassport':
|
|
145
|
+
return `enabled **${passport}** passport of user ${user} for ${team}`;
|
|
146
|
+
case 'updateUserApproval':
|
|
147
|
+
return `${args.user.approved ? 'enabled' : 'disabled'} user ${user} for ${team}`;
|
|
148
|
+
case 'deletePassportIssuance':
|
|
149
|
+
return `removed passport issuance ${args.sessionId} from ${team}`;
|
|
150
|
+
case 'createInvitation':
|
|
151
|
+
return `created member invitation(${result.inviteId}: ${args.remark}) with **${args.role}** passport for ${team}`; // prettier-ignore
|
|
152
|
+
case 'deleteInvitation':
|
|
153
|
+
return `removed unused member invitation(${args.inviteId}) from ${team}`;
|
|
154
|
+
case 'createRole':
|
|
155
|
+
return `created passport ${args.name}(${args.title}) for ${team}`;
|
|
156
|
+
case 'updateRole':
|
|
157
|
+
return `updated passport ${args.role.name}(${args.role.title}) for ${team}`;
|
|
158
|
+
case 'updatePermissionsForRole':
|
|
159
|
+
return `granted following permissions to passport ${args.roleName} for ${team}: \n${args.grantNames.map(x => `- ${x}`).join('\n')}`; // prettier-ignore
|
|
160
|
+
case 'configTrustedPassports':
|
|
161
|
+
if (args.trustedPassports.length === 0) {
|
|
162
|
+
return `removed all trusted passport issuers for ${team}`;
|
|
163
|
+
}
|
|
164
|
+
return `updated trusted passport issuers to following for ${team}: \n${args.trustedPassports.map(x => `- ${x.remark}: ${x.issuerDid}`).join('\n')}`; // prettier-ignore
|
|
165
|
+
|
|
166
|
+
// accessKeys
|
|
167
|
+
case 'createAccessKey':
|
|
168
|
+
return `created access key ${result.accessKeyId} with passport ${args.passport}: ${args.remark}`;
|
|
169
|
+
case 'updateAccessKey':
|
|
170
|
+
return `updated access key ${result.accessKeyId} with following changes:\n - passport: ${args.passport}\n - remark: ${args.remark}`; // prettier-ignore
|
|
171
|
+
case 'deleteAccessKey':
|
|
172
|
+
return `deleted access key ${args.accessKeyId}`; // prettier-ignore
|
|
173
|
+
|
|
174
|
+
// integrations
|
|
175
|
+
case 'createWebHook':
|
|
176
|
+
return `added integration ${result._id}: \n- type: ${args.type}\n${args.params.map(x => `- ${x.name}: ${x.value}`).join('\n')}`; // prettier-ignore
|
|
177
|
+
case 'deleteWebHook':
|
|
178
|
+
return `deleted integration ${args.id}`;
|
|
179
|
+
|
|
180
|
+
// server
|
|
181
|
+
case 'updateNodeInfo':
|
|
182
|
+
return `updated basic server settings: \n${Object.keys(args).map(x => `- ${x}: ${args[x]}`).join('\n')}`; // prettier-ignore
|
|
183
|
+
case 'upgradeNodeVersion':
|
|
184
|
+
return `triggered server upgrade to ${info.nextVersion}`;
|
|
185
|
+
case 'startServer':
|
|
186
|
+
return 'server was started';
|
|
187
|
+
case 'stopServer':
|
|
188
|
+
return 'server was stopped';
|
|
189
|
+
|
|
190
|
+
// certificates
|
|
191
|
+
case 'addCertificate':
|
|
192
|
+
return `added certificate #${args.id}, domain ${result.domain}`;
|
|
193
|
+
case 'deleteCertificate':
|
|
194
|
+
return `deleted certificate #${args.id}`;
|
|
195
|
+
case 'updateCertificate':
|
|
196
|
+
return `updated certificate #${args.id}`;
|
|
197
|
+
case 'issueLetsEncryptCert':
|
|
198
|
+
return `tried to issue lets encrypt certificate for domain **${args.domain}**, ticket: ${result._id}`;
|
|
199
|
+
|
|
200
|
+
// router
|
|
201
|
+
case 'addDomainAlias':
|
|
202
|
+
return `added extra domain **${args.domainAlias}** to ${site}`; // prettier-ignore
|
|
203
|
+
case 'deleteDomainAlias':
|
|
204
|
+
return `removed extra domain **${args.domainAlias}** from ${site}`; // prettier-ignore
|
|
205
|
+
case 'updateGateway': {
|
|
206
|
+
let message = args.requestLimit.enabled ? `status: enabled, rate: ${args.requestLimit.rate}` : 'status: disabled';
|
|
207
|
+
message = `update gateway. ${message}`;
|
|
208
|
+
|
|
209
|
+
return message;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
default:
|
|
213
|
+
return action;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const getLogCategory = (action) => {
|
|
218
|
+
switch (action) {
|
|
219
|
+
// blocklets
|
|
220
|
+
case 'installBlocklet':
|
|
221
|
+
case 'installComponent':
|
|
222
|
+
case 'startBlocklet':
|
|
223
|
+
case 'restartBlocklet':
|
|
224
|
+
case 'reloadBlocklet':
|
|
225
|
+
case 'stopBlocklet':
|
|
226
|
+
case 'resetBlocklet':
|
|
227
|
+
case 'deleteBlocklet':
|
|
228
|
+
case 'deleteComponent':
|
|
229
|
+
case 'configBlocklet':
|
|
230
|
+
case 'upgradeBlocklet':
|
|
231
|
+
case 'updateChildBlocklets':
|
|
232
|
+
return 'blocklet';
|
|
233
|
+
|
|
234
|
+
// store
|
|
235
|
+
case 'addBlockletStore':
|
|
236
|
+
case 'deleteBlockletStore':
|
|
237
|
+
case 'selectBlockletStore':
|
|
238
|
+
return 'server';
|
|
239
|
+
|
|
240
|
+
// teams: members/passports
|
|
241
|
+
case 'addUser':
|
|
242
|
+
case 'updateUser':
|
|
243
|
+
case 'configWhoCanAccess':
|
|
244
|
+
case 'processPassportIssuance':
|
|
245
|
+
case 'configPassportIssuance':
|
|
246
|
+
case 'createPassportIssuance':
|
|
247
|
+
case 'deletePassportIssuance':
|
|
248
|
+
case 'revokeUserPassport':
|
|
249
|
+
case 'enableUserPassport':
|
|
250
|
+
case 'updateUserApproval':
|
|
251
|
+
case 'createInvitation':
|
|
252
|
+
case 'deleteInvitation':
|
|
253
|
+
case 'createRole':
|
|
254
|
+
case 'updateRole':
|
|
255
|
+
case 'updatePermissionsForRole':
|
|
256
|
+
case 'configTrustedPassports':
|
|
257
|
+
return 'team';
|
|
258
|
+
|
|
259
|
+
// accessKeys
|
|
260
|
+
case 'createAccessKey':
|
|
261
|
+
case 'updateAccessKey':
|
|
262
|
+
case 'deleteAccessKey':
|
|
263
|
+
return 'security';
|
|
264
|
+
|
|
265
|
+
// integrations
|
|
266
|
+
case 'createWebHook':
|
|
267
|
+
case 'deleteWebHook':
|
|
268
|
+
return 'integrations';
|
|
269
|
+
|
|
270
|
+
// server
|
|
271
|
+
case 'updateNodeInfo':
|
|
272
|
+
case 'upgradeNodeVersion':
|
|
273
|
+
case 'startServer':
|
|
274
|
+
case 'stopServer':
|
|
275
|
+
return 'server';
|
|
276
|
+
|
|
277
|
+
// certificates
|
|
278
|
+
case 'addCertificate':
|
|
279
|
+
case 'deleteCertificate':
|
|
280
|
+
case 'updateCertificate':
|
|
281
|
+
case 'issueLetsEncryptCert':
|
|
282
|
+
return 'certificates';
|
|
283
|
+
|
|
284
|
+
case 'updateGateway':
|
|
285
|
+
return 'gateway';
|
|
286
|
+
|
|
287
|
+
default:
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
class AuditLogState extends BaseState {
|
|
293
|
+
constructor(baseDir, options = {}) {
|
|
294
|
+
super(baseDir, { filename: 'audit-log.db', ...options });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create new audit log
|
|
299
|
+
*
|
|
300
|
+
* @param {string} action the api name
|
|
301
|
+
* @param {object} args the api args
|
|
302
|
+
* @param {object} result the api result
|
|
303
|
+
* @param {object} context the log context
|
|
304
|
+
* {
|
|
305
|
+
protocol: 'http',
|
|
306
|
+
user: {
|
|
307
|
+
meta: 'profile',
|
|
308
|
+
fullName: 'wangshijun',
|
|
309
|
+
email: 'shijun@arcblock.io',
|
|
310
|
+
type: 'profile',
|
|
311
|
+
did: 'z1jq5bGF64wnZiy29EbRyuQqUxmRHmSdt1Q',
|
|
312
|
+
pk: 'zHTcKwgi9DK8US8QQY9K7wQ3qF79CtrWYn6BYAgTQUeVQ',
|
|
313
|
+
passports: [ [Object] ],
|
|
314
|
+
approved: true,
|
|
315
|
+
locale: 'en',
|
|
316
|
+
firstLoginAt: '2022-04-27T22:55:51.788Z',
|
|
317
|
+
lastLoginAt: '2022-04-27T22:55:51.788Z',
|
|
318
|
+
_id: 'dvOIJJVUJHGxnWBU',
|
|
319
|
+
createdAt: '2022-04-27T22:55:51.789Z',
|
|
320
|
+
updatedAt: '2022-04-27T22:55:51.789Z',
|
|
321
|
+
role: 'owner',
|
|
322
|
+
passportId: 'z2iTzSDhwGQFtYXeya8kqCyXro1tRkwUuwN6K'
|
|
323
|
+
},
|
|
324
|
+
url: '/api/gql?locale=en',
|
|
325
|
+
query: { locale: 'en' },
|
|
326
|
+
hostname: '192.168.123.236',
|
|
327
|
+
port: 0,
|
|
328
|
+
ip: '127.0.0.1'
|
|
329
|
+
}
|
|
330
|
+
* @return {object} new doc
|
|
331
|
+
*/
|
|
332
|
+
create({ action, args, context, result }, node) {
|
|
333
|
+
return new Promise(async (resolve) => {
|
|
334
|
+
// Do not store secure configs in audit log
|
|
335
|
+
if (Array.isArray(args.configs)) {
|
|
336
|
+
args.configs.forEach((x) => {
|
|
337
|
+
if (x.secure) {
|
|
338
|
+
x.value = '******';
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const { ip, ua, user } = context;
|
|
345
|
+
const [info, geoInfo, uaInfo] = await Promise.all([node.states.node.read(), lookup(ip), parse(ua)]);
|
|
346
|
+
|
|
347
|
+
const data = await this.asyncDB.insert({
|
|
348
|
+
scope: args.teamDid || info.did, // server or blocklet did
|
|
349
|
+
action,
|
|
350
|
+
category: await getLogCategory(action, args, context, result, info, node),
|
|
351
|
+
content: (await getLogContent(action, args, context, result, info, node)).trim(),
|
|
352
|
+
actor: pick(user.actual || user, ['did', 'fullName', 'role']),
|
|
353
|
+
extra: args,
|
|
354
|
+
env: pick(uaInfo, ['browser', 'os', 'device']),
|
|
355
|
+
geo: pick(geoInfo || { country: '', city: '' }, ['country', 'city']),
|
|
356
|
+
ip,
|
|
357
|
+
ua,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
logger.info('create', data);
|
|
361
|
+
return resolve(data);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
logger.error('create error', { error: err, action, args, context });
|
|
364
|
+
return resolve(null);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async find({ scope, category, paging } = {}) {
|
|
370
|
+
const conditions = {};
|
|
371
|
+
if (scope) {
|
|
372
|
+
conditions.scope = scope;
|
|
373
|
+
}
|
|
374
|
+
if (category) {
|
|
375
|
+
conditions.category = category;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return this.paginate(conditions, { createdAt: -1 }, { pageSize: 20, ...paging });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = AuditLogState;
|
package/lib/states/blocklet.js
CHANGED
|
@@ -9,7 +9,7 @@ const detectPort = require('detect-port');
|
|
|
9
9
|
const Lock = require('@abtnode/util/lib/lock');
|
|
10
10
|
const security = require('@abtnode/util/lib/security');
|
|
11
11
|
const { fixPerson, fixInterfaces } = require('@blocklet/meta/lib/fix');
|
|
12
|
-
const { getDisplayName } = require('@blocklet/meta/lib/util');
|
|
12
|
+
const { getDisplayName, forEachBlocklet } = require('@blocklet/meta/lib/util');
|
|
13
13
|
const {
|
|
14
14
|
BlockletStatus,
|
|
15
15
|
BlockletSource,
|
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
const logger = require('@abtnode/logger')('state-blocklet');
|
|
22
22
|
|
|
23
23
|
const BaseState = require('./base');
|
|
24
|
-
const {
|
|
24
|
+
const { checkDuplicateComponents, ensureMeta } = require('../util/blocklet');
|
|
25
25
|
const { validateBlockletMeta } = require('../util');
|
|
26
26
|
|
|
27
27
|
const lock = new Lock('blocklet-port-assign-lock');
|
|
@@ -143,7 +143,6 @@ class BlockletState extends BaseState {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
addBlocklet({
|
|
146
|
-
did,
|
|
147
146
|
meta,
|
|
148
147
|
source = BlockletSource.registry,
|
|
149
148
|
status = BlockletStatus.added,
|
|
@@ -151,7 +150,7 @@ class BlockletState extends BaseState {
|
|
|
151
150
|
mode = BLOCKLET_MODES.PRODUCTION,
|
|
152
151
|
children: rawChildren = [],
|
|
153
152
|
} = {}) {
|
|
154
|
-
return this.getBlocklet(did).then(
|
|
153
|
+
return this.getBlocklet(meta.did).then(
|
|
155
154
|
(doc) =>
|
|
156
155
|
new Promise(async (resolve, reject) => {
|
|
157
156
|
if (doc) {
|
|
@@ -162,7 +161,10 @@ class BlockletState extends BaseState {
|
|
|
162
161
|
try {
|
|
163
162
|
fixPerson(meta);
|
|
164
163
|
fixInterfaces(meta);
|
|
165
|
-
|
|
164
|
+
let sanitized = validateBlockletMeta(meta);
|
|
165
|
+
// bundle info
|
|
166
|
+
sanitized = ensureMeta(sanitized);
|
|
167
|
+
sanitized = omit(sanitized, ['htmlAst']);
|
|
166
168
|
|
|
167
169
|
// get ports
|
|
168
170
|
await lock.acquire();
|
|
@@ -180,7 +182,7 @@ class BlockletState extends BaseState {
|
|
|
180
182
|
{
|
|
181
183
|
appDid: null, // will updated later when updating blocklet environments
|
|
182
184
|
mode,
|
|
183
|
-
meta:
|
|
185
|
+
meta: sanitized,
|
|
184
186
|
status,
|
|
185
187
|
source,
|
|
186
188
|
deployedFrom,
|
|
@@ -530,7 +532,7 @@ class BlockletState extends BaseState {
|
|
|
530
532
|
throw new Error('Cannot add self as a component');
|
|
531
533
|
}
|
|
532
534
|
|
|
533
|
-
checkDuplicateComponents([child
|
|
535
|
+
checkDuplicateComponents([child, ...newChildren]);
|
|
534
536
|
|
|
535
537
|
newChildren.push({
|
|
536
538
|
mountPoint,
|