abler-api 1.0.78 → 1.0.80
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/dist/cjs/pp-util.js +195 -20
- package/package.json +7 -7
package/dist/cjs/pp-util.js
CHANGED
|
@@ -291,6 +291,12 @@ class apiUtil$2 {
|
|
|
291
291
|
};
|
|
292
292
|
};
|
|
293
293
|
static appAsPrefix = '?';
|
|
294
|
+
static cekEncryptOptions = {
|
|
295
|
+
algorithm: "aes-256-gcm",
|
|
296
|
+
// iv: 随机
|
|
297
|
+
encryptEncoding: "base64",
|
|
298
|
+
decryptEncoding: "utf8"
|
|
299
|
+
};
|
|
294
300
|
|
|
295
301
|
// static apiCallRecSaver;
|
|
296
302
|
|
|
@@ -1362,32 +1368,155 @@ class apiUtil$2 {
|
|
|
1362
1368
|
static corsCheckOrigin(origin, callback) {
|
|
1363
1369
|
callback(null, apiUtil$2.isOriginAllowed(origin));
|
|
1364
1370
|
}
|
|
1371
|
+
static async restoreObj(key) {
|
|
1372
|
+
return kvStorage.restoreObj(key).catch(() => null);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
//#region ==== CEK 加解密及签名 ====
|
|
1376
|
+
|
|
1377
|
+
// 应用配置:
|
|
1378
|
+
// appSetting:
|
|
1379
|
+
// e2eEncryptionNeeded
|
|
1380
|
+
// cekRotateTime
|
|
1381
|
+
// errCfg:
|
|
1382
|
+
// ACCESS_REFUSED
|
|
1383
|
+
// CEK_EXPIRED
|
|
1384
|
+
// PUBLIC_ENCRYPT_FAIL
|
|
1385
|
+
// DECRYPT_FAIL
|
|
1386
|
+
// dbSql
|
|
1387
|
+
// APP_QUERY
|
|
1388
|
+
|
|
1389
|
+
static async rotateCek(req) {
|
|
1390
|
+
try {
|
|
1391
|
+
const appInfo = req.appInfo;
|
|
1392
|
+
const apiKey = appInfo.apiKey;
|
|
1393
|
+
let cek = null;
|
|
1394
|
+
if (appSetting.e2eEncryptionNeeded) {
|
|
1395
|
+
await this.storePreviousCek(apiKey, req);
|
|
1396
|
+
// 生成新的CEK
|
|
1397
|
+
cek = await this.generateCek(appInfo, req);
|
|
1398
|
+
console.log("已生成新的CEK", cek);
|
|
1399
|
+
}
|
|
1400
|
+
let result = this.apiSuccess(cek, req);
|
|
1401
|
+
delete result.message; //减少数据尺寸,否则数据太长无法用公钥加密
|
|
1402
|
+
delete result._elapse;
|
|
1403
|
+
// options._res.publicKey = appInfo.publicKey; // 设置用于加密结果的公钥
|
|
1404
|
+
return result;
|
|
1405
|
+
} catch (e) {
|
|
1406
|
+
return apiUtil$2.apiFail(e, req);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
static cekExpired(cek) {
|
|
1410
|
+
return new Date().valueOf() >= cek.expireAt;
|
|
1411
|
+
}
|
|
1365
1412
|
static validCek(cek, checkExpired = true) {
|
|
1366
1413
|
if (!cek) {
|
|
1367
|
-
throw [errCfg.
|
|
1414
|
+
throw [this.errCfg.CEK_EXPIRED, t("或尚未申请")];
|
|
1368
1415
|
}
|
|
1369
|
-
if (checkExpired &&
|
|
1370
|
-
throw errCfg.CEK_EXPIRED;
|
|
1416
|
+
if (checkExpired && this.cekExpired(cek)) {
|
|
1417
|
+
throw this.errCfg.CEK_EXPIRED;
|
|
1371
1418
|
}
|
|
1372
1419
|
}
|
|
1420
|
+
static encryptContentText(contentText, cek) {
|
|
1421
|
+
return ppCrypto.encryptData(contentText, cek.key, this.cekEncryptOptions);
|
|
1422
|
+
}
|
|
1373
1423
|
static encryptContent(content, cek) {
|
|
1374
|
-
|
|
1424
|
+
// let contentText = commonUtil.orderedJsonStringify(content);
|
|
1425
|
+
let contentText = JSON.stringify(content);
|
|
1426
|
+
return this.encryptContentText(contentText, cek);
|
|
1427
|
+
}
|
|
1428
|
+
static encryptResponseContent(response, result) {
|
|
1429
|
+
if (!appSetting.e2eEncryptionNeeded) {
|
|
1430
|
+
if (response.traceId) {
|
|
1431
|
+
console.log(1, response.traceId, 'Response:', result);
|
|
1432
|
+
}
|
|
1433
|
+
return result;
|
|
1434
|
+
}
|
|
1435
|
+
if (response.traceId) {
|
|
1436
|
+
console.log(1, response.traceId, 'Response before encrypt:', result);
|
|
1437
|
+
}
|
|
1438
|
+
const {
|
|
1439
|
+
publicKey,
|
|
1440
|
+
cek
|
|
1441
|
+
} = response;
|
|
1442
|
+
if (cek) {
|
|
1443
|
+
if (response.newCek) {
|
|
1444
|
+
result.newCek = response.newCek;
|
|
1445
|
+
}
|
|
1446
|
+
if (response.traceId) {
|
|
1447
|
+
console.log(1, response.traceId, 'CEK:', cek);
|
|
1448
|
+
}
|
|
1449
|
+
result = {
|
|
1450
|
+
encryptedContent: this.encryptContent(result, cek)
|
|
1451
|
+
};
|
|
1452
|
+
} else if (publicKey) {
|
|
1453
|
+
if (response.traceId) {
|
|
1454
|
+
console.log(1, response.traceId, 'PUBLIC KEY:', publicKey);
|
|
1455
|
+
}
|
|
1456
|
+
const message = Buffer.from(JSON.stringify(result), "utf8");
|
|
1457
|
+
try {
|
|
1458
|
+
result = ppCrypto.rsaEncrypt({
|
|
1459
|
+
publicKey,
|
|
1460
|
+
message
|
|
1461
|
+
});
|
|
1462
|
+
} catch (e) {
|
|
1463
|
+
result = apiUtil$2.apiFail([errCfg.PUBLIC_ENCRYPT_FAIL, e.message], response.req);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (response.traceId) {
|
|
1467
|
+
console.log(1, response.traceId, 'Response after encrypt:', result);
|
|
1468
|
+
}
|
|
1469
|
+
return result;
|
|
1375
1470
|
}
|
|
1376
|
-
static
|
|
1471
|
+
static decryptContentText(content, cek) {
|
|
1377
1472
|
this.validCek(cek);
|
|
1378
1473
|
try {
|
|
1379
|
-
|
|
1380
|
-
if (result.startsWith('{') || result.startsWith('[')) {
|
|
1381
|
-
return JSON.parse(result);
|
|
1382
|
-
}
|
|
1383
|
-
return result;
|
|
1474
|
+
return ppCrypto.decryptData(content, cek.key, this.cekEncryptOptions);
|
|
1384
1475
|
} catch (e) {
|
|
1385
|
-
|
|
1476
|
+
// console.log("内容", content);
|
|
1477
|
+
// const req = RequestContext.get('req');
|
|
1478
|
+
// console.log("headers", req.headers);
|
|
1479
|
+
// console.error(e);
|
|
1480
|
+
throw [this.errCfg.DECRYPT_FAIL, e.message];
|
|
1386
1481
|
}
|
|
1387
1482
|
}
|
|
1388
|
-
static
|
|
1483
|
+
static decryptContent(content, cek) {
|
|
1484
|
+
let contentText = this.decryptContentText(content, cek);
|
|
1485
|
+
if (contentText.startsWith('{') || contentText.startsWith('[')) {
|
|
1486
|
+
return JSON.parse(contentText);
|
|
1487
|
+
}
|
|
1488
|
+
return contentText;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// static decryptRequestContent(req, res) {
|
|
1492
|
+
// if (!appSetting.e2eEncryptionNeeded)
|
|
1493
|
+
// return;
|
|
1494
|
+
// let params = apiUtil.extractParams(req);
|
|
1495
|
+
// if (params.encryptedContent) {
|
|
1496
|
+
// if (['DELETE', 'GET'].contains(req.method)) {
|
|
1497
|
+
// params.encryptedContent = decodeURIComponent(params.encryptedContent);
|
|
1498
|
+
// }
|
|
1499
|
+
// if (!req.appInfo) {
|
|
1500
|
+
// throw t('应用系统信息未知');
|
|
1501
|
+
// }
|
|
1502
|
+
// const decryptedContent = apiUtil.decryptContent(params.encryptedContent, req.appInfo.cek);
|
|
1503
|
+
// delete params.encryptedContent;
|
|
1504
|
+
// Object.assign(req.params, decryptedContent);
|
|
1505
|
+
// }
|
|
1506
|
+
// if (res) {
|
|
1507
|
+
// apiUtil.validCek(req.appInfo.cek);
|
|
1508
|
+
// res.cek = req.appInfo.cek; // 设置用于对称加密的CEK
|
|
1509
|
+
// }
|
|
1510
|
+
// }
|
|
1511
|
+
|
|
1512
|
+
static async decryptRequestContent(req, res) {
|
|
1389
1513
|
if (!appSetting.e2eEncryptionNeeded) return;
|
|
1514
|
+
const appInfo = req.appInfo;
|
|
1390
1515
|
let params = apiUtil$2.extractParams(req);
|
|
1516
|
+
if (req.traceId) {
|
|
1517
|
+
console.log(1, req.traceId, 'Request Params before decrypt:', params);
|
|
1518
|
+
console.log(1, req.traceId, 'CEK:', appInfo.cek);
|
|
1519
|
+
}
|
|
1391
1520
|
if (params.encryptedContent) {
|
|
1392
1521
|
if (['DELETE', 'GET'].contains(req.method)) {
|
|
1393
1522
|
params.encryptedContent = decodeURIComponent(params.encryptedContent);
|
|
@@ -1395,23 +1524,67 @@ class apiUtil$2 {
|
|
|
1395
1524
|
if (!req.appInfo) {
|
|
1396
1525
|
throw t('应用系统信息未知');
|
|
1397
1526
|
}
|
|
1398
|
-
|
|
1527
|
+
let decryptedContent;
|
|
1528
|
+
try {
|
|
1529
|
+
if (req.traceId) {
|
|
1530
|
+
const dataText = this.decryptContentText(params.encryptedContent, appInfo.cek);
|
|
1531
|
+
console.log(1, req.traceId, 'Decrypted text:', dataText);
|
|
1532
|
+
}
|
|
1533
|
+
decryptedContent = this.decryptContent(params.encryptedContent, appInfo.cek);
|
|
1534
|
+
} catch (e) {
|
|
1535
|
+
let cek = appInfo.cek;
|
|
1536
|
+
console.log("解密失败 CEK", cek?.key, new Date(cek?.expireAt).format('MM.dd hh:mm:ss'));
|
|
1537
|
+
// 尝试用前一个 CEK 解密
|
|
1538
|
+
const previousCek = await this.restorePreviousCek(appInfo.apiKey, req);
|
|
1539
|
+
if (!previousCek) {
|
|
1540
|
+
throw e;
|
|
1541
|
+
}
|
|
1542
|
+
cek = previousCek;
|
|
1543
|
+
console.log("换用前任 CEK", cek.key, new Date(cek.expireAt).format('MM.dd hh:mm:ss'));
|
|
1544
|
+
try {
|
|
1545
|
+
decryptedContent = this.decryptContent(params.encryptedContent, cek);
|
|
1546
|
+
if (res) {
|
|
1547
|
+
res.newCek = appInfo.cek;
|
|
1548
|
+
}
|
|
1549
|
+
appInfo.cek = cek;
|
|
1550
|
+
} catch (e) {
|
|
1551
|
+
console.log("换用前任解密依然失败", "ApiKey", appInfo.apiKey, "x-api-key", req.headers["x-api-key"] || req.headers["api-key"]);
|
|
1552
|
+
throw e;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1399
1555
|
delete params.encryptedContent;
|
|
1400
|
-
Object.assign(
|
|
1556
|
+
Object.assign(params, decryptedContent);
|
|
1557
|
+
}
|
|
1558
|
+
if (req.traceId) {
|
|
1559
|
+
console.log(1, req.traceId, 'Request Params after decrypt:', params);
|
|
1401
1560
|
}
|
|
1402
1561
|
if (res) {
|
|
1403
|
-
|
|
1404
|
-
res.cek =
|
|
1562
|
+
this.validCek(appInfo.cek);
|
|
1563
|
+
res.cek = appInfo.cek; // 设置用于对称加密的CEK
|
|
1405
1564
|
}
|
|
1406
1565
|
}
|
|
1407
|
-
static async
|
|
1408
|
-
|
|
1566
|
+
static async storePreviousCek(apiKey, req) {
|
|
1567
|
+
let cekKey = this.cekStoreKey(apiKey, req);
|
|
1568
|
+
const previousCek = await this.restoreObj(cekKey);
|
|
1569
|
+
if (previousCek && !this.cekExpired(previousCek)) {
|
|
1570
|
+
// 轮换期间保留旧版本一段时间
|
|
1571
|
+
cekKey += "_p";
|
|
1572
|
+
await kvStorage.storeObj(cekKey, previousCek, appSetting.cekRotateTime);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
static async restorePreviousCek(apiKey, req) {
|
|
1576
|
+
let cekKey = this.cekStoreKey(apiKey, req) + "_p";
|
|
1577
|
+
return await this.restoreObj(cekKey);
|
|
1409
1578
|
}
|
|
1410
1579
|
static cekStoreKey(apiKey, req) {
|
|
1411
|
-
return `cek_${this.appAsPrefix}_${apiKey}_${apiUtil
|
|
1580
|
+
// return `cek_${this.appAsPrefix}_${apiKey}_${apiUtil.getClientIp(req)}`
|
|
1581
|
+
let key = `cek_${this.appAsPrefix}_${apiKey}`;
|
|
1582
|
+
if (req?.apiClientId) {
|
|
1583
|
+
return `${key}@${req.apiClientId}`;
|
|
1584
|
+
}
|
|
1585
|
+
return key;
|
|
1412
1586
|
}
|
|
1413
1587
|
static async generateCek(appInfo, req, key) {
|
|
1414
|
-
//todo: 检查既有 cek
|
|
1415
1588
|
let t = new Date().valueOf();
|
|
1416
1589
|
const cek = {
|
|
1417
1590
|
key: key || ppUtil$3.newGuid() + ppUtil$3.newGuid(),
|
|
@@ -1605,6 +1778,8 @@ class apiUtil$2 {
|
|
|
1605
1778
|
|
|
1606
1779
|
//#endregion
|
|
1607
1780
|
|
|
1781
|
+
//#endregion
|
|
1782
|
+
|
|
1608
1783
|
//region ====中间件
|
|
1609
1784
|
|
|
1610
1785
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abler-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.80",
|
|
4
4
|
"description": "API服务相关工具",
|
|
5
5
|
"main": "./dist/cjs/pp-util.js",
|
|
6
6
|
"-module": "./dist/es/pp-util.js",
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
"author": "peng_peng",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"abler-db": "^1.0.
|
|
19
|
-
"abler-i18n": "^1.0.
|
|
20
|
-
"abler-messenger": "^1.1.
|
|
21
|
-
"abler-net": "^1.0.
|
|
22
|
-
"abler-util": "^1.0.
|
|
18
|
+
"abler-db": "^1.0.88",
|
|
19
|
+
"abler-i18n": "^1.0.22",
|
|
20
|
+
"abler-messenger": "^1.1.43",
|
|
21
|
+
"abler-net": "^1.0.44",
|
|
22
|
+
"abler-util": "^1.0.42",
|
|
23
23
|
"basic-auth": "^2.0.1",
|
|
24
24
|
"node-cron": "^3.0.1"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "24a18765761c03e679c089ea9c0c6eb255ea95e6"
|
|
27
27
|
}
|