abler-api 0.1.81 → 0.2.0
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 +289 -48
- package/package.json +2 -2
package/dist/cjs/pp-util.js
CHANGED
|
@@ -37,7 +37,7 @@ const {
|
|
|
37
37
|
kvStorage: kvStorage$1,
|
|
38
38
|
redisSimulator
|
|
39
39
|
} = require$$4__default["default"];
|
|
40
|
-
let conf$3, err$
|
|
40
|
+
let conf$3, err$1;
|
|
41
41
|
// const dbCheck = require('../dbupdate/dd-version');
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -57,7 +57,7 @@ class preconditions$2 {
|
|
|
57
57
|
static setAppName(aName) {
|
|
58
58
|
ppUtil$4.configNeeded();
|
|
59
59
|
conf$3 = ppUtil$4.appConfig;
|
|
60
|
-
err$
|
|
60
|
+
err$1 = ppUtil$4.appErrCfg;
|
|
61
61
|
preconditions$2.appName = aName || conf$3.thisApp;
|
|
62
62
|
return preconditions$2;
|
|
63
63
|
}
|
|
@@ -167,7 +167,7 @@ async function createEnvSettingFile(fileName, lastEnvSettingEx) {
|
|
|
167
167
|
}
|
|
168
168
|
return t_f$3("已創建新的本機環境配置文件\n%s\n请檢查並正確設置各項內容,然後將配置版本號修改為%s", fileName, conf$3._envSettings.default.cfgVersion);
|
|
169
169
|
} catch (e) {
|
|
170
|
-
return t_f$3("創建本機環境配置文件失敗\n《%s》\n%s", fileName, err$
|
|
170
|
+
return t_f$3("創建本機環境配置文件失敗\n《%s》\n%s", fileName, err$1.ERROR(e).message);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
async function checkExEnvSetting(loadjs) {
|
|
@@ -222,7 +222,7 @@ async function checkRedis() {
|
|
|
222
222
|
}
|
|
223
223
|
async function initDb(dbSql) {
|
|
224
224
|
try {
|
|
225
|
-
dbUtil$1.config(conf$3, err$
|
|
225
|
+
dbUtil$1.config(conf$3, err$1, dbSql);
|
|
226
226
|
} catch (e) {
|
|
227
227
|
if (e.reason === 'bad decrypt') {
|
|
228
228
|
e = new Error(t_f$3("解密数据库密码(%s)失败", conf$3.dbconn.dbpwd));
|
|
@@ -235,7 +235,7 @@ async function initDb(dbSql) {
|
|
|
235
235
|
}
|
|
236
236
|
async function initKvStorage(dbSql) {
|
|
237
237
|
try {
|
|
238
|
-
kvStorage$1.config(conf$3, err$
|
|
238
|
+
kvStorage$1.config(conf$3, err$1);
|
|
239
239
|
if (conf$3.redis.enabled) {
|
|
240
240
|
kvStorage$1.setRedis(ppRedis.newClient("kv"));
|
|
241
241
|
} else {
|
|
@@ -258,7 +258,8 @@ const {
|
|
|
258
258
|
t
|
|
259
259
|
} = require$$2__default["default"];
|
|
260
260
|
const {
|
|
261
|
-
ppUtil: ppUtil$3
|
|
261
|
+
ppUtil: ppUtil$3,
|
|
262
|
+
ppCrypto
|
|
262
263
|
} = require$$3__default["default"];
|
|
263
264
|
const {
|
|
264
265
|
dbUtil,
|
|
@@ -268,7 +269,7 @@ const {
|
|
|
268
269
|
netUtil: netUtil$1
|
|
269
270
|
} = require$$6__default["default"];
|
|
270
271
|
const preconditions$1 = ppPrecond;
|
|
271
|
-
let conf$2, appSetting,
|
|
272
|
+
let conf$2, appSetting, errCfg, dbSql;
|
|
272
273
|
const pnToken = "access_token",
|
|
273
274
|
hnToken = pnToken,
|
|
274
275
|
pnApiKey = "apiKey",
|
|
@@ -276,8 +277,6 @@ const pnToken = "access_token",
|
|
|
276
277
|
// 我们接收到的 headers 中的字段名总是全小写的
|
|
277
278
|
pnApiSecret = "apiSecret",
|
|
278
279
|
hnApiSecret = pnApiSecret.toLowerCase();
|
|
279
|
-
const MD5 = ppUtil$3.MD5,
|
|
280
|
-
moveProperty = ppUtil$3.moveProperty;
|
|
281
280
|
class apiUtil$2 {
|
|
282
281
|
static debugFlag = ppUtil$3.newGuid(); //应用必须设置,否则谁也不知道是啥
|
|
283
282
|
static testFlag = ppUtil$3.newGuid();
|
|
@@ -288,13 +287,14 @@ class apiUtil$2 {
|
|
|
288
287
|
message: 'no basic auth validator'
|
|
289
288
|
};
|
|
290
289
|
};
|
|
290
|
+
static appAsPrefix = '?';
|
|
291
291
|
|
|
292
292
|
// static apiCallRecSaver;
|
|
293
293
|
|
|
294
294
|
static config(appConfig, appErrCfg, appDbSql) {
|
|
295
295
|
conf$2 = appConfig;
|
|
296
296
|
appSetting = conf$2?.appSetting;
|
|
297
|
-
|
|
297
|
+
errCfg = appErrCfg;
|
|
298
298
|
dbSql = appDbSql;
|
|
299
299
|
ppUtil$3.config(appConfig, appErrCfg);
|
|
300
300
|
kvStorage.config(appConfig, appErrCfg);
|
|
@@ -302,6 +302,7 @@ class apiUtil$2 {
|
|
|
302
302
|
apiUtil$2.testFlag = appSetting?.testFlag || apiUtil$2.testFlag;
|
|
303
303
|
// apiUtil.apiCallRecSaver = apiCallRecSaver;
|
|
304
304
|
preconditions$1.setAppName();
|
|
305
|
+
apiUtil$2.appAsPrefix = conf$2.thisApp.toLowerCase();
|
|
305
306
|
}
|
|
306
307
|
|
|
307
308
|
//#region ===== 需要应用系统重写的方法
|
|
@@ -406,7 +407,7 @@ class apiUtil$2 {
|
|
|
406
407
|
if (this[name] === undefined) return defaultValue;
|
|
407
408
|
let result = +this[name];
|
|
408
409
|
if (typeof result !== "number" || result < minValue || result > maxValue) {
|
|
409
|
-
throw [
|
|
410
|
+
throw [errCfg.INVALID_PARAM, t_f$2("参数 %s 必须是 %s ~ %s 之间的数字", name, minValue, maxValue)];
|
|
410
411
|
}
|
|
411
412
|
return result;
|
|
412
413
|
});
|
|
@@ -471,7 +472,7 @@ class apiUtil$2 {
|
|
|
471
472
|
*/
|
|
472
473
|
static apiFail(error, req, spOrderNum) {
|
|
473
474
|
configNeeded();
|
|
474
|
-
let response =
|
|
475
|
+
let response = errCfg.ERROR(error, errCfg.errorLangParamFlag + ppUtil$3.getMsgLang(req));
|
|
475
476
|
response.datetime = new Date();
|
|
476
477
|
if (spOrderNum) {
|
|
477
478
|
response.spOrderNum = spOrderNum;
|
|
@@ -679,11 +680,11 @@ class apiUtil$2 {
|
|
|
679
680
|
delete params.token;
|
|
680
681
|
} else {
|
|
681
682
|
// 如果没有token,则返回错误
|
|
682
|
-
console.log(
|
|
683
|
+
console.log(errCfg.TOKEN_NEEDED);
|
|
683
684
|
if (res) {
|
|
684
|
-
res.send(
|
|
685
|
+
res.send(errCfg.ERROR(errCfg.TOKEN_NEEDED));
|
|
685
686
|
} else if (!noErr) {
|
|
686
|
-
throw
|
|
687
|
+
throw errCfg.TOKEN_NEEDED;
|
|
687
688
|
}
|
|
688
689
|
}
|
|
689
690
|
}
|
|
@@ -701,7 +702,7 @@ class apiUtil$2 {
|
|
|
701
702
|
// return await checkInternalToken(req);
|
|
702
703
|
const params = apiUtil$2.extractParams(req);
|
|
703
704
|
req.accessToken = req.accessToken || apiUtil$2.extractToken(req);
|
|
704
|
-
if (req.accessToken === MD5(ppUtil$3.commonHashDisturbing)) {
|
|
705
|
+
if (req.accessToken === ppCrypto.MD5(ppUtil$3.commonHashDisturbing)) {
|
|
705
706
|
//todo: 检查IP
|
|
706
707
|
params.__internal__ = true;
|
|
707
708
|
req.ignoreToken = true;
|
|
@@ -722,7 +723,7 @@ class apiUtil$2 {
|
|
|
722
723
|
return true;
|
|
723
724
|
} else {
|
|
724
725
|
// console.log('token error.', req.accessToken);
|
|
725
|
-
throw
|
|
726
|
+
throw errCfg.TOKEN_INVALID;
|
|
726
727
|
}
|
|
727
728
|
}
|
|
728
729
|
|
|
@@ -755,7 +756,7 @@ class apiUtil$2 {
|
|
|
755
756
|
let dbgToken = req.headers['__debug__'] || req.headers[apiUtil$2.debugFlag];
|
|
756
757
|
if (dbgToken) {
|
|
757
758
|
let envId = process.env.ECS_DEPLOY_ID;
|
|
758
|
-
req.__debug__ = dbgToken && dbgToken === MD5(req.headers.timestamp, ppUtil$3.commonHashDisturbing) && (envId === "florist_longdan" || envId === "shuzi");
|
|
759
|
+
req.__debug__ = dbgToken && dbgToken === ppCrypto.MD5(req.headers.timestamp, ppUtil$3.commonHashDisturbing) && (envId === "florist_longdan" || envId === "shuzi");
|
|
759
760
|
} else {
|
|
760
761
|
req.__debug__ = false;
|
|
761
762
|
}
|
|
@@ -775,7 +776,7 @@ class apiUtil$2 {
|
|
|
775
776
|
let dbgToken = req.headers["__postman__"] || req.headers[apiUtil$2.testFlag];
|
|
776
777
|
if (dbgToken) {
|
|
777
778
|
let envId = process.env.ECS_DEPLOY_ID;
|
|
778
|
-
let testToken = MD5(req.headers.timestamp, apiUtil$2.extractToken(req, null, true) || req.headers.apikey);
|
|
779
|
+
let testToken = ppCrypto.MD5(req.headers.timestamp, apiUtil$2.extractToken(req, null, true) || req.headers.apikey);
|
|
779
780
|
req.__postman__ = dbgToken === testToken && (envId.indexOf("myfacesign.com") < 0 || envId.indexOf(".dev.") > 0);
|
|
780
781
|
} else {
|
|
781
782
|
req.__postman__ = false;
|
|
@@ -995,7 +996,7 @@ class apiUtil$2 {
|
|
|
995
996
|
return certInfo;
|
|
996
997
|
}).catch(e => {
|
|
997
998
|
console.log('获取企业证书失败:\n', e);
|
|
998
|
-
throw [
|
|
999
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("获取企业证书失败:%s", errCfg.ERROR(e).message)];
|
|
999
1000
|
});
|
|
1000
1001
|
}
|
|
1001
1002
|
|
|
@@ -1005,20 +1006,20 @@ class apiUtil$2 {
|
|
|
1005
1006
|
* @param req
|
|
1006
1007
|
* @returns {Promise<*|boolean>}
|
|
1007
1008
|
*/
|
|
1008
|
-
static async
|
|
1009
|
+
static async verifyApiTokenSignature(tokenData, req) {
|
|
1009
1010
|
try {
|
|
1010
1011
|
if (!req.headers.timestamp) {
|
|
1011
|
-
throw [
|
|
1012
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中设置时戳")];
|
|
1012
1013
|
}
|
|
1013
1014
|
let timestamp = parseInt(req.headers.timestamp);
|
|
1014
1015
|
let currentTime = new Date().valueOf();
|
|
1015
1016
|
let maxTimeDiff = apiUtil$2.isDebugMode(req) ? 7 * 24 * 3600 : apiUtil$2.isTestMode(req) ? 4 * 3600 : 100; // 100
|
|
1016
1017
|
if (timestamp.toString() == "NaN" || currentTime < timestamp - 5000 || currentTime - timestamp > maxTimeDiff * 1000) {
|
|
1017
|
-
throw [
|
|
1018
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("时戳(%s)无效,当前时戳:%s,时差 %s秒", timestamp, currentTime, (currentTime - timestamp) / 1000)];
|
|
1018
1019
|
}
|
|
1019
1020
|
let signature = req.headers.signature;
|
|
1020
1021
|
if (!signature) {
|
|
1021
|
-
throw [
|
|
1022
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中提供签名")];
|
|
1022
1023
|
}
|
|
1023
1024
|
let signData = apiUtil$2.extractToken(req, null, true) || tokenData.apiKey;
|
|
1024
1025
|
signData += timestamp;
|
|
@@ -1064,7 +1065,7 @@ class apiUtil$2 {
|
|
|
1064
1065
|
verifyOK = false;
|
|
1065
1066
|
if (!tokenData.certPublicKeySpare) {
|
|
1066
1067
|
throw {
|
|
1067
|
-
eo:
|
|
1068
|
+
eo: errCfg.ACCESS_REFUSED,
|
|
1068
1069
|
msgArgv: t("签名验证异常"),
|
|
1069
1070
|
data: e
|
|
1070
1071
|
};
|
|
@@ -1109,7 +1110,7 @@ class apiUtil$2 {
|
|
|
1109
1110
|
} catch (e) {
|
|
1110
1111
|
console.log("SPO服务签名验证异常(备用证书), ", e);
|
|
1111
1112
|
throw {
|
|
1112
|
-
eo:
|
|
1113
|
+
eo: errCfg.ACCESS_REFUSED,
|
|
1113
1114
|
msgArgv: t("签名验证异常(spare)"),
|
|
1114
1115
|
data: e
|
|
1115
1116
|
};
|
|
@@ -1117,7 +1118,7 @@ class apiUtil$2 {
|
|
|
1117
1118
|
}
|
|
1118
1119
|
if (!verifyOK) {
|
|
1119
1120
|
console.log("SPO服务签名验证失败, tokenData: ", tokenData);
|
|
1120
|
-
throw [
|
|
1121
|
+
throw [errCfg.ACCESS_REFUSED, t("签名验证未通过")];
|
|
1121
1122
|
}
|
|
1122
1123
|
return verifyOK;
|
|
1123
1124
|
}
|
|
@@ -1133,26 +1134,26 @@ class apiUtil$2 {
|
|
|
1133
1134
|
if (!tokenData.companyInfo) {
|
|
1134
1135
|
tokenData.companyInfo = await apiUtil$2.queryCompanyInfo(tokenData.apiKey, true).catch(e => {
|
|
1135
1136
|
console.log("执行企业信息查询发生异常", e);
|
|
1136
|
-
throw [
|
|
1137
|
+
throw [errCfg.API_KEY_INVALID, t("执行企业信息查询发生异常:") + e.message || ""];
|
|
1137
1138
|
});
|
|
1138
1139
|
}
|
|
1139
1140
|
const companyInfo = tokenData.companyInfo;
|
|
1140
1141
|
if (companyInfo != null) {
|
|
1141
1142
|
let secret = apiUtil$2._getApiSecret(tokenData.apiKey);
|
|
1142
|
-
if (secret !== tokenData.apiSecret) throw
|
|
1143
|
+
if (secret !== tokenData.apiSecret) throw errCfg.API_SCREPT_INVALID;
|
|
1143
1144
|
// 不再检查 IP 白名单,改为验证证书签名
|
|
1144
1145
|
// console.log("fromIp:", tokenData.clientIp, "ipWhiteList:", companyInfo.ipWhiteList);
|
|
1145
1146
|
// let whiteList = companyInfo.ipWhiteList || "";
|
|
1146
1147
|
// // 没有设置ip白名单的就不检查了
|
|
1147
1148
|
// if (whiteList == "" || ipMatched(tokenData.clientIp, whiteList + ",127.0.0.1,1"))
|
|
1148
1149
|
if (apiUtil$2.signatureVerified(req)) return companyInfo;
|
|
1149
|
-
return apiUtil$2.
|
|
1150
|
+
return apiUtil$2.verifyApiTokenSignature(tokenData, req).then(x => {
|
|
1150
1151
|
return companyInfo;
|
|
1151
1152
|
}, e => {
|
|
1152
1153
|
throw e;
|
|
1153
1154
|
});
|
|
1154
1155
|
}
|
|
1155
|
-
throw [
|
|
1156
|
+
throw [errCfg.API_KEY_INVALID, tokenData.apiKey];
|
|
1156
1157
|
}
|
|
1157
1158
|
|
|
1158
1159
|
/**
|
|
@@ -1195,7 +1196,7 @@ class apiUtil$2 {
|
|
|
1195
1196
|
} else {
|
|
1196
1197
|
paramExists = typeof params[paramName] != 'undefined' || typeof params[paramName + paramSufix] != 'undefined';
|
|
1197
1198
|
}
|
|
1198
|
-
if (!paramExists) return ppUtil$3.errorPormise(
|
|
1199
|
+
if (!paramExists) return ppUtil$3.errorPormise(errCfg.ERROR(errCfg.PARAMETER_NEEDED, paramName));
|
|
1199
1200
|
}
|
|
1200
1201
|
return Promise.resolve(paramSufix);
|
|
1201
1202
|
}
|
|
@@ -1261,7 +1262,7 @@ class apiUtil$2 {
|
|
|
1261
1262
|
const myEnvHosts = apiUtil$2.getEnvHosts(params);
|
|
1262
1263
|
const myIp = ppUtil$3.getMyIp();
|
|
1263
1264
|
const host = params.host || myIp;
|
|
1264
|
-
if (!myEnvHosts.hosts.contains(host)) throw [
|
|
1265
|
+
if (!myEnvHosts.hosts.contains(host)) throw [errCfg.ACCESS_REFUSED, t_f$2("环境%s貌似无此主机:%s", conf$2.envId, params.host)];
|
|
1265
1266
|
if (!apiUtil$2.hostIsMySelf(host, options._res.req)) {
|
|
1266
1267
|
let result = await apiUtil$2.forwardsTo(host, myEnvHosts.port, params, options._res.req, myIp);
|
|
1267
1268
|
options._res.send(result);
|
|
@@ -1292,7 +1293,7 @@ class apiUtil$2 {
|
|
|
1292
1293
|
let result = await netUtil$1.apiRequest(reqOptions);
|
|
1293
1294
|
if (result.status !== 200) {
|
|
1294
1295
|
console.log(t_f$2("访问 %s 出错", reqOptions.url), result.status, result.res.statusMessage);
|
|
1295
|
-
throw [
|
|
1296
|
+
throw [errCfg.EXCEPTION, `[${result.status}] - ${result.res.statusMessage}`];
|
|
1296
1297
|
}
|
|
1297
1298
|
return result.data;
|
|
1298
1299
|
}
|
|
@@ -1314,7 +1315,7 @@ class apiUtil$2 {
|
|
|
1314
1315
|
key = keys[i];
|
|
1315
1316
|
value = parent[key];
|
|
1316
1317
|
if (value === undefined) {
|
|
1317
|
-
throw [
|
|
1318
|
+
throw [errCfg.INVALID_PARAM, t_f$2("无此配置项: %s", key)];
|
|
1318
1319
|
}
|
|
1319
1320
|
}
|
|
1320
1321
|
return {
|
|
@@ -1350,6 +1351,246 @@ class apiUtil$2 {
|
|
|
1350
1351
|
static corsCheckOrigin(origin, callback) {
|
|
1351
1352
|
callback(null, apiUtil$2.isOriginAllowed(origin));
|
|
1352
1353
|
}
|
|
1354
|
+
static validCek(cek, checkExpired = true) {
|
|
1355
|
+
if (!cek) {
|
|
1356
|
+
throw [errCfg.ACCESS_REFUSED, t("CEK(内容加密密钥)尚未申请或已过期")];
|
|
1357
|
+
}
|
|
1358
|
+
if (checkExpired && new Date().valueOf() >= cek.expireAt) {
|
|
1359
|
+
throw errCfg.CEK_EXPIRED;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
static encryptContent(content, cek) {
|
|
1363
|
+
return ppCrypto.encryptData(content, cek.key, 'base64');
|
|
1364
|
+
}
|
|
1365
|
+
static decryptContent(content, cek) {
|
|
1366
|
+
this.validCek(cek);
|
|
1367
|
+
try {
|
|
1368
|
+
const result = ppCrypto.decryptData(content, cek.key, 'utf8');
|
|
1369
|
+
if (result.startsWith('{') || result.startsWith('[')) {
|
|
1370
|
+
return JSON.parse(result);
|
|
1371
|
+
}
|
|
1372
|
+
return result;
|
|
1373
|
+
} catch (e) {
|
|
1374
|
+
throw [errCfg.DECRYPT_FAIL, e.message];
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
static decryptRequestContent(req, res) {
|
|
1378
|
+
if (!appSetting.e2eEncryptionNeeded) return;
|
|
1379
|
+
let params = apiUtil$2.extractParams(req);
|
|
1380
|
+
if (params.encryptedContent) {
|
|
1381
|
+
if (['DELETE', 'GET'].contains(req.method)) {
|
|
1382
|
+
params.encryptedContent = decodeURIComponent(params.encryptedContent);
|
|
1383
|
+
}
|
|
1384
|
+
if (!req.appInfo) {
|
|
1385
|
+
throw t('应用系统信息未知');
|
|
1386
|
+
}
|
|
1387
|
+
const decryptedContent = apiUtil$2.decryptContent(params.encryptedContent, req.appInfo.cek);
|
|
1388
|
+
delete params.encryptedContent;
|
|
1389
|
+
Object.assign(req.params, decryptedContent);
|
|
1390
|
+
}
|
|
1391
|
+
if (res) {
|
|
1392
|
+
apiUtil$2.validCek(req.appInfo.cek);
|
|
1393
|
+
res.cek = req.appInfo.cek; // 设置用于对称加密的CEK
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
static async restoreObj(key) {
|
|
1397
|
+
return kvStorage.restoreObj(key).catch(() => null);
|
|
1398
|
+
}
|
|
1399
|
+
static cekStoreKey(apiKey, req) {
|
|
1400
|
+
return `cek_${this.appAsPrefix}_${apiKey}_${apiUtil$2.getClientIp(req)}`;
|
|
1401
|
+
}
|
|
1402
|
+
static async generateCek(appInfo, req, key) {
|
|
1403
|
+
//todo: 检查既有 cek
|
|
1404
|
+
let t = new Date().valueOf();
|
|
1405
|
+
const cek = {
|
|
1406
|
+
key: key || ppUtil$3.newGuid() + ppUtil$3.newGuid(),
|
|
1407
|
+
//要求32字节
|
|
1408
|
+
issueAt: t,
|
|
1409
|
+
expireAt: t + appSetting.cekExpireTime * 1000
|
|
1410
|
+
};
|
|
1411
|
+
if (appInfo) {
|
|
1412
|
+
// appInfo.cek = cek;
|
|
1413
|
+
await kvStorage.storeObj(cekStoreKey(appInfo.apiKey, req), cek, appSetting.cekExpireTime);
|
|
1414
|
+
}
|
|
1415
|
+
return cek;
|
|
1416
|
+
}
|
|
1417
|
+
static async getContentEncKey(options) {
|
|
1418
|
+
const req = options._res.req;
|
|
1419
|
+
try {
|
|
1420
|
+
const appInfo = req.appInfo;
|
|
1421
|
+
//todo: 检查既有CEK,限制频繁申请
|
|
1422
|
+
const apiKey = appInfo.apiKey;
|
|
1423
|
+
let cek = null;
|
|
1424
|
+
if (appSetting.e2eEncryptionNeeded) {
|
|
1425
|
+
const cekKey = apiUtil$2.cekStoreKey(apiKey, req);
|
|
1426
|
+
// cek = await apiUtil.restoreObj(cekKey);
|
|
1427
|
+
cek = await apiUtil$2.restoreObj(cekKey);
|
|
1428
|
+
if (cek && new Date().valueOf() + 60 * 1000 >= cek.expireAt) cek = null;
|
|
1429
|
+
if (!cek) {
|
|
1430
|
+
cek = await apiUtil$2.generateCek(appInfo, req);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
let result = apiUtil$2.apiSuccess(cek, req);
|
|
1434
|
+
delete result.message; //减少数据尺寸,否则数据太长无法用公钥加密
|
|
1435
|
+
delete result._elapse;
|
|
1436
|
+
// options._res.publicKey = appInfo.publicKey; // 设置用于加密结果的公钥
|
|
1437
|
+
return result;
|
|
1438
|
+
} catch (e) {
|
|
1439
|
+
return apiUtil$2.apiFail(e, req);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
static async restoreAppInfo(apiKey, req) {
|
|
1443
|
+
let appInfo = await apiUtil$2.restoreObj(`${this.appAsPrefix}App_${apiKey}`, appSetting.tokenExpireTime);
|
|
1444
|
+
if (!appInfo) {
|
|
1445
|
+
appInfo = await dbUtil.dbQueryOneAndUnstringify(dbSql.APP_QUERY, {
|
|
1446
|
+
apiKey
|
|
1447
|
+
}, "exData");
|
|
1448
|
+
if (appInfo) {
|
|
1449
|
+
// await generateCek(appInfo);
|
|
1450
|
+
await kvStorage.storeObj(`cbpApp_${appInfo.apiKey}`, appInfo, appSetting.tokenExpireTime);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
// if (appInfo && !appInfo.cek && appSetting.e2eEncryptionNeeded) {
|
|
1454
|
+
if (appInfo && appSetting.e2eEncryptionNeeded) {
|
|
1455
|
+
appInfo.cek = await apiUtil$2.restoreObj(apiUtil$2.cekStoreKey(apiKey, req));
|
|
1456
|
+
}
|
|
1457
|
+
return appInfo;
|
|
1458
|
+
}
|
|
1459
|
+
static async generateApiSignature(options) {
|
|
1460
|
+
const params = options.replacements || options;
|
|
1461
|
+
await this.parametersOK(params, 'apiKey', 'privateKey');
|
|
1462
|
+
// console.log("generateApiSignature for apiKey", params.apiKey);
|
|
1463
|
+
// console.log("privateKey", params.privateKey);
|
|
1464
|
+
const result = {
|
|
1465
|
+
headers: {
|
|
1466
|
+
"api-key": params.apiKey
|
|
1467
|
+
},
|
|
1468
|
+
body: {
|
|
1469
|
+
encryptedContent: ''
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
const appInfo = await this.restoreAppInfo(params.apiKey, options._res.req);
|
|
1473
|
+
if (!appInfo) {
|
|
1474
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("apiKey (%s) 无效", params.apiKey)];
|
|
1475
|
+
}
|
|
1476
|
+
let content = '';
|
|
1477
|
+
if (appSetting.e2eEncryptionNeeded) {
|
|
1478
|
+
if (params.decryptedContent) {
|
|
1479
|
+
// if (!appInfo.cek) {
|
|
1480
|
+
// await generateCek(appInfo);
|
|
1481
|
+
// }
|
|
1482
|
+
await this.parametersOK(params, 'cek');
|
|
1483
|
+
const cek = await this.generateCek(undefined, undefined, params.cek.key || params.cek); //避免过期
|
|
1484
|
+
// commonUtil.validCek(params.cek);
|
|
1485
|
+
content = this.encryptContent(params.decryptedContent, cek);
|
|
1486
|
+
result.body.encryptedContent = content;
|
|
1487
|
+
}
|
|
1488
|
+
} else {
|
|
1489
|
+
// result.body = null
|
|
1490
|
+
content = JSON.stringify(params.decryptedContent);
|
|
1491
|
+
}
|
|
1492
|
+
if (appSetting.reqSignatureNeeded) {
|
|
1493
|
+
result.headers.timestamp = new Date().valueOf();
|
|
1494
|
+
result.headers.signature = this.getRequestSignature(appInfo.apiKey, result.headers.timestamp, content, params.privateKey);
|
|
1495
|
+
}
|
|
1496
|
+
return result;
|
|
1497
|
+
}
|
|
1498
|
+
static getRequestSignature(apiKey, timestamp, content, privateKey) {
|
|
1499
|
+
let dataToSign = apiKey + timestamp + (content || '');
|
|
1500
|
+
const sign = crypto.createSign('RSA-SHA256');
|
|
1501
|
+
sign.update(dataToSign);
|
|
1502
|
+
return sign.sign(privateKey, 'base64');
|
|
1503
|
+
}
|
|
1504
|
+
static async decryptApiResponse(options) {
|
|
1505
|
+
const params = options.replacements || options;
|
|
1506
|
+
await this.parametersOK(params, 'encryptedContent', 'cek/privateKey');
|
|
1507
|
+
let result;
|
|
1508
|
+
if (params.cek) {
|
|
1509
|
+
const cek = await this.generateCek(undefined, undefined, params.cek.key || params.cek); //避免过期
|
|
1510
|
+
result = this.decryptContent(params.encryptedContent, cek);
|
|
1511
|
+
console.log('CEK 解密结果:', result);
|
|
1512
|
+
} else {
|
|
1513
|
+
let content = await ppCrypto.rsaDecrypt(params);
|
|
1514
|
+
result = JSON.parse(content.decryptedContent);
|
|
1515
|
+
// result = await commonUtil.rsaDecrypt(params);
|
|
1516
|
+
}
|
|
1517
|
+
return result;
|
|
1518
|
+
}
|
|
1519
|
+
static async reqAppInfoNeeded(req) {
|
|
1520
|
+
if (!req.appInfo) {
|
|
1521
|
+
let apiKey = req.headers["api-key"] || req.leInfo?.apiKey || req.userInfo?.apiKey;
|
|
1522
|
+
if (!apiKey) {
|
|
1523
|
+
throw [errCfg.ACCESS_REFUSED, t("必须在请求头中设置api-key")];
|
|
1524
|
+
}
|
|
1525
|
+
req.appInfo = await this.restoreAppInfo(apiKey, req);
|
|
1526
|
+
if (!req.appInfo) {
|
|
1527
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("api-key (%s) 无效", apiKey)];
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
static async verifyApiSignature(req) {
|
|
1532
|
+
if (req.appInfo) {
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
await this.reqAppInfoNeeded(req);
|
|
1536
|
+
if (!appSetting.reqSignatureNeeded) return true;
|
|
1537
|
+
if (!req.headers.timestamp) {
|
|
1538
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中设置时戳")];
|
|
1539
|
+
}
|
|
1540
|
+
let timestamp = parseInt(req.headers.timestamp);
|
|
1541
|
+
let currentTime = new Date().valueOf();
|
|
1542
|
+
let maxTimeDiff = apiUtil$2.isDebugMode(req) ? 7 * 24 * 3600 : apiUtil$2.isTestMode(req) ? 4 * 3600 : 300; // 100
|
|
1543
|
+
if (timestamp.toString() === "NaN" || currentTime < timestamp - 5000 || currentTime - timestamp > maxTimeDiff * 1000) {
|
|
1544
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("时戳(%s)无效,当前时戳:%s,时差 %s秒", timestamp, currentTime, (currentTime - timestamp) / 1000)];
|
|
1545
|
+
}
|
|
1546
|
+
let signature = req.headers.signature;
|
|
1547
|
+
if (!signature) {
|
|
1548
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中提供签名")];
|
|
1549
|
+
}
|
|
1550
|
+
const params = apiUtil$2.extractParams(req);
|
|
1551
|
+
const content = appSetting.e2eEncryptionNeeded ? params?.encryptedContent || '' : JSON.stringify(params || null);
|
|
1552
|
+
const apiKey = req.appInfo.apiKey;
|
|
1553
|
+
let signData = apiKey + timestamp + content;
|
|
1554
|
+
let verify = crypto.createVerify('RSA-SHA256');
|
|
1555
|
+
verify.update(signData);
|
|
1556
|
+
let verifyOK = false;
|
|
1557
|
+
try {
|
|
1558
|
+
verifyOK = verify.verify(req.appInfo.publicKey, signature, 'base64');
|
|
1559
|
+
} catch (e) {
|
|
1560
|
+
console.log("服务签名验证异常, ", e);
|
|
1561
|
+
verifyOK = false;
|
|
1562
|
+
}
|
|
1563
|
+
if (!verifyOK) {
|
|
1564
|
+
// console.log("服务签名验证失败, tokenData: ", tokenData);
|
|
1565
|
+
throw [errCfg.ACCESS_REFUSED, t("签名验证未通过")];
|
|
1566
|
+
}
|
|
1567
|
+
return verifyOK;
|
|
1568
|
+
}
|
|
1569
|
+
static checkWhiteList(req) {
|
|
1570
|
+
const ipWhiteList = req.appInfo.exData?.ipWhiteList;
|
|
1571
|
+
if (!ipWhiteList || ipWhiteList.length === 0) {
|
|
1572
|
+
// 没有设置ip白名单的就不检查了
|
|
1573
|
+
return true;
|
|
1574
|
+
}
|
|
1575
|
+
let clientIp = apiUtil$2.getClientIp(req);
|
|
1576
|
+
console.log("fromIp:", clientIp, "ipWhiteList:", ipWhiteList);
|
|
1577
|
+
if (!clientIp) {
|
|
1578
|
+
throw errCfg.GET_CLIENTIP_FAIL;
|
|
1579
|
+
}
|
|
1580
|
+
return this.ipMatched(clientIp, ipWhiteList, true);
|
|
1581
|
+
}
|
|
1582
|
+
static ipMatched(fromIp, whiteList, localAllowed) {
|
|
1583
|
+
let ips = Array.isArray(whiteList) ? whiteList : (whiteList || '').split(',');
|
|
1584
|
+
for (let i = 0; i < ips.length; i++) {
|
|
1585
|
+
let ip = ips[i];
|
|
1586
|
+
if (ip === fromIp) return true;
|
|
1587
|
+
if (ip.indexOf("*") > 0) {
|
|
1588
|
+
let l = ip.indexOf("*");
|
|
1589
|
+
if (fromIp.substring(0, l) === ip.substring(0, l)) return true;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return localAllowed && (fromIp === "127.0.0.1" || fromIp === "1");
|
|
1593
|
+
}
|
|
1353
1594
|
|
|
1354
1595
|
//#endregion
|
|
1355
1596
|
|
|
@@ -1363,7 +1604,7 @@ class apiUtil$2 {
|
|
|
1363
1604
|
await apiUtil$2.checkRequestToken(req);
|
|
1364
1605
|
next();
|
|
1365
1606
|
} catch (e) {
|
|
1366
|
-
res.send(
|
|
1607
|
+
res.send(errCfg.ERROR(e));
|
|
1367
1608
|
}
|
|
1368
1609
|
return false;
|
|
1369
1610
|
}
|
|
@@ -1405,7 +1646,7 @@ class apiUtil$2 {
|
|
|
1405
1646
|
}
|
|
1406
1647
|
return true;
|
|
1407
1648
|
} catch (e) {
|
|
1408
|
-
res.send(
|
|
1649
|
+
res.send(errCfg.ERROR(e));
|
|
1409
1650
|
return false;
|
|
1410
1651
|
}
|
|
1411
1652
|
}
|
|
@@ -1426,9 +1667,9 @@ class apiUtil$2 {
|
|
|
1426
1667
|
req.isMobile = true;
|
|
1427
1668
|
if (!apiUtil$2.signatureVerified(req)) {
|
|
1428
1669
|
let params = apiUtil$2.extractParams(req);
|
|
1429
|
-
moveProperty(params, req.headers, "access_token");
|
|
1430
|
-
moveProperty(params, req.headers, "timestamp");
|
|
1431
|
-
if (moveProperty(params, req.headers, "signature")) {
|
|
1670
|
+
ppUtil$3.moveProperty(params, req.headers, "access_token");
|
|
1671
|
+
ppUtil$3.moveProperty(params, req.headers, "timestamp");
|
|
1672
|
+
if (ppUtil$3.moveProperty(params, req.headers, "signature")) {
|
|
1432
1673
|
req.headers.signature = decodeURIComponent(req.headers.signature);
|
|
1433
1674
|
}
|
|
1434
1675
|
}
|
|
@@ -1448,11 +1689,11 @@ class apiUtil$2 {
|
|
|
1448
1689
|
apiUtil$2.createApiCallRec(tokenData, req);
|
|
1449
1690
|
let errResponse = null;
|
|
1450
1691
|
if (!tokenData.clientIp) {
|
|
1451
|
-
errResponse = apiUtil$2.spoApiFail(
|
|
1692
|
+
errResponse = apiUtil$2.spoApiFail(errCfg.GET_CLIENTIP_FAIL, req);
|
|
1452
1693
|
} else if (!tokenData.apiKey) {
|
|
1453
|
-
errResponse = apiUtil$2.spoApiFail([
|
|
1694
|
+
errResponse = apiUtil$2.spoApiFail([errCfg.PARAMETER_NEEDED, "apiKey"], req);
|
|
1454
1695
|
} else if (!tokenData.apiSecret) {
|
|
1455
|
-
errResponse = apiUtil$2.spoApiFail([
|
|
1696
|
+
errResponse = apiUtil$2.spoApiFail([errCfg.PARAMETER_NEEDED, "apiSecret"], req);
|
|
1456
1697
|
}
|
|
1457
1698
|
if (errResponse != null) {
|
|
1458
1699
|
apiUtil$2._saveApiCallRec(req.apiCallRec, errResponse);
|
|
@@ -1481,7 +1722,7 @@ class apiUtil$2 {
|
|
|
1481
1722
|
}
|
|
1482
1723
|
let accessToken = apiUtil$2.extractToken(req);
|
|
1483
1724
|
if (!accessToken) {
|
|
1484
|
-
res.send(apiUtil$2.spoApiFail(
|
|
1725
|
+
res.send(apiUtil$2.spoApiFail(errCfg.TOKEN_NEEDED, req));
|
|
1485
1726
|
// apiUtil.sendOrRedirect(req, res, spoApiFail(err.TOKEN_NEEDED, req));
|
|
1486
1727
|
return;
|
|
1487
1728
|
}
|
|
@@ -1501,7 +1742,7 @@ class apiUtil$2 {
|
|
|
1501
1742
|
return apiUtil$2.$checkApiKeyOld(req, res, next);
|
|
1502
1743
|
}).catch(e => {
|
|
1503
1744
|
const params = apiUtil$2.extractParams(req);
|
|
1504
|
-
apiUtil$2.sendOrRedirect(res, params.redirectUrl || params.callbackUrl, apiUtil$2.spoApiFail(
|
|
1745
|
+
apiUtil$2.sendOrRedirect(res, params.redirectUrl || params.callbackUrl, apiUtil$2.spoApiFail(errCfg.TOKEN_INVALID, req));
|
|
1505
1746
|
});
|
|
1506
1747
|
}
|
|
1507
1748
|
|
|
@@ -1611,7 +1852,7 @@ class apiUtil$2 {
|
|
|
1611
1852
|
await apiUtil$2.parametersOK(params, "value");
|
|
1612
1853
|
let c = apiUtil$2.parseCfgPath(cfgItem);
|
|
1613
1854
|
if (typeof c.value === "object") {
|
|
1614
|
-
throw [
|
|
1855
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("暂不支持修改复杂配置")];
|
|
1615
1856
|
}
|
|
1616
1857
|
// c.oldValue = c.value;
|
|
1617
1858
|
c.newValue = params.value;
|
|
@@ -1664,11 +1905,11 @@ class apiUtil$2 {
|
|
|
1664
1905
|
//#endregion
|
|
1665
1906
|
}
|
|
1666
1907
|
function configNeeded() {
|
|
1667
|
-
if (!conf$2 || !
|
|
1908
|
+
if (!conf$2 || !errCfg) {
|
|
1668
1909
|
ppUtil$3.configNeeded();
|
|
1669
1910
|
conf$2 = ppUtil$3.appConfig;
|
|
1670
1911
|
appSetting = conf$2.appSetting;
|
|
1671
|
-
|
|
1912
|
+
errCfg = ppUtil$3.appErrCfg;
|
|
1672
1913
|
}
|
|
1673
1914
|
}
|
|
1674
1915
|
var ppUtilApi = apiUtil$2;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abler-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "API服务相关工具",
|
|
5
5
|
"main": "./dist/cjs/pp-util.js",
|
|
6
6
|
"-module": "./dist/es/pp-util.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"q": "^1.5.1",
|
|
19
|
-
"abler-util": ">=0.
|
|
19
|
+
"abler-util": ">=0.3.0",
|
|
20
20
|
"abler-db": ">=0.1.1",
|
|
21
21
|
"abler-i18n": "^0.1.20",
|
|
22
22
|
"abler-messenger": ">=0.1.1",
|