abler-api 1.0.78 → 1.0.81
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 +221 -28
- 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(),
|
|
@@ -1450,7 +1623,7 @@ class apiUtil$2 {
|
|
|
1450
1623
|
return apiUtil$2.apiFail(e, req);
|
|
1451
1624
|
}
|
|
1452
1625
|
}
|
|
1453
|
-
static async
|
|
1626
|
+
static async restoreOrLoadAppInfo(apiKey, req) {
|
|
1454
1627
|
let appInfo = await apiUtil$2.restoreObj(`${this.appAsPrefix}App_${apiKey}`, appSetting.tokenExpireTime);
|
|
1455
1628
|
if (!appInfo) {
|
|
1456
1629
|
appInfo = await dbUtil.dbQueryOneAndUnstringify(conf$2.dbSql.APP_QUERY, {
|
|
@@ -1458,7 +1631,7 @@ class apiUtil$2 {
|
|
|
1458
1631
|
}, "exData");
|
|
1459
1632
|
if (appInfo) {
|
|
1460
1633
|
// await generateCek(appInfo);
|
|
1461
|
-
await kvStorage.storeObj(
|
|
1634
|
+
await kvStorage.storeObj(`${this.appAsPrefix}App_${apiKey}`, appInfo, appSetting.tokenExpireTime);
|
|
1462
1635
|
}
|
|
1463
1636
|
}
|
|
1464
1637
|
// if (appInfo && !appInfo.cek && appSetting.e2eEncryptionNeeded) {
|
|
@@ -1480,7 +1653,7 @@ class apiUtil$2 {
|
|
|
1480
1653
|
encryptedContent: ''
|
|
1481
1654
|
}
|
|
1482
1655
|
};
|
|
1483
|
-
const appInfo = await this.
|
|
1656
|
+
const appInfo = await this.restoreOrLoadAppInfo(params.apiKey, options._res.req);
|
|
1484
1657
|
if (!appInfo) {
|
|
1485
1658
|
throw [errCfg.ACCESS_REFUSED, t_f$2("apiKey (%s) 无效", params.apiKey)];
|
|
1486
1659
|
}
|
|
@@ -1529,16 +1702,34 @@ class apiUtil$2 {
|
|
|
1529
1702
|
}
|
|
1530
1703
|
static async reqAppInfoNeeded(req) {
|
|
1531
1704
|
if (!req.appInfo) {
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
throw [errCfg.ACCESS_REFUSED, t("必须在请求头中设置api-key")];
|
|
1535
|
-
}
|
|
1536
|
-
req.appInfo = await this.restoreAppInfo(apiKey, req);
|
|
1705
|
+
const apiKey = this.extractApiKey(req);
|
|
1706
|
+
req.appInfo = await this.restoreOrLoadAppInfo(apiKey, req);
|
|
1537
1707
|
if (!req.appInfo) {
|
|
1538
1708
|
throw [errCfg.ACCESS_REFUSED, t_f$2("api-key (%s) 无效", apiKey)];
|
|
1539
1709
|
}
|
|
1540
1710
|
}
|
|
1541
1711
|
}
|
|
1712
|
+
static extractApiKey(req, noErr) {
|
|
1713
|
+
if (!req.apiKey) {
|
|
1714
|
+
let apiKey = req.headers['x-api-key'] || req.headers['api-key'] || req.leInfo?.apiKey || req.userInfo?.apiKey;
|
|
1715
|
+
if (!apiKey) {
|
|
1716
|
+
if (noErr) return;
|
|
1717
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中设置api-key")];
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// 不能用IP作为客户端主机识别,因为一台主机常常会有多个IP,而每次请求的IP可能会发生变化
|
|
1721
|
+
// 用户在 apiKey 加后缀 @xx 来区分同一ApiKey的不同客户端(服务器主机/进程),或者在请求头中增加 x-api-client-id 来设置客户端标识
|
|
1722
|
+
if (apiKey.includes("@")) {
|
|
1723
|
+
const two = apiKey.split("@");
|
|
1724
|
+
apiKey = two[0];
|
|
1725
|
+
req.apiClientId = two[1];
|
|
1726
|
+
} else {
|
|
1727
|
+
req.apiClientId = req.headers['x-api-client-id'] || req.headers['api-client-id'];
|
|
1728
|
+
}
|
|
1729
|
+
req.apiKey = apiKey;
|
|
1730
|
+
}
|
|
1731
|
+
return req.apiKey;
|
|
1732
|
+
}
|
|
1542
1733
|
static async verifyApiSignature(req) {
|
|
1543
1734
|
if (req.appInfo) {
|
|
1544
1735
|
return true;
|
|
@@ -1605,6 +1796,8 @@ class apiUtil$2 {
|
|
|
1605
1796
|
|
|
1606
1797
|
//#endregion
|
|
1607
1798
|
|
|
1799
|
+
//#endregion
|
|
1800
|
+
|
|
1608
1801
|
//region ====中间件
|
|
1609
1802
|
|
|
1610
1803
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abler-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.81",
|
|
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": "aa1ffa76c3eedf9ec6b343d5b32518a86f7bdfaf"
|
|
27
27
|
}
|