@abtnode/core 1.6.6 → 1.6.10

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.
@@ -20,12 +20,13 @@ const hashFiles = require('@abtnode/util/lib/hash-files');
20
20
  const downloadFile = require('@abtnode/util/lib/download-file');
21
21
  const Lock = require('@abtnode/util/lib/lock');
22
22
  const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
23
+ const { updateBlocklet: updateDidDocument } = require('@abtnode/util/lib/did-document');
23
24
  const { BLOCKLET_PURCHASE_NFT_TYPE } = require('@abtnode/constant');
24
25
 
25
26
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
26
- const { isFreeBlocklet } = require('@blocklet/meta/lib/util');
27
+ const { isFreeBlocklet, isDeletableBlocklet, getRequiredMissingConfigs } = require('@blocklet/meta/lib/util');
27
28
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
28
- const { getRequiredMissingConfigs } = require('@blocklet/meta/lib/util');
29
+ const { getBlockletInfo } = require('@blocklet/meta/lib');
29
30
 
30
31
  const {
31
32
  BlockletStatus,
@@ -243,7 +244,7 @@ class BlockletManager extends BaseBlockletManager {
243
244
  hooks.preStart(b, {
244
245
  appDir: b.env.appDir,
245
246
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
246
- env: getRuntimeEnvironments(b, nodeEnvironments),
247
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
247
248
  did, // root blocklet did,
248
249
  progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
249
250
  }),
@@ -315,7 +316,7 @@ class BlockletManager extends BaseBlockletManager {
315
316
  hooks.preStop({
316
317
  appDir: b.env.appDir,
317
318
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
318
- env: getRuntimeEnvironments(b, nodeEnvironments),
319
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
319
320
  did, // root blocklet did
320
321
  notification: states.notification,
321
322
  context,
@@ -380,6 +381,9 @@ class BlockletManager extends BaseBlockletManager {
380
381
 
381
382
  try {
382
383
  const blocklet = await this.ensureBlocklet(did);
384
+ if (isDeletableBlocklet(blocklet) === false) {
385
+ throw new Error('Blocklet is protected from accidental deletion');
386
+ }
383
387
 
384
388
  const nodeEnvironments = await states.node.getEnvironments();
385
389
 
@@ -388,7 +392,7 @@ class BlockletManager extends BaseBlockletManager {
388
392
  hooks.preUninstall({
389
393
  appDir: b.env.appDir,
390
394
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
391
- env: getRuntimeEnvironments(b, nodeEnvironments),
395
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
392
396
  did, // root blocklet did
393
397
  notification: states.notification,
394
398
  context,
@@ -513,7 +517,7 @@ class BlockletManager extends BaseBlockletManager {
513
517
 
514
518
  // run hook
515
519
  const nodeEnvironments = await states.node.getEnvironments();
516
- newConfigs.forEach((x) => {
520
+ for (const x of newConfigs) {
517
521
  if (x.key === 'BLOCKLET_APP_SK') {
518
522
  try {
519
523
  fromSecretKey(x.value);
@@ -524,6 +528,13 @@ class BlockletManager extends BaseBlockletManager {
524
528
  throw new Error('Invalid custom blocklet secret key');
525
529
  }
526
530
  }
531
+
532
+ // Ensure sk is not used by other blocklets, otherwise we may encounter appDid collision
533
+ const blocklets = await states.blocklet.getBlocklets({});
534
+ const others = blocklets.filter((b) => b.meta.did !== did);
535
+ if (others.some((b) => b.environments.find((e) => e.key === 'BLOCKLET_APP_SK').value === x.value)) {
536
+ throw new Error('Invalid custom blocklet secret key: already used by another blocklet');
537
+ }
527
538
  }
528
539
 
529
540
  if (x.key === 'BLOCKLET_WALLET_TYPE') {
@@ -532,8 +543,16 @@ class BlockletManager extends BaseBlockletManager {
532
543
  }
533
544
  }
534
545
 
546
+ if (x.key === 'BLOCKLET_DELETABLE') {
547
+ if (['yes', 'no'].includes(x.value) === false) {
548
+ throw new Error('BLOCKLET_DELETABLE must be either "yes" or "no"');
549
+ }
550
+ }
551
+
535
552
  blocklet.configObj[x.key] = x.value;
536
- });
553
+ }
554
+
555
+ // FIXME: we should also call preConfig for child blocklets
537
556
  await hooks.preConfig({
538
557
  appDir: blocklet.env.appDir,
539
558
  hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
@@ -897,7 +916,7 @@ class BlockletManager extends BaseBlockletManager {
897
916
  };
898
917
 
899
918
  const childConfigs = await states.blockletExtras.getChildConfigs(did, childDid);
900
- fillBlockletConfigs(child, childConfigs);
919
+ fillBlockletConfigs(child, childConfigs, blocklet);
901
920
  }
902
921
 
903
922
  return blocklet;
@@ -1496,6 +1515,15 @@ class BlockletManager extends BaseBlockletManager {
1496
1515
  return null;
1497
1516
  }
1498
1517
 
1518
+ // When new version found from the store where the blocklet was installed from, we should use that store first
1519
+ if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
1520
+ const latestFromSameRegistry = versions.find((x) => x.registryUrl === blocklet.deployedFrom);
1521
+ if (latestFromSameRegistry) {
1522
+ return latestFromSameRegistry;
1523
+ }
1524
+ }
1525
+
1526
+ // Otherwise try upgrading from other store
1499
1527
  let latestBlockletVersion = versions[0];
1500
1528
  versions.forEach((item) => {
1501
1529
  if (semver.lt(latestBlockletVersion.version, item.version)) {
@@ -1561,7 +1589,7 @@ class BlockletManager extends BaseBlockletManager {
1561
1589
 
1562
1590
  await this._setConfigs(did);
1563
1591
 
1564
- logger.info('blocklet added to database', { meta });
1592
+ logger.info('blocklet added to database', { did: meta.did });
1565
1593
 
1566
1594
  const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1567
1595
  this.emit(BlockletEvents.added, blocklet1);
@@ -1855,7 +1883,7 @@ class BlockletManager extends BaseBlockletManager {
1855
1883
  const postInstall = (b) =>
1856
1884
  hooks.postInstall({
1857
1885
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1858
- env: getRuntimeEnvironments(b, nodeEnvironments),
1886
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1859
1887
  appDir: blocklet.env.appDir,
1860
1888
  did, // root blocklet did
1861
1889
  notification: states.notification,
@@ -1865,7 +1893,19 @@ class BlockletManager extends BaseBlockletManager {
1865
1893
 
1866
1894
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
1867
1895
  blocklet = await this.ensureBlocklet(did);
1868
- logger.info('blocklet installed', { source, meta });
1896
+ logger.info('blocklet installed', { source, did: meta.did });
1897
+ if (process.env.NODE_ENV !== 'test') {
1898
+ const nodeInfo = await states.node.read();
1899
+ const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
1900
+ const updateDidDnsResult = await updateDidDocument({
1901
+ wallet: blockletInfo.wallet,
1902
+ domain: nodeInfo.didDomain,
1903
+ nodeDid: nodeInfo.did,
1904
+ didRegistryUrl: nodeInfo.didRegistry,
1905
+ });
1906
+ logger.info('updated did document', { updateDidDnsResult, blockletId: blocklet.meta.did });
1907
+ }
1908
+
1869
1909
  this.emit(BlockletEvents.installed, { blocklet, context });
1870
1910
 
1871
1911
  states.notification.create({
@@ -1943,7 +1983,7 @@ class BlockletManager extends BaseBlockletManager {
1943
1983
  const postInstall = (b) =>
1944
1984
  hooks.postInstall({
1945
1985
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1946
- env: getRuntimeEnvironments(b, nodeEnvironments),
1986
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1947
1987
  appDir: b.env.appDir,
1948
1988
  did, // root blocklet did
1949
1989
  notification: states.notification,
@@ -1959,7 +1999,7 @@ class BlockletManager extends BaseBlockletManager {
1959
1999
  blocklet: b,
1960
2000
  oldVersion,
1961
2001
  newVersion: version,
1962
- env: getRuntimeEnvironments(b, nodeEnvironments),
2002
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1963
2003
  appDir: b.env.appDir,
1964
2004
  did: b.meta.did,
1965
2005
  notification: states.notification,
@@ -2251,8 +2291,9 @@ class BlockletManager extends BaseBlockletManager {
2251
2291
  try {
2252
2292
  const state = await states.blocklet.getBlocklet(did);
2253
2293
  if (state && util.shouldUpdateBlockletStatus(state.status)) {
2254
- const result = await states.blocklet.setBlockletStatus(did, pm2StatusMap[pm2Status]);
2255
- logger.info('sync pm2 status to blocklet', { result });
2294
+ const newStatus = pm2StatusMap[pm2Status];
2295
+ await states.blocklet.setBlockletStatus(did, newStatus);
2296
+ logger.info('sync pm2 status to blocklet', { did, pm2Status, newStatus });
2256
2297
  }
2257
2298
  } catch (error) {
2258
2299
  logger.error('sync pm2 status to blocklet failed', { did, pm2Status, error });
package/lib/cert.js ADDED
@@ -0,0 +1,124 @@
1
+ const { EventEmitter } = require('events');
2
+ const CertificateManager = require('@abtnode/certificate-manager/sdk/manager');
3
+ const logger = require('@abtnode/logger')('@abtnode/core:cert');
4
+ const states = require('./states');
5
+
6
+ const onCertExpired = async (cert) => {
7
+ logger.info('send certificate expire notification', { domain: cert.domain });
8
+ states.notification.create({
9
+ title: 'SSL Certificate Expired',
10
+ description: `Your SSL certificate for domain ${cert.domain} has expired, please update it in Blocklet Server`,
11
+ severity: 'error',
12
+ entityType: 'certificate',
13
+ entityId: cert.id,
14
+ });
15
+ };
16
+
17
+ const onCertAboutExpire = (cert) => {
18
+ logger.info('send certificate about-expire notification', { domain: cert.domain });
19
+ states.notification.create({
20
+ title: 'SSL Certificate Expire Warning',
21
+ description: `Your SSL certificate for domain ${cert.domain} will expire in ${
22
+ cert.expireInDays
23
+ } days (on ${new Date(cert.validTo).toLocaleString()}), please remember to update it in Blocklet Server`,
24
+ severity: 'warning',
25
+ entityType: 'certificate',
26
+ entityId: cert.id, // eslint-disable-line no-underscore-dangle
27
+ });
28
+ };
29
+
30
+ const onCertIssued = (cert) => {
31
+ states.notification.create({
32
+ title: 'Certificate Issued',
33
+ description: `The ${cert.domain} certificate is issued successfully`,
34
+ severity: 'success',
35
+ entityType: 'certificate',
36
+ entityId: cert.id,
37
+ });
38
+ };
39
+
40
+ class Cert extends EventEmitter {
41
+ constructor({ maintainerEmail, dataDir }) {
42
+ super();
43
+
44
+ this.manager = new CertificateManager({ maintainerEmail, dataDir });
45
+
46
+ this.manager.on('cert.issued', (cert) => {
47
+ this.emit('cert.issued', cert);
48
+ onCertIssued(cert);
49
+ });
50
+
51
+ this.manager.on('cert.expired', onCertExpired);
52
+ this.manager.on('cert.about_to_expire', onCertAboutExpire);
53
+ }
54
+
55
+ start() {
56
+ return this.manager.start();
57
+ }
58
+
59
+ static fixCertificate(entity) {
60
+ // Hack: this logic exists because gql does not allow line breaks in arg values
61
+ entity.privateKey = entity.privateKey.split('|').join('\n');
62
+ entity.certificate = entity.certificate.split('|').join('\n');
63
+ }
64
+
65
+ getAll() {
66
+ return this.manager.getAll();
67
+ }
68
+
69
+ getAllNormal() {
70
+ return this.manager.getAllNormal();
71
+ }
72
+
73
+ getNormalByDomain(domain) {
74
+ return this.manager.getNormalByDomain(domain);
75
+ }
76
+
77
+ getByDomain(domain) {
78
+ return this.manager.getByDomain(domain);
79
+ }
80
+
81
+ async add(data) {
82
+ if (!data.certificate || !data.privateKey) {
83
+ throw new Error('certificate and privateKey is required');
84
+ }
85
+
86
+ Cert.fixCertificate(data);
87
+
88
+ await this.manager.add(data);
89
+ logger.info('add certificate result', { name: data.name });
90
+ this.emit('cert.added', data);
91
+ }
92
+
93
+ async issue({ domain }) {
94
+ logger.info(`generate certificate for ${domain}`);
95
+
96
+ return this.manager.issue(domain);
97
+ }
98
+
99
+ async upsertByDomain(data) {
100
+ return this.manager.upsertByDomain(data);
101
+ }
102
+
103
+ async update(data) {
104
+ return this.manager.update(data.id, { name: data.name, public: data.public });
105
+ }
106
+
107
+ async remove({ id }) {
108
+ await this.manager.remove(id);
109
+ logger.info('delete certificate', { id });
110
+ this.emit('cert.removed');
111
+
112
+ return {};
113
+ }
114
+
115
+ async addWithoutValidations(data) {
116
+ return this.manager.addWithoutValidations(data);
117
+ }
118
+
119
+ async updateWithoutValidations(id, data) {
120
+ return this.manager.updateWithoutValidations(id, data);
121
+ }
122
+ }
123
+
124
+ module.exports = Cert;
package/lib/event.js CHANGED
@@ -1,5 +1,8 @@
1
+ const get = require('lodash/get');
2
+ const cloneDeep = require('lodash/cloneDeep');
1
3
  const { EventEmitter } = require('events');
2
4
  const { BLOCKLET_MODES } = require('@blocklet/meta/lib/constants');
5
+ const { wipeSensitiveData } = require('@blocklet/meta/lib/util');
3
6
  const logger = require('@abtnode/logger')('@abtnode/core:event');
4
7
  const { BlockletStatus, BlockletSource, BlockletEvents } = require('@blocklet/meta/lib/constants');
5
8
 
@@ -43,9 +46,13 @@ module.exports = ({
43
46
  let eventHandler = null;
44
47
 
45
48
  const onEvent = (name, data) => {
46
- events.emit(name, data);
49
+ let safeData = data;
50
+ if (get(data, 'meta.did', '')) {
51
+ safeData = wipeSensitiveData(cloneDeep(data));
52
+ }
53
+ events.emit(name, safeData);
47
54
  if (typeof eventHandler === 'function') {
48
- eventHandler({ name, data });
55
+ eventHandler({ name, data: safeData });
49
56
  }
50
57
  };
51
58
 
package/lib/index.js CHANGED
@@ -11,6 +11,7 @@ const {
11
11
  toBlockletSource,
12
12
  } = require('@blocklet/meta/lib/constants');
13
13
  const { listProviders } = require('@abtnode/router-provider');
14
+ const { DEFAULT_CERTIFICATE_EMAIL } = require('@abtnode/constant');
14
15
 
15
16
  const RoutingSnapshot = require('./states/routing-snapshot');
16
17
  const BlockletRegistry = require('./blocklet/registry');
@@ -22,6 +23,7 @@ const NodeAPI = require('./api/node');
22
23
  const TeamAPI = require('./api/team');
23
24
  const WebHook = require('./webhook');
24
25
  const states = require('./states');
26
+ const Cert = require('./cert');
25
27
 
26
28
  const IP = require('./util/ip');
27
29
  const DomainStatus = require('./util/domain-status');
@@ -108,7 +110,11 @@ function ABTNode(options) {
108
110
  // Initialize storage
109
111
  states.init(dataDirs, options);
110
112
 
111
- const routerManager = new RouterManager();
113
+ const certManager = new Cert({
114
+ maintainerEmail: DEFAULT_CERTIFICATE_EMAIL,
115
+ dataDir: dataDirs.certManagerModule,
116
+ });
117
+ const routerManager = new RouterManager({ certManager });
112
118
  const routingSnapshot = new RoutingSnapshot({
113
119
  baseDir: dataDirs.core,
114
120
  getRoutingData: async () => {
@@ -117,7 +123,6 @@ function ABTNode(options) {
117
123
  return { sites };
118
124
  },
119
125
  });
120
-
121
126
  const blockletRegistry = new BlockletRegistry();
122
127
  const blockletManager = new BlockletManager({
123
128
  dataDirs,
@@ -141,10 +146,10 @@ function ABTNode(options) {
141
146
  getCertificates,
142
147
  getSitesFromSnapshot,
143
148
  getRoutingCrons,
144
- } = getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager });
149
+ ensureDashboardCertificate,
150
+ } = getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager });
145
151
 
146
152
  const teamManager = new TeamManager({ nodeDid: options.nodeDid, dataDirs, states });
147
-
148
153
  const nodeAPI = new NodeAPI(states.node, blockletRegistry);
149
154
  const teamAPI = new TeamAPI({ states, teamManager });
150
155
 
@@ -162,10 +167,10 @@ function ABTNode(options) {
162
167
  ensureBlockletRoutingForUpgrade,
163
168
  removeBlockletRouting,
164
169
  takeRoutingSnapshot,
165
- routerManager,
166
170
  handleRouting,
167
171
  domainStatus,
168
172
  teamAPI,
173
+ certManager,
169
174
  });
170
175
 
171
176
  const isInitialized = async () => {
@@ -311,6 +316,7 @@ function ABTNode(options) {
311
316
  takeRoutingSnapshot,
312
317
  getSitesFromSnapshot,
313
318
  ensureDashboardRouting,
319
+ ensureDashboardCertificate,
314
320
 
315
321
  addDomainAlias: routerManager.addDomainAlias.bind(routerManager),
316
322
  deleteDomainAlias: routerManager.deleteDomainAlias.bind(routerManager),
@@ -319,11 +325,13 @@ function ABTNode(options) {
319
325
  checkDomains: domainStatus.checkDomainsStatus.bind(domainStatus),
320
326
  handleRouting,
321
327
 
322
- updateNginxHttpsCert: routerManager.updateNginxHttpsCert.bind(routerManager),
328
+ certManager,
329
+ updateNginxHttpsCert: certManager.update.bind(certManager), // TODO: 重命名:updateCert
323
330
  getCertificates,
324
- addCertificate: routerManager.addCertificate.bind(routerManager),
325
- deleteCertificate: routerManager.deleteCertificate.bind(routerManager),
326
- findCertificateByDomain: routerManager.findCertificateByDomain.bind(routerManager),
331
+ addCertificate: certManager.add.bind(certManager),
332
+ deleteCertificate: certManager.remove.bind(certManager),
333
+ findCertificateByDomain: certManager.getByDomain.bind(certManager),
334
+ issueLetsEncryptCert: certManager.issue.bind(certManager),
327
335
 
328
336
  // Access Key
329
337
  getAccessKeys: states.accessKey.list.bind(states.accessKey),
@@ -385,11 +393,23 @@ function ABTNode(options) {
385
393
  };
386
394
 
387
395
  if (options.daemon) {
396
+ certManager.start(); // 启动证书服务
397
+
388
398
  if (process.env.NODE_ENV === 'development') {
389
399
  initCron();
390
400
  } else {
391
401
  // We should only respond to pm2 events when node is alive
392
- events.on('node.started', () => {
402
+ events.on('node.started', async () => {
403
+ const info = await states.node.read();
404
+ certManager
405
+ .issue({ domain: `${info.did.toLowerCase()}.${info.didDomain}` })
406
+ .then(() => {
407
+ logger.info('add issue daemon certificate job');
408
+ })
409
+ .catch((error) => {
410
+ logger.error('issue daemon certificate job failed', { error });
411
+ });
412
+
393
413
  pm2Events.resume();
394
414
  initCron();
395
415
  });
@@ -0,0 +1,30 @@
1
+ module.exports = async ({ states, node, printInfo }) => {
2
+ printInfo('Migrate certificates...');
3
+ const certs = await states.certificate.find(
4
+ {},
5
+ {
6
+ domain: 1,
7
+ privateKey: 1,
8
+ certificate: 1,
9
+ createdAt: 1,
10
+ updatedAt: 1,
11
+ }
12
+ );
13
+
14
+ const tasks = certs.map((cert) => {
15
+ const data = {
16
+ _id: cert.id,
17
+ domain: cert.domain,
18
+ privateKey: cert.privateKey,
19
+ certificate: cert.certificate,
20
+ createdAt: cert.createdAt,
21
+ updatedAt: cert.updatedAt,
22
+ source: 'lets_encrypt',
23
+ status: 'generated',
24
+ };
25
+
26
+ return node.certManager.addWithoutValidations(data);
27
+ });
28
+
29
+ await Promise.all(tasks);
30
+ };
@@ -0,0 +1,38 @@
1
+ /* eslint-disable no-await-in-loop */
2
+
3
+ const { DEFAULT_DID_REGISTRY, DEFAULT_DID_DOMAIN } = require('@abtnode/constant');
4
+ const { isEmpty, omit } = require('lodash');
5
+
6
+ const updateNodeInfo = async ({ printInfo, states }) => {
7
+ // add didRegistry and didDomain
8
+ const info = await states.node.read();
9
+ const data = {};
10
+ if (!info.didRegistry) {
11
+ data.didRegistry = DEFAULT_DID_REGISTRY;
12
+ }
13
+
14
+ if (!info.didDomain) {
15
+ data.didDomain = DEFAULT_DID_DOMAIN;
16
+ }
17
+
18
+ if (!isEmpty(data)) {
19
+ await states.node.update({ _id: info._id }, { $set: data });
20
+ }
21
+
22
+ printInfo('update did registry and did domain successfully');
23
+ };
24
+
25
+ const updateCertificate = async ({ node, printInfo }) => {
26
+ const certs = await node.certManager.getAllNormal();
27
+ for (const cert of certs || []) {
28
+ await node.certManager.updateWithoutValidations(cert.id, omit(cert, 'id'));
29
+ }
30
+
31
+ printInfo('update certificate successfully');
32
+ };
33
+
34
+ /* eslint-disable no-underscore-dangle */
35
+ module.exports = async ({ node, printInfo, states }) => {
36
+ await updateNodeInfo({ printInfo, states });
37
+ await updateCertificate({ node, printInfo, states });
38
+ };