@abtnode/core 1.6.6 → 1.6.7
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/cert.js +116 -0
- package/lib/index.js +18 -8
- package/lib/migrations/1.6.7-certificate.js +30 -0
- package/lib/router/helper.js +99 -107
- package/lib/router/manager.js +6 -5
- package/lib/states/base.js +3 -220
- package/lib/states/index.js +4 -21
- package/lib/states/node.js +2 -1
- package/lib/util/index.js +2 -0
- package/package.json +22 -20
package/lib/cert.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
getByDomain(domain) {
|
|
74
|
+
return this.manager.getByDomain(domain);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async add(data) {
|
|
78
|
+
if (!data.certificate || !data.privateKey) {
|
|
79
|
+
throw new Error('certificate and privateKey is required');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Cert.fixCertificate(data);
|
|
83
|
+
|
|
84
|
+
await this.manager.add(data);
|
|
85
|
+
logger.info('add certificate result', { name: data.name });
|
|
86
|
+
this.emit('cert.added', data);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async issue({ domain }) {
|
|
90
|
+
logger.info(`generate certificate for ${domain}`);
|
|
91
|
+
|
|
92
|
+
return this.manager.issue(domain);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async upsertByDomain(data) {
|
|
96
|
+
return this.manager.upsertByDomain(data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async update(data) {
|
|
100
|
+
return this.manager.update(data.id, { name: data.name, public: data.public });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async remove({ id }) {
|
|
104
|
+
await this.manager.remove(id);
|
|
105
|
+
logger.info('delete certificate', { id });
|
|
106
|
+
this.emit('cert.removed');
|
|
107
|
+
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async addWithoutValidations(data) {
|
|
112
|
+
return this.manager.addWithoutValidations(data);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = Cert;
|
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
|
|
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,
|
|
@@ -127,6 +132,8 @@ function ABTNode(options) {
|
|
|
127
132
|
daemon: options.daemon,
|
|
128
133
|
});
|
|
129
134
|
|
|
135
|
+
certManager.start(); // 启动证书服务
|
|
136
|
+
|
|
130
137
|
const {
|
|
131
138
|
handleRouting,
|
|
132
139
|
getRoutingRulesByDid,
|
|
@@ -141,10 +148,10 @@ function ABTNode(options) {
|
|
|
141
148
|
getCertificates,
|
|
142
149
|
getSitesFromSnapshot,
|
|
143
150
|
getRoutingCrons,
|
|
144
|
-
|
|
151
|
+
ensureDashboardCertificate,
|
|
152
|
+
} = getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager });
|
|
145
153
|
|
|
146
154
|
const teamManager = new TeamManager({ nodeDid: options.nodeDid, dataDirs, states });
|
|
147
|
-
|
|
148
155
|
const nodeAPI = new NodeAPI(states.node, blockletRegistry);
|
|
149
156
|
const teamAPI = new TeamAPI({ states, teamManager });
|
|
150
157
|
|
|
@@ -311,6 +318,7 @@ function ABTNode(options) {
|
|
|
311
318
|
takeRoutingSnapshot,
|
|
312
319
|
getSitesFromSnapshot,
|
|
313
320
|
ensureDashboardRouting,
|
|
321
|
+
ensureDashboardCertificate,
|
|
314
322
|
|
|
315
323
|
addDomainAlias: routerManager.addDomainAlias.bind(routerManager),
|
|
316
324
|
deleteDomainAlias: routerManager.deleteDomainAlias.bind(routerManager),
|
|
@@ -319,11 +327,13 @@ function ABTNode(options) {
|
|
|
319
327
|
checkDomains: domainStatus.checkDomainsStatus.bind(domainStatus),
|
|
320
328
|
handleRouting,
|
|
321
329
|
|
|
322
|
-
|
|
330
|
+
certManager,
|
|
331
|
+
updateNginxHttpsCert: certManager.update.bind(certManager), // TODO: 重命名:updateCert
|
|
323
332
|
getCertificates,
|
|
324
|
-
addCertificate:
|
|
325
|
-
deleteCertificate:
|
|
326
|
-
findCertificateByDomain:
|
|
333
|
+
addCertificate: certManager.add.bind(certManager),
|
|
334
|
+
deleteCertificate: certManager.remove.bind(certManager),
|
|
335
|
+
findCertificateByDomain: certManager.getByDomain.bind(certManager),
|
|
336
|
+
issueLetsEncryptCert: certManager.issue.bind(certManager),
|
|
327
337
|
|
|
328
338
|
// Access Key
|
|
329
339
|
getAccessKeys: states.accessKey.list.bind(states.accessKey),
|
|
@@ -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
|
+
};
|
package/lib/router/helper.js
CHANGED
|
@@ -21,15 +21,13 @@ const {
|
|
|
21
21
|
NAME_FOR_WELLKNOWN_SITE,
|
|
22
22
|
DEFAULT_HTTP_PORT,
|
|
23
23
|
DEFAULT_HTTPS_PORT,
|
|
24
|
-
DAY_IN_MS,
|
|
25
24
|
NODE_MODES,
|
|
26
25
|
ROUTING_RULE_TYPES,
|
|
27
26
|
CERTIFICATE_EXPIRES_OFFSET,
|
|
28
|
-
CERTIFICATE_EXPIRES_WARNING_OFFSET,
|
|
29
|
-
DEFAULT_DAEMON_PORT,
|
|
30
27
|
DEFAULT_SERVICE_PATH,
|
|
31
28
|
SLOT_FOR_IP_DNS_SITE,
|
|
32
29
|
BLOCKLET_SITE_GROUP_SUFFIX,
|
|
30
|
+
WELLKNOWN_ACME_CHALLENGE_PREFIX,
|
|
33
31
|
} = require('@abtnode/constant');
|
|
34
32
|
const {
|
|
35
33
|
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
@@ -403,11 +401,10 @@ const decompressCertificates = async (source, dest) => {
|
|
|
403
401
|
return dest;
|
|
404
402
|
};
|
|
405
403
|
|
|
406
|
-
module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager }) {
|
|
404
|
+
module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager }) {
|
|
407
405
|
const nodeState = states.node;
|
|
408
406
|
const blockletState = states.blocklet;
|
|
409
407
|
const siteState = states.site;
|
|
410
|
-
const httpsCertState = states.certificate;
|
|
411
408
|
const notification = states.notification;
|
|
412
409
|
|
|
413
410
|
// site level duplication detection
|
|
@@ -468,58 +465,105 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
468
465
|
const certificate = fs.readFileSync(certificateFilePath).toString();
|
|
469
466
|
const privateKey = fs.readFileSync(privateKeyFilePath).toString();
|
|
470
467
|
|
|
471
|
-
await
|
|
468
|
+
await certManager.upsertByDomain({
|
|
469
|
+
domain: dashboardDomain,
|
|
470
|
+
privateKey,
|
|
471
|
+
certificate,
|
|
472
|
+
isProtected: true,
|
|
473
|
+
});
|
|
472
474
|
logger.info('dashboard certificate updated');
|
|
473
475
|
} catch (error) {
|
|
474
|
-
logger.error('
|
|
476
|
+
logger.error('update dashboard certificate failed', { error });
|
|
477
|
+
throw error;
|
|
475
478
|
} finally {
|
|
476
479
|
fs.removeSync(destFolder);
|
|
477
480
|
}
|
|
478
481
|
};
|
|
479
482
|
|
|
483
|
+
const ensureDashboardCertificate = async () => {
|
|
484
|
+
const info = await nodeState.read();
|
|
485
|
+
const downloadUrl = get(info, 'routing.dashboardCertDownloadAddress', '');
|
|
486
|
+
const dashboardDomain = get(info, 'routing.dashboardDomain', '');
|
|
487
|
+
if (!dashboardDomain || !downloadUrl) {
|
|
488
|
+
throw new Error('dashboardCertDownloadAddress and dashboardDomain are not found in the routing configs');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const cert = await certManager.getByDomain(dashboardDomain);
|
|
492
|
+
if (cert) {
|
|
493
|
+
return { status: 'existed' };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
|
|
497
|
+
await updateDashboardCertificate({ checkExpire: false });
|
|
498
|
+
logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
|
|
499
|
+
return { status: 'downloaded' };
|
|
500
|
+
};
|
|
501
|
+
|
|
480
502
|
const addWellknownSite = async (sites, context) => {
|
|
481
503
|
const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
|
|
482
504
|
|
|
483
505
|
try {
|
|
484
|
-
const
|
|
506
|
+
const info = await nodeState.read();
|
|
507
|
+
const proxyTarget = {
|
|
508
|
+
port: info.port,
|
|
509
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
510
|
+
interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
|
|
511
|
+
};
|
|
512
|
+
|
|
485
513
|
const didResolverWellknownRule = {
|
|
486
|
-
from: { pathPrefix },
|
|
487
|
-
to:
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
},
|
|
514
|
+
from: { pathPrefix: joinUrl(WELLKNOWN_PATH_PREFIX, '/did.json') },
|
|
515
|
+
to: proxyTarget,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const acmeChallengeWellknownRule = {
|
|
519
|
+
from: { pathPrefix: WELLKNOWN_ACME_CHALLENGE_PREFIX },
|
|
520
|
+
to: proxyTarget,
|
|
492
521
|
};
|
|
493
522
|
|
|
494
523
|
if (site) {
|
|
495
|
-
|
|
496
|
-
|
|
524
|
+
let changed = false;
|
|
525
|
+
const exists = (prefix) => !!site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(prefix));
|
|
526
|
+
|
|
527
|
+
if (!exists(didResolverWellknownRule.from.pathPrefix)) {
|
|
528
|
+
await routerManager.addRoutingRule(
|
|
529
|
+
{
|
|
530
|
+
id: site.id,
|
|
531
|
+
rule: didResolverWellknownRule,
|
|
532
|
+
},
|
|
533
|
+
context
|
|
534
|
+
);
|
|
535
|
+
changed = true;
|
|
497
536
|
}
|
|
498
537
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
context
|
|
505
|
-
);
|
|
506
|
-
} else {
|
|
507
|
-
await routerManager.addRoutingSite(
|
|
508
|
-
{
|
|
509
|
-
site: {
|
|
510
|
-
domain: DOMAIN_FOR_INTERNAL_SITE,
|
|
511
|
-
port: await getWellknownSitePort(),
|
|
512
|
-
name: NAME_FOR_WELLKNOWN_SITE,
|
|
513
|
-
rules: [didResolverWellknownRule],
|
|
514
|
-
isProtected: true,
|
|
538
|
+
if (!exists(acmeChallengeWellknownRule.from.pathPrefix)) {
|
|
539
|
+
await routerManager.addRoutingRule(
|
|
540
|
+
{
|
|
541
|
+
id: site.id,
|
|
542
|
+
rule: acmeChallengeWellknownRule,
|
|
515
543
|
},
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
544
|
+
context
|
|
545
|
+
);
|
|
546
|
+
changed = true;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return changed;
|
|
521
550
|
}
|
|
522
551
|
|
|
552
|
+
await routerManager.addRoutingSite(
|
|
553
|
+
{
|
|
554
|
+
site: {
|
|
555
|
+
domain: DOMAIN_FOR_INTERNAL_SITE,
|
|
556
|
+
port: await getWellknownSitePort(),
|
|
557
|
+
name: NAME_FOR_WELLKNOWN_SITE,
|
|
558
|
+
rules: [didResolverWellknownRule, acmeChallengeWellknownRule],
|
|
559
|
+
isProtected: true,
|
|
560
|
+
},
|
|
561
|
+
skipCheckDynamicBlacklist: true,
|
|
562
|
+
skipValidation: true,
|
|
563
|
+
},
|
|
564
|
+
context
|
|
565
|
+
);
|
|
566
|
+
|
|
523
567
|
return true;
|
|
524
568
|
} catch (err) {
|
|
525
569
|
console.error('add well-known site failed:', err);
|
|
@@ -533,7 +577,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
533
577
|
*
|
|
534
578
|
* @returns {boolean} if routing changed
|
|
535
579
|
*/
|
|
536
|
-
const ensureDashboardRouting = async (context = {}
|
|
580
|
+
const ensureDashboardRouting = async (context = {}) => {
|
|
537
581
|
const info = await nodeState.read();
|
|
538
582
|
|
|
539
583
|
const provider = getProviderFromNodeInfo(info);
|
|
@@ -618,18 +662,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
618
662
|
updatedResult.push(wellknownRes);
|
|
619
663
|
}
|
|
620
664
|
|
|
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
665
|
if (updatedResult.length) {
|
|
634
666
|
const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
|
|
635
667
|
logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
|
|
@@ -1068,9 +1100,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1068
1100
|
sites = await ensureAuthService(sites, blockletManager);
|
|
1069
1101
|
sites = await ensureServiceRule(sites);
|
|
1070
1102
|
|
|
1071
|
-
const certificates = httpsEnabled
|
|
1072
|
-
? await httpsCertState.find({ type: providerName }, { domain: 1, certificate: 1, privateKey: 1 })
|
|
1073
|
-
: [];
|
|
1103
|
+
const certificates = httpsEnabled ? await certManager.getAllNormal() : [];
|
|
1074
1104
|
|
|
1075
1105
|
return {
|
|
1076
1106
|
sites,
|
|
@@ -1093,9 +1123,9 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1093
1123
|
},
|
|
1094
1124
|
});
|
|
1095
1125
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1126
|
+
certManager.on('cert.added', () => routers[providerName].restart());
|
|
1127
|
+
certManager.on('cert.removed', () => routers[providerName].restart());
|
|
1128
|
+
certManager.on('cert.issued', () => routers[providerName].restart());
|
|
1099
1129
|
|
|
1100
1130
|
await routers[providerName].start();
|
|
1101
1131
|
}
|
|
@@ -1257,18 +1287,21 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1257
1287
|
};
|
|
1258
1288
|
|
|
1259
1289
|
const getCertificates = async () => {
|
|
1260
|
-
const certificates = await
|
|
1290
|
+
const certificates = await certManager.getAll();
|
|
1261
1291
|
const sites = await getSitesFromSnapshot();
|
|
1292
|
+
|
|
1293
|
+
const isMatch = (cert, domain) =>
|
|
1294
|
+
domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
|
|
1295
|
+
|
|
1262
1296
|
return certificates.map((cert) => {
|
|
1263
1297
|
cert.matchedSites = [];
|
|
1264
1298
|
sites.forEach((site) => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1299
|
+
const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
|
|
1300
|
+
domains.forEach((domain) => {
|
|
1301
|
+
if (isMatch(cert, domain)) {
|
|
1302
|
+
cert.matchedSites.push({ id: site.id, domain });
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1272
1305
|
});
|
|
1273
1306
|
|
|
1274
1307
|
return cert;
|
|
@@ -1281,42 +1314,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1281
1314
|
return { domain, isHttps: !!matchedCert, matchedCert };
|
|
1282
1315
|
};
|
|
1283
1316
|
|
|
1284
|
-
const checkCertificatesExpiration = async () => {
|
|
1285
|
-
const now = Date.now();
|
|
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
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
};
|
|
1319
|
-
|
|
1320
1317
|
return {
|
|
1321
1318
|
ensureDashboardRouting,
|
|
1322
1319
|
ensureBlockletRouting,
|
|
@@ -1328,9 +1325,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1328
1325
|
takeRoutingSnapshot,
|
|
1329
1326
|
getRoutingSites,
|
|
1330
1327
|
getSnapshotSites,
|
|
1331
|
-
getCertificates,
|
|
1332
1328
|
getSitesFromSnapshot,
|
|
1329
|
+
getCertificates,
|
|
1333
1330
|
checkDomain,
|
|
1331
|
+
ensureDashboardCertificate,
|
|
1334
1332
|
|
|
1335
1333
|
getRoutingCrons: () => [
|
|
1336
1334
|
{
|
|
@@ -1339,12 +1337,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1339
1337
|
fn: () => updateDashboardCertificate({ checkExpire: true }),
|
|
1340
1338
|
options: { runOnInit: true },
|
|
1341
1339
|
},
|
|
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
1340
|
{
|
|
1349
1341
|
name: 'rotate-log-files',
|
|
1350
1342
|
time: '5 0 0 * * *', // rotate at 05:00 every day
|
package/lib/router/manager.js
CHANGED
|
@@ -70,8 +70,9 @@ const normalizeRedirectUrl = (url) => {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
class RouterManager extends EventEmitter {
|
|
73
|
-
constructor() {
|
|
73
|
+
constructor({ certManager }) {
|
|
74
74
|
super();
|
|
75
|
+
this.certManager = certManager;
|
|
75
76
|
|
|
76
77
|
// HACK: do not emit any events from CLI
|
|
77
78
|
if (isCLI()) {
|
|
@@ -366,7 +367,7 @@ class RouterManager extends EventEmitter {
|
|
|
366
367
|
domain,
|
|
367
368
|
});
|
|
368
369
|
logger.info('add certificate result', { domain: newCert.domain });
|
|
369
|
-
this.emit('
|
|
370
|
+
this.emit('cert.added', { type: 'nginx', data: newCert });
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -379,7 +380,7 @@ class RouterManager extends EventEmitter {
|
|
|
379
380
|
const removeResult = await states.certificate.remove({ _id: id });
|
|
380
381
|
|
|
381
382
|
logger.info('delete certificate', { removeResult, domain: tmpCert.domain });
|
|
382
|
-
this.emit('
|
|
383
|
+
this.emit('cert.removed', { type: 'nginx', data: { domain: tmpCert.domain } });
|
|
383
384
|
return {};
|
|
384
385
|
}
|
|
385
386
|
|
|
@@ -389,7 +390,7 @@ class RouterManager extends EventEmitter {
|
|
|
389
390
|
this.fixCertificate(entity);
|
|
390
391
|
this.validateCertificate(entity, entity.domain);
|
|
391
392
|
const dbEntity = await states.certificate.upsert(entity);
|
|
392
|
-
this.emit('
|
|
393
|
+
this.emit('cert.issued', { type: 'nginx', data: dbEntity });
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
findCertificateByDomain(domain) {
|
|
@@ -451,7 +452,7 @@ class RouterManager extends EventEmitter {
|
|
|
451
452
|
}
|
|
452
453
|
|
|
453
454
|
async getMatchedCert(domain) {
|
|
454
|
-
const certs = await
|
|
455
|
+
const certs = await this.certManager.getAll();
|
|
455
456
|
const matchedCert = certs.find((cert) => this.isCertMatchedDomain(cert, domain));
|
|
456
457
|
|
|
457
458
|
if (matchedCert) {
|
package/lib/states/base.js
CHANGED
|
@@ -1,234 +1,17 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const util = require('util');
|
|
4
|
-
const { EventEmitter } = require('events');
|
|
5
|
-
const cloneDeep = require('lodash/cloneDeep');
|
|
6
|
-
const DataStore =
|
|
7
|
-
process.env.NODE_ENV === 'test' ? require('@nedb/core') : require('@nedb/multi')(Number(process.env.NEDB_MULTI_PORT));
|
|
1
|
+
const DB = require('@abtnode/db');
|
|
8
2
|
const logger = require('@abtnode/logger')('@abtnode/core:states');
|
|
9
3
|
|
|
10
4
|
const { isCLI } = require('../util');
|
|
11
5
|
|
|
12
|
-
class BaseState extends
|
|
6
|
+
class BaseState extends DB {
|
|
13
7
|
constructor(baseDir, options) {
|
|
14
|
-
super();
|
|
8
|
+
super(baseDir, options);
|
|
15
9
|
|
|
16
10
|
// HACK: do not emit any events from CLI
|
|
17
11
|
if (isCLI() && process.env.NODE_ENV !== 'test') {
|
|
18
12
|
this.emit = (name) => logger.debug('stopped state db event in CLI', name);
|
|
19
13
|
}
|
|
20
|
-
|
|
21
|
-
const dbOptions = options.db || {};
|
|
22
|
-
this.filename = path.join(baseDir, options.filename);
|
|
23
|
-
this.options = Object.freeze(cloneDeep(options));
|
|
24
|
-
this.db = new DataStore({
|
|
25
|
-
filename: this.filename,
|
|
26
|
-
timestampData: true,
|
|
27
|
-
...dbOptions,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
logger.info('initialized', { filename: this.filename });
|
|
31
|
-
|
|
32
|
-
this.ready = false;
|
|
33
|
-
this.readyCallbacks = [];
|
|
34
|
-
this.db.loadDatabase((err) => {
|
|
35
|
-
if (err) {
|
|
36
|
-
logger.error(`failed to load disk database ${this.filename}`, { error: err });
|
|
37
|
-
console.error(err);
|
|
38
|
-
} else {
|
|
39
|
-
this.ready = true;
|
|
40
|
-
if (this.readyCallbacks.length) {
|
|
41
|
-
this.readyCallbacks.forEach((x) => x());
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
this.asyncDB = new Proxy(this.db, {
|
|
47
|
-
get(target, property) {
|
|
48
|
-
if (typeof target[property] === 'function') {
|
|
49
|
-
return util
|
|
50
|
-
.promisify((...args) => {
|
|
51
|
-
const cb = args[args.length - 1];
|
|
52
|
-
const rest = args.slice(0, args.length - 1);
|
|
53
|
-
|
|
54
|
-
target[property](...rest, (err, ...result) => {
|
|
55
|
-
if (err) {
|
|
56
|
-
return cb(err);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (result.length === 1) {
|
|
60
|
-
return cb(null, result[0]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return cb(null, result);
|
|
64
|
-
});
|
|
65
|
-
})
|
|
66
|
-
.bind(target);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return target[property];
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
onReady(cb) {
|
|
75
|
-
if (this.ready) {
|
|
76
|
-
cb();
|
|
77
|
-
} else {
|
|
78
|
-
this.readyCallbacks.push(cb);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
createNotification(payload) {
|
|
83
|
-
if (this.notification && typeof this.notification.create === 'function') {
|
|
84
|
-
this.notification.create(payload);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
paginate(conditions, sort, paging) {
|
|
89
|
-
const { pageSize: size = 20, page = 1 } = paging || {};
|
|
90
|
-
const pageSize = Math.min(100, size);
|
|
91
|
-
|
|
92
|
-
return new Promise((resolve, reject) => {
|
|
93
|
-
this.db
|
|
94
|
-
.find(conditions)
|
|
95
|
-
.sort(sort)
|
|
96
|
-
.exec((err, docs) => {
|
|
97
|
-
if (err) {
|
|
98
|
-
return reject(err);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const pageCount = Math.ceil(docs.length / pageSize);
|
|
102
|
-
const total = docs.length;
|
|
103
|
-
const skip = (page - 1) * pageSize;
|
|
104
|
-
const list = docs.slice(skip, skip + pageSize);
|
|
105
|
-
|
|
106
|
-
list.forEach((doc) => {
|
|
107
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
108
|
-
doc.id = doc._id;
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return resolve({
|
|
112
|
-
list,
|
|
113
|
-
paging: {
|
|
114
|
-
total,
|
|
115
|
-
pageSize,
|
|
116
|
-
pageCount,
|
|
117
|
-
page,
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async updateById(id, updates, options = {}) {
|
|
125
|
-
const [, doc] = await this.asyncDB.update({ _id: id }, updates, {
|
|
126
|
-
multi: false,
|
|
127
|
-
upsert: false,
|
|
128
|
-
returnUpdatedDocs: true,
|
|
129
|
-
...options,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
return doc;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
update(...args) {
|
|
136
|
-
if (args.length === 0) {
|
|
137
|
-
throw new Error('param is required by update method');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (typeof args[0] === 'string') {
|
|
141
|
-
return this.updateById(...args);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return this.asyncDB.update(args[0], args[1], { returnUpdatedDocs: true, ...(args[2] || {}) });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
count(conditions = {}) {
|
|
148
|
-
return new Promise((resolve, reject) => {
|
|
149
|
-
this.db.count(conditions, (err, num) => {
|
|
150
|
-
if (err) {
|
|
151
|
-
return reject(err);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return resolve(num);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
remove(conditions = {}, options = { multi: false }) {
|
|
160
|
-
return new Promise((resolve, reject) => {
|
|
161
|
-
this.db.remove(conditions, options, (err, num) => {
|
|
162
|
-
if (err) {
|
|
163
|
-
return reject(err);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return resolve(num);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
reset() {
|
|
172
|
-
fs.unlinkSync(this.filename);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
find(...args) {
|
|
176
|
-
if (args.length === 0) {
|
|
177
|
-
return this.asyncDB.find({});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return this.asyncDB.find(...args);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
findOne(...args) {
|
|
184
|
-
if (args.length === 0) {
|
|
185
|
-
return this.asyncDB.findOne({});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return this.asyncDB.findOne(...args);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
insert(...args) {
|
|
192
|
-
return this.asyncDB.insert(...args);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
compactDatafile() {
|
|
196
|
-
this.db.persistence.compactDatafile((err) => {
|
|
197
|
-
if (err) {
|
|
198
|
-
console.error(`failed to compact datafile: ${this.filename}`, err);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
14
|
}
|
|
202
15
|
}
|
|
203
16
|
|
|
204
|
-
/**
|
|
205
|
-
* Rename _id field name to id, this method has side effects
|
|
206
|
-
* @param {object} entities
|
|
207
|
-
*/
|
|
208
|
-
const renameIdFiledName = (entities, from = '_id', to = 'id') => {
|
|
209
|
-
/* eslint-disable no-underscore-dangle, no-param-reassign */
|
|
210
|
-
|
|
211
|
-
if (!entities) {
|
|
212
|
-
return entities;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const mapEntity = (entity) => {
|
|
216
|
-
if (entity[from]) {
|
|
217
|
-
entity[to] = entity[from];
|
|
218
|
-
delete entity[from];
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
if (!Array.isArray(entities)) {
|
|
223
|
-
mapEntity(entities);
|
|
224
|
-
return entities;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
entities.forEach(mapEntity);
|
|
228
|
-
|
|
229
|
-
return entities;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
BaseState.renameIdFiledName = renameIdFiledName;
|
|
233
|
-
|
|
234
17
|
module.exports = BaseState;
|
package/lib/states/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const stateFactory = require('@abtnode/db/lib/factory');
|
|
2
2
|
const NodeState = require('./node');
|
|
3
3
|
const ChallengeState = require('./challenge');
|
|
4
4
|
const BlockletState = require('./blocklet');
|
|
@@ -12,8 +12,6 @@ const SessionState = require('./session');
|
|
|
12
12
|
const ExtrasState = require('./blocklet-extras');
|
|
13
13
|
const CacheState = require('./cache');
|
|
14
14
|
|
|
15
|
-
const states = {};
|
|
16
|
-
|
|
17
15
|
const init = (dataDirs, options) => {
|
|
18
16
|
const notificationState = new NotificationState(dataDirs.core, options);
|
|
19
17
|
const nodeState = new NodeState(dataDirs.core, options, dataDirs, notificationState);
|
|
@@ -28,7 +26,7 @@ const init = (dataDirs, options) => {
|
|
|
28
26
|
const extrasState = new ExtrasState(dataDirs.core, options);
|
|
29
27
|
const cacheState = new CacheState(dataDirs.core, options);
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
return {
|
|
32
30
|
node: nodeState,
|
|
33
31
|
blocklet: blockletState,
|
|
34
32
|
notification: notificationState,
|
|
@@ -41,22 +39,7 @@ const init = (dataDirs, options) => {
|
|
|
41
39
|
session: sessionState,
|
|
42
40
|
blockletExtras: extrasState,
|
|
43
41
|
cache: cacheState,
|
|
44
|
-
}
|
|
42
|
+
};
|
|
45
43
|
};
|
|
46
44
|
|
|
47
|
-
module.exports =
|
|
48
|
-
{},
|
|
49
|
-
{
|
|
50
|
-
get(target, prop) {
|
|
51
|
-
if (prop === 'init') {
|
|
52
|
-
return init;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (states[prop] instanceof BaseState) {
|
|
56
|
-
return states[prop];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
throw new Error(`State ${String(prop)} may not be initialized`);
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
);
|
|
45
|
+
module.exports = stateFactory(init);
|
package/lib/states/node.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
const semver = require('semver');
|
|
2
3
|
const omit = require('lodash/omit');
|
|
3
4
|
const isEqual = require('lodash/isEqual');
|
|
4
5
|
const isEmpty = require('lodash/isEmpty');
|
|
@@ -163,7 +164,7 @@ class NodeState extends BaseState {
|
|
|
163
164
|
|
|
164
165
|
cleanupDirtyUpgradeState() {
|
|
165
166
|
return this.read().then((doc) => {
|
|
166
|
-
if (doc.nextVersion
|
|
167
|
+
if (doc.nextVersion && semver.lte(doc.nextVersion, doc.version)) {
|
|
167
168
|
const updates = { nextVersion: '', upgradeSessionId: '' };
|
|
168
169
|
|
|
169
170
|
// FIXME: this may cause the node exit some mode unexpectedly if it is not being upgraded
|
package/lib/util/index.js
CHANGED
|
@@ -332,6 +332,8 @@ const getDataDirs = (dataDir) => ({
|
|
|
332
332
|
tmp: path.join(dataDir, 'tmp'),
|
|
333
333
|
blocklets: path.join(dataDir, 'blocklets'),
|
|
334
334
|
services: path.join(dataDir, 'services'),
|
|
335
|
+
modules: path.join(dataDir, 'modules'),
|
|
336
|
+
certManagerModule: path.join(dataDir, 'modules', 'certificate-manager'),
|
|
335
337
|
});
|
|
336
338
|
|
|
337
339
|
// Ensure data dir for Blocklet Server exists
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.6.
|
|
6
|
+
"version": "1.6.7",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,26 +19,28 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/
|
|
23
|
-
"@abtnode/
|
|
24
|
-
"@abtnode/
|
|
25
|
-
"@abtnode/
|
|
26
|
-
"@abtnode/
|
|
27
|
-
"@abtnode/
|
|
28
|
-
"@abtnode/
|
|
29
|
-
"@abtnode/
|
|
30
|
-
"@abtnode/
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
22
|
+
"@abtnode/certificate-manager": "1.6.7",
|
|
23
|
+
"@abtnode/constant": "1.6.7",
|
|
24
|
+
"@abtnode/cron": "1.6.7",
|
|
25
|
+
"@abtnode/db": "1.6.7",
|
|
26
|
+
"@abtnode/logger": "1.6.7",
|
|
27
|
+
"@abtnode/queue": "1.6.7",
|
|
28
|
+
"@abtnode/rbac": "1.6.7",
|
|
29
|
+
"@abtnode/router-provider": "1.6.7",
|
|
30
|
+
"@abtnode/static-server": "1.6.7",
|
|
31
|
+
"@abtnode/timemachine": "1.6.7",
|
|
32
|
+
"@abtnode/util": "1.6.7",
|
|
33
|
+
"@arcblock/did": "^1.13.79",
|
|
34
|
+
"@arcblock/event-hub": "1.13.79",
|
|
33
35
|
"@arcblock/pm2-events": "^0.0.5",
|
|
34
|
-
"@arcblock/vc": "^1.13.
|
|
35
|
-
"@blocklet/meta": "1.6.
|
|
36
|
+
"@arcblock/vc": "^1.13.79",
|
|
37
|
+
"@blocklet/meta": "1.6.7",
|
|
36
38
|
"@fidm/x509": "^1.2.1",
|
|
37
39
|
"@nedb/core": "^1.2.2",
|
|
38
40
|
"@nedb/multi": "^1.2.2",
|
|
39
|
-
"@ocap/mcrypto": "^1.13.
|
|
40
|
-
"@ocap/util": "^1.13.
|
|
41
|
-
"@ocap/wallet": "^1.13.
|
|
41
|
+
"@ocap/mcrypto": "^1.13.79",
|
|
42
|
+
"@ocap/util": "^1.13.79",
|
|
43
|
+
"@ocap/wallet": "^1.13.79",
|
|
42
44
|
"@slack/webhook": "^5.0.3",
|
|
43
45
|
"axios": "^0.21.4",
|
|
44
46
|
"axon": "^2.0.3",
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"is-base64": "^1.1.0",
|
|
52
54
|
"is-ip": "^3.1.0",
|
|
53
55
|
"is-url": "^1.2.4",
|
|
54
|
-
"joi": "^17.
|
|
56
|
+
"joi": "^17.5.0",
|
|
55
57
|
"js-yaml": "^3.14.0",
|
|
56
58
|
"lodash": "^4.17.21",
|
|
57
59
|
"lru-cache": "^6.0.0",
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
"compression": "^1.7.4",
|
|
72
74
|
"expand-tilde": "^2.0.2",
|
|
73
75
|
"express": "^4.17.1",
|
|
74
|
-
"jest": "^27.
|
|
76
|
+
"jest": "^27.4.5"
|
|
75
77
|
},
|
|
76
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "d681c03a398e3c7d4dbc878db93b2ab778dded81"
|
|
77
79
|
}
|