w-web-api 1.0.81 → 1.0.83
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/css/{app.9fbdb016.css → app.6da12a32.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.bc36c734.js +1 -0
- package/dist/js/app.bc36c734.js +1 -0
- package/dist/js/{chunk-vendors.de0404f6.js → chunk-vendors-legacy.a68e060c.js} +3 -3
- package/dist/js/{chunk-vendors-legacy.de0404f6.js → chunk-vendors.a68e060c.js} +3 -3
- package/dist/w-web-api.umd.js +2 -2
- package/dist/w-web-api.umd.js.map +1 -1
- package/docs/WWebApi.html +42 -2
- package/docs/index.html +1 -1
- package/docs/server_WWebApi.mjs.html +101 -18
- package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
- package/package.json +14 -18
- package/server/WWebApi.mjs +100 -17
- package/server/procApis.mjs +3 -1
- package/server/procLang.mjs +12 -0
- package/server/procProxy.mjs +183 -41
- package/server/provideTabs.mjs +9 -8
- package/server/staLogs/staLogsCore.mjs +441 -420
- package/src/components/LayoutContent.vue +25 -20
- package/src/components/LayoutContentEdit.vue +42 -13
- package/src/components/LayoutContentStats.vue +81 -22
- package/src/components/LayoutContentTest.vue +63 -91
- package/src/main.js +3 -1
- package/src/plugins/mShare.mjs +105 -0
- package/src/store/mutations.mjs +1 -0
- package/srv.mjs +9 -4
- package/tableTags-web-api.json +1 -0
- package/dist/js/app-legacy.075a8cf9.js +0 -1
- package/dist/js/app.075a8cf9.js +0 -1
- package/server/staLogs/test_staEvent.mjs +0 -214
- package/tableTags.json +0 -1
package/docs/WWebApi.html
CHANGED
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<dt class="tag-source">Source:</dt>
|
|
82
82
|
<dd class="tag-source"><ul class="dummy"><li>
|
|
83
|
-
<a href="server_WWebApi.mjs.html">server/WWebApi.mjs</a>, <a href="server_WWebApi.mjs.html#
|
|
83
|
+
<a href="server_WWebApi.mjs.html">server/WWebApi.mjs</a>, <a href="server_WWebApi.mjs.html#line141">line 141</a>
|
|
84
84
|
</li></ul></dd>
|
|
85
85
|
|
|
86
86
|
|
|
@@ -991,6 +991,46 @@ instWWebApi.on('error', (err) => {
|
|
|
991
991
|
</tr>
|
|
992
992
|
|
|
993
993
|
|
|
994
|
+
|
|
995
|
+
<tr>
|
|
996
|
+
|
|
997
|
+
<td class="name"><code>isAllowTarget</code></td>
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
<td class="type">
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
<span class="param-type">function</span>
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
</td>
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
<td class="attributes">
|
|
1012
|
+
|
|
1013
|
+
<optional><br>
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
</td>
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
<td class="default">
|
|
1024
|
+
|
|
1025
|
+
<code>null</code>
|
|
1026
|
+
|
|
1027
|
+
</td>
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
<td class="description last"><p>輸入測試分頁proxy之目標位址檢核函數,函數會傳入目標網址字串(首跳與轉址後每一跳皆會呼叫),回傳false即拒絕該請求(回err-key 'errReqTargetNotAllowed'),預設null(不限制)</p></td>
|
|
1031
|
+
</tr>
|
|
1032
|
+
|
|
1033
|
+
|
|
994
1034
|
</tbody>
|
|
995
1035
|
</table>
|
|
996
1036
|
|
|
@@ -1079,7 +1119,7 @@ instWWebApi.on('error', (err) => {
|
|
|
1079
1119
|
<br class="clear">
|
|
1080
1120
|
|
|
1081
1121
|
<footer>
|
|
1082
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
1122
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 12:02:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
1083
1123
|
</footer>
|
|
1084
1124
|
|
|
1085
1125
|
<script>prettyPrint();</script>
|
package/docs/index.html
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<br class="clear">
|
|
72
72
|
|
|
73
73
|
<footer>
|
|
74
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
74
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 12:02:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
|
@@ -53,8 +53,9 @@ import map from 'lodash-es/map.js'
|
|
|
53
53
|
import keys from 'lodash-es/keys.js'
|
|
54
54
|
import j2o from 'wsemi/src/j2o.mjs'
|
|
55
55
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
56
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
56
57
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
57
|
-
import
|
|
58
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
58
59
|
import ispint from 'wsemi/src/ispint.mjs'
|
|
59
60
|
import isErr from 'wsemi/src/isErr.mjs'
|
|
60
61
|
import isfun from 'wsemi/src/isfun.mjs'
|
|
@@ -103,6 +104,7 @@ import ds from '../src/schema/index.mjs'
|
|
|
103
104
|
* @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串(srLog 寫入與統計掃描共用),預設'./logs'
|
|
104
105
|
* @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
|
|
105
106
|
* @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
|
|
107
|
+
* @param {Function} [opt.isAllowTarget=null] 輸入測試分頁proxy之目標位址檢核函數,函數會傳入目標網址字串(首跳與轉址後每一跳皆會呼叫),回傳false即拒絕該請求(回err-key 'errReqTargetNotAllowed'),預設null(不限制)
|
|
106
108
|
* @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
|
|
107
109
|
* @example
|
|
108
110
|
*
|
|
@@ -281,6 +283,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
281
283
|
let showLanguage = get(opt, 'showLanguage', 'y')
|
|
282
284
|
let language = get(opt, 'language', 'eng')
|
|
283
285
|
let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
|
|
286
|
+
let isAllowTarget = get(opt, 'isAllowTarget', null) //測試分頁 proxy 之目標檢核(含轉址每一跳),交 procProxy;改造前 procProxy 雖支援此 hook 但建構子未接線
|
|
284
287
|
|
|
285
288
|
|
|
286
289
|
//kpLangExt
|
|
@@ -313,7 +316,30 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
313
316
|
let srLog = srLogInit(opt)
|
|
314
317
|
|
|
315
318
|
//maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
|
|
316
|
-
|
|
319
|
+
//陣列亦須逐元素遮罩:Hapi 對重複之 query 參數(?token=a&token=b)給陣列,若只認字串則原樣印出完整憑證
|
|
320
|
+
//(複審實測 `req.query { token: [ 'ARRAYSECRET', 'x' ] }` 明文落 stdout);此處收斂後 maskQuery 與 token 兩行同時生效。
|
|
321
|
+
let maskTok = (t) => {
|
|
322
|
+
if (isarr(t)) {
|
|
323
|
+
return t.map(maskTok)
|
|
324
|
+
}
|
|
325
|
+
return isestr(t) ? (strleft(t, 4) + '***') : t
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
//maskQuery:印 req.query 整包時,把其中之憑證欄位遮罩後才輸出。
|
|
329
|
+
//why:query 內含 token,直接 console.log(query) 會讓完整憑證落入 stdout,等同繞過 maskTok(原本三處
|
|
330
|
+
//「先印整包 query、下一行才印遮罩後 token」即為此情形)。回傳淺拷貝,不動原物件。
|
|
331
|
+
let maskQuery = (q) => {
|
|
332
|
+
if (!isobj(q)) {
|
|
333
|
+
return q
|
|
334
|
+
}
|
|
335
|
+
let r = { ...q }
|
|
336
|
+
for (let k of Object.keys(r)) {
|
|
337
|
+
if (k.toLowerCase() === 'token') {
|
|
338
|
+
r[k] = maskTok(r[k])
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return r
|
|
342
|
+
}
|
|
317
343
|
|
|
318
344
|
//kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
|
|
319
345
|
//原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
|
|
@@ -332,9 +358,24 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
332
358
|
}
|
|
333
359
|
}
|
|
334
360
|
|
|
361
|
+
//HTTP route 邊界統一處理(與 kpFunExt 之 wrapErrLog 同構):core 內任一 reject 之 err-key 皆於此記錄後原樣再拋。
|
|
362
|
+
//why:route 之錯誤路徑(token 無效、keyTable 無效、user 無效、payload 無效)原僅 console.log,不入 srLog,
|
|
363
|
+
//而統計頁與稽核讀的正是 srLog,導致認證失敗永不可見。逐點加 log 會使同一規則散落多處,故收斂於邊界。
|
|
364
|
+
let wrapRouteErrLog = (event, fn) => {
|
|
365
|
+
return async (...args) => {
|
|
366
|
+
try {
|
|
367
|
+
return await fn(...args)
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
srLog.error({ event, err })
|
|
371
|
+
throw err
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
335
376
|
|
|
336
377
|
//procProxy
|
|
337
|
-
let { proxyRequest } = procProxy({ srLog })
|
|
378
|
+
let { proxyRequest } = procProxy({ srLog, isAllowTarget })
|
|
338
379
|
|
|
339
380
|
|
|
340
381
|
//procApis
|
|
@@ -461,7 +502,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
461
502
|
|
|
462
503
|
//check
|
|
463
504
|
if (!iseobj(user)) {
|
|
464
|
-
console.log(`token`, token)
|
|
505
|
+
console.log(`token`, maskTok(token))
|
|
465
506
|
console.log(`can not find the user from token`)
|
|
466
507
|
return Promise.reject('errUserNotFound')
|
|
467
508
|
}
|
|
@@ -522,8 +563,9 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
522
563
|
//rows
|
|
523
564
|
let rows = get(payload, 'rows', [])
|
|
524
565
|
|
|
525
|
-
//check
|
|
526
|
-
|
|
566
|
+
//check(陣列層:非空陣列即可;逐列檢核統一於 syncAndReplaceTabs,使 [null] 與 [null,null] 皆回同一 err-key errRowInvalid,
|
|
567
|
+
//不因 wsemi isearr「僅長度 1 檢查元素」而分岔)
|
|
568
|
+
if (!isarr(rows) || rows.length === 0) {
|
|
527
569
|
console.log('payload', payload)
|
|
528
570
|
console.log(`invalid rows in payload`)
|
|
529
571
|
return Promise.reject('errRowsInvalid')
|
|
@@ -554,6 +596,12 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
554
596
|
// }
|
|
555
597
|
|
|
556
598
|
|
|
599
|
+
//各資料表同步時之必填欄位(與前端編輯表單同步檢測對稱:apis 之 name/url 必填)
|
|
600
|
+
let kpRequiredFields = {
|
|
601
|
+
apis: ['name', 'url'],
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
|
|
557
605
|
//syncAndReplaceTabs
|
|
558
606
|
let syncAndReplaceTabs = async (userId, inp, keyTable) => {
|
|
559
607
|
|
|
@@ -573,13 +621,30 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
573
621
|
let rows = get(inp, 'rows', [])
|
|
574
622
|
// console.log('rows', rows)
|
|
575
623
|
|
|
576
|
-
//check
|
|
577
|
-
if (!
|
|
624
|
+
//check(陣列層:須為非空陣列;逐列檢核見下)
|
|
625
|
+
if (!isarr(rows) || rows.length === 0) {
|
|
578
626
|
console.log('inp', inp)
|
|
579
627
|
console.log('keyTable', keyTable)
|
|
580
628
|
return Promise.reject('errRowsInvalid')
|
|
581
629
|
}
|
|
582
630
|
|
|
631
|
+
//逐列檢核:任一列無效即整批拒絕(回 err-key errRowInvalid),且一律在 delAll 之前,既有群組資料不動;
|
|
632
|
+
//列索引與欄位名入 srLog 供整合端查因(回呼叫端只給 key,同其他錯誤契約)。
|
|
633
|
+
//why:改造前只有陣列層 isearr(wsemi:僅長度 1 時檢查元素),[null,null]/[1,2] 可通過,下方 v.group= 即 TypeError,
|
|
634
|
+
//原始例外訊息經 pm2resolve 外洩給呼叫端;缺必填欄位之物件列則經 ltdtmapping 補空值後以空名稱進樹。
|
|
635
|
+
let rejectRow = (index, field) => {
|
|
636
|
+
srLog.error({ event: 'syncAndReplaceTabs', err: 'errRowInvalid', keyTable, group, index, field })
|
|
637
|
+
console.log(`[API]syncAndReplaceTabs/check rows[${index}]: invalid row (field: ${field})`)
|
|
638
|
+
return Promise.reject('errRowInvalid')
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
//逐列檢核 ①:每列須為物件(非物件列連 group 都無法寫入)
|
|
642
|
+
for (let k = 0; k < rows.length; k++) {
|
|
643
|
+
if (!iseobj(rows[k])) {
|
|
644
|
+
return rejectRow(k, '(row)')
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
583
648
|
//save from
|
|
584
649
|
rows = map(rows, (v, k) => {
|
|
585
650
|
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
@@ -606,11 +671,20 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
606
671
|
})
|
|
607
672
|
}
|
|
608
673
|
|
|
674
|
+
//逐列檢核 ②(b64 還原後):必填欄位須為非空字串(funNew/ltdtmapping 只補預設值、不驗證)
|
|
675
|
+
let ksRequired = get(kpRequiredFields, keyTable, [])
|
|
676
|
+
for (let k = 0; k < rows.length; k++) {
|
|
677
|
+
let field = ksRequired.find((f) => !isestr(rows[k][f]))
|
|
678
|
+
if (isestr(field)) {
|
|
679
|
+
return rejectRow(k, field)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
609
683
|
//ltdtmapping
|
|
610
684
|
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
611
685
|
// console.log('ltdtmapping rows', rows)
|
|
612
686
|
|
|
613
|
-
//delAll group
|
|
687
|
+
//delAll group(逐列檢核皆已通過,此後才動既有資料;delAll→insert 非原子屬已知限制 ADR-016)
|
|
614
688
|
await woItems[keyTable].delAll({ group })
|
|
615
689
|
|
|
616
690
|
//insert
|
|
@@ -673,7 +747,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
673
747
|
|
|
674
748
|
//check
|
|
675
749
|
if (!isestr(token)) {
|
|
676
|
-
console.log('req.query', get(req, 'query'))
|
|
750
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
677
751
|
console.log('token', maskTok(token))
|
|
678
752
|
console.log('[API]getUserByToken/check token: invalid token')
|
|
679
753
|
return Promise.reject('errTokenNoPermission')
|
|
@@ -694,7 +768,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
694
768
|
}
|
|
695
769
|
|
|
696
770
|
//pm2resolve core
|
|
697
|
-
let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
|
|
771
|
+
let r = await pm2resolve(wrapRouteErrLog('api/getUserByToken', core))() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
|
|
698
772
|
if (isErr(r.msg)) {
|
|
699
773
|
r.msg = r.msg.message
|
|
700
774
|
}
|
|
@@ -814,7 +888,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
814
888
|
|
|
815
889
|
//check
|
|
816
890
|
if (!isestr(token)) {
|
|
817
|
-
console.log('req.query', get(req, 'query'))
|
|
891
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
818
892
|
console.log('token', maskTok(token))
|
|
819
893
|
console.log('[API]syncAndReplaceTabs/check token: invalid token')
|
|
820
894
|
return Promise.reject('errTokenNoPermission')
|
|
@@ -825,7 +899,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
825
899
|
|
|
826
900
|
//check
|
|
827
901
|
if (!isestr(keyTable)) {
|
|
828
|
-
console.log('req.query', get(req, 'query'))
|
|
902
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
829
903
|
console.log('keyTable', keyTable)
|
|
830
904
|
console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
|
|
831
905
|
return Promise.reject('errKeyTableInvalid')
|
|
@@ -870,7 +944,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
870
944
|
}
|
|
871
945
|
|
|
872
946
|
//pm2resolve core
|
|
873
|
-
let r = await pm2resolve(core)()
|
|
947
|
+
let r = await pm2resolve(wrapRouteErrLog('api/syncAndReplaceTabs', core))()
|
|
874
948
|
if (isErr(r.msg)) {
|
|
875
949
|
r.msg = r.msg.message
|
|
876
950
|
}
|
|
@@ -910,6 +984,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
910
984
|
console.log('apiType', apiType)
|
|
911
985
|
console.log('authorization', maskTok(authorization))
|
|
912
986
|
console.log('invalid token')
|
|
987
|
+
srLog.error({ event: 'verifyConn', err: 'errTokenNoPermission', apiType }) //認證失敗須入 log 供稽核/統計(CLAUDE.md「log 記錄要點」)
|
|
913
988
|
return false
|
|
914
989
|
}
|
|
915
990
|
|
|
@@ -917,9 +992,17 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
917
992
|
//套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
|
|
918
993
|
//或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
|
|
919
994
|
//保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
|
|
920
|
-
|
|
995
|
+
//checkToken 失敗會 throw(err-key),原本直接向上拋致認證失敗不入 log;此處攔截記錄後原樣再拋,行為不變。
|
|
996
|
+
let b
|
|
997
|
+
try {
|
|
998
|
+
b = await p.checkToken(token)
|
|
999
|
+
}
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
srLog.error({ event: 'verifyConn', err, apiType })
|
|
1002
|
+
throw err
|
|
1003
|
+
}
|
|
921
1004
|
|
|
922
|
-
srLog.info({ event: 'verifyConn', apiType }) //連線之 token
|
|
1005
|
+
srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過
|
|
923
1006
|
return b
|
|
924
1007
|
},
|
|
925
1008
|
getUserIdByToken: async (token) => { //可使用async或sync函數
|
|
@@ -969,7 +1052,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
969
1052
|
// },
|
|
970
1053
|
|
|
971
1054
|
},
|
|
972
|
-
|
|
1055
|
+
fpTableTags: 'tableTags-web-api.json', //w-serv-hapi 1.0.87 起正名 fpTableTags(同 sso / perm); 舊名 fnTableTags 於 1.0.86 傳到 w-serv-webdata 時不被讀取, 實際一直寫預設 tableTags.json
|
|
973
1056
|
})
|
|
974
1057
|
|
|
975
1058
|
|
|
@@ -992,7 +1075,7 @@ export default WWebApi
|
|
|
992
1075
|
<br class="clear">
|
|
993
1076
|
|
|
994
1077
|
<footer>
|
|
995
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
1078
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 12:02:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
996
1079
|
</footer>
|
|
997
1080
|
|
|
998
1081
|
<script>prettyPrint();</script>
|
|
@@ -174,7 +174,7 @@ export default {
|
|
|
174
174
|
<br class="clear">
|
|
175
175
|
|
|
176
176
|
<footer>
|
|
177
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
177
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 12:02:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
178
178
|
</footer>
|
|
179
179
|
|
|
180
180
|
<script>prettyPrint();</script>
|
package/package.json
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-web-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.83",
|
|
4
4
|
"main": "dist/w-web-api.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"axios": "^1.18.1",
|
|
7
6
|
"json5": "^2.2.3",
|
|
8
|
-
"w-component-vue": "^2.5.
|
|
9
|
-
"w-data-collector": "^1.0.
|
|
10
|
-
"w-echarts-vue": "^1.0.
|
|
11
|
-
"w-md2html": "^1.0.
|
|
12
|
-
"w-serv-hapi": "^1.0.
|
|
13
|
-
"w-serv-orm": "^1.0.
|
|
14
|
-
"w-syslog": "^1.0.
|
|
15
|
-
"w-ui-loginout": "^1.0.
|
|
16
|
-
"wsemi": "^1.8.
|
|
7
|
+
"w-component-vue": "^2.5.12",
|
|
8
|
+
"w-data-collector": "^1.0.36",
|
|
9
|
+
"w-echarts-vue": "^1.0.10",
|
|
10
|
+
"w-md2html": "^1.0.50",
|
|
11
|
+
"w-serv-hapi": "^1.0.87",
|
|
12
|
+
"w-serv-orm": "^1.0.53",
|
|
13
|
+
"w-syslog": "^1.0.25",
|
|
14
|
+
"w-ui-loginout": "^1.0.24",
|
|
15
|
+
"wsemi": "^1.8.92"
|
|
17
16
|
},
|
|
18
17
|
"devDependencies": {
|
|
19
18
|
"@vue/cli-plugin-babel": "^5.0.8",
|
|
@@ -28,18 +27,15 @@
|
|
|
28
27
|
"eslint-plugin-vue": "^9.10.0",
|
|
29
28
|
"vue": "^2.7.16",
|
|
30
29
|
"vuex": "^3.6.2",
|
|
31
|
-
"w-orm-lmdb": "^1.0.
|
|
32
|
-
"w-package-tools": "^1.1.
|
|
30
|
+
"w-orm-lmdb": "^1.0.30",
|
|
31
|
+
"w-package-tools": "^1.1.16",
|
|
33
32
|
"w-package-tools-e2e": "^1.0.0",
|
|
34
|
-
"w-screenctl": "^1.0.
|
|
33
|
+
"w-screenctl": "^1.0.18"
|
|
35
34
|
},
|
|
36
35
|
"scripts": {
|
|
37
36
|
"serve": "vue-cli-service serve",
|
|
38
37
|
"build": "vue-cli-service build",
|
|
39
|
-
"test
|
|
40
|
-
"test:apiunit": "node server/staLogs/test_staEvent.mjs && mocha test/unit-*.test.mjs test/api-*.test.mjs --timeout 300000",
|
|
41
|
-
"test:e2e": "node test/run-e2e-isolated.mjs",
|
|
42
|
-
"test": "npm run test:apiunit && npm run test:e2e",
|
|
38
|
+
"test": "mocha --no-parallel \"test/*.test.mjs\" --timeout 180000",
|
|
43
39
|
"lint": "vue-cli-service lint",
|
|
44
40
|
"deploy": "gh-pages -d docs"
|
|
45
41
|
},
|
package/server/WWebApi.mjs
CHANGED
|
@@ -6,8 +6,9 @@ import map from 'lodash-es/map.js'
|
|
|
6
6
|
import keys from 'lodash-es/keys.js'
|
|
7
7
|
import j2o from 'wsemi/src/j2o.mjs'
|
|
8
8
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
9
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
9
10
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
10
|
-
import
|
|
11
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
11
12
|
import ispint from 'wsemi/src/ispint.mjs'
|
|
12
13
|
import isErr from 'wsemi/src/isErr.mjs'
|
|
13
14
|
import isfun from 'wsemi/src/isfun.mjs'
|
|
@@ -56,6 +57,7 @@ import ds from '../src/schema/index.mjs'
|
|
|
56
57
|
* @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串(srLog 寫入與統計掃描共用),預設'./logs'
|
|
57
58
|
* @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
|
|
58
59
|
* @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
|
|
60
|
+
* @param {Function} [opt.isAllowTarget=null] 輸入測試分頁proxy之目標位址檢核函數,函數會傳入目標網址字串(首跳與轉址後每一跳皆會呼叫),回傳false即拒絕該請求(回err-key 'errReqTargetNotAllowed'),預設null(不限制)
|
|
59
61
|
* @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
|
|
60
62
|
* @example
|
|
61
63
|
*
|
|
@@ -234,6 +236,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
234
236
|
let showLanguage = get(opt, 'showLanguage', 'y')
|
|
235
237
|
let language = get(opt, 'language', 'eng')
|
|
236
238
|
let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
|
|
239
|
+
let isAllowTarget = get(opt, 'isAllowTarget', null) //測試分頁 proxy 之目標檢核(含轉址每一跳),交 procProxy;改造前 procProxy 雖支援此 hook 但建構子未接線
|
|
237
240
|
|
|
238
241
|
|
|
239
242
|
//kpLangExt
|
|
@@ -266,7 +269,30 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
266
269
|
let srLog = srLogInit(opt)
|
|
267
270
|
|
|
268
271
|
//maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
|
|
269
|
-
|
|
272
|
+
//陣列亦須逐元素遮罩:Hapi 對重複之 query 參數(?token=a&token=b)給陣列,若只認字串則原樣印出完整憑證
|
|
273
|
+
//(複審實測 `req.query { token: [ 'ARRAYSECRET', 'x' ] }` 明文落 stdout);此處收斂後 maskQuery 與 token 兩行同時生效。
|
|
274
|
+
let maskTok = (t) => {
|
|
275
|
+
if (isarr(t)) {
|
|
276
|
+
return t.map(maskTok)
|
|
277
|
+
}
|
|
278
|
+
return isestr(t) ? (strleft(t, 4) + '***') : t
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
//maskQuery:印 req.query 整包時,把其中之憑證欄位遮罩後才輸出。
|
|
282
|
+
//why:query 內含 token,直接 console.log(query) 會讓完整憑證落入 stdout,等同繞過 maskTok(原本三處
|
|
283
|
+
//「先印整包 query、下一行才印遮罩後 token」即為此情形)。回傳淺拷貝,不動原物件。
|
|
284
|
+
let maskQuery = (q) => {
|
|
285
|
+
if (!isobj(q)) {
|
|
286
|
+
return q
|
|
287
|
+
}
|
|
288
|
+
let r = { ...q }
|
|
289
|
+
for (let k of Object.keys(r)) {
|
|
290
|
+
if (k.toLowerCase() === 'token') {
|
|
291
|
+
r[k] = maskTok(r[k])
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return r
|
|
295
|
+
}
|
|
270
296
|
|
|
271
297
|
//kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
|
|
272
298
|
//原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
|
|
@@ -285,9 +311,24 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
285
311
|
}
|
|
286
312
|
}
|
|
287
313
|
|
|
314
|
+
//HTTP route 邊界統一處理(與 kpFunExt 之 wrapErrLog 同構):core 內任一 reject 之 err-key 皆於此記錄後原樣再拋。
|
|
315
|
+
//why:route 之錯誤路徑(token 無效、keyTable 無效、user 無效、payload 無效)原僅 console.log,不入 srLog,
|
|
316
|
+
//而統計頁與稽核讀的正是 srLog,導致認證失敗永不可見。逐點加 log 會使同一規則散落多處,故收斂於邊界。
|
|
317
|
+
let wrapRouteErrLog = (event, fn) => {
|
|
318
|
+
return async (...args) => {
|
|
319
|
+
try {
|
|
320
|
+
return await fn(...args)
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
srLog.error({ event, err })
|
|
324
|
+
throw err
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
288
329
|
|
|
289
330
|
//procProxy
|
|
290
|
-
let { proxyRequest } = procProxy({ srLog })
|
|
331
|
+
let { proxyRequest } = procProxy({ srLog, isAllowTarget })
|
|
291
332
|
|
|
292
333
|
|
|
293
334
|
//procApis
|
|
@@ -414,7 +455,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
414
455
|
|
|
415
456
|
//check
|
|
416
457
|
if (!iseobj(user)) {
|
|
417
|
-
console.log(`token`, token)
|
|
458
|
+
console.log(`token`, maskTok(token))
|
|
418
459
|
console.log(`can not find the user from token`)
|
|
419
460
|
return Promise.reject('errUserNotFound')
|
|
420
461
|
}
|
|
@@ -475,8 +516,9 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
475
516
|
//rows
|
|
476
517
|
let rows = get(payload, 'rows', [])
|
|
477
518
|
|
|
478
|
-
//check
|
|
479
|
-
|
|
519
|
+
//check(陣列層:非空陣列即可;逐列檢核統一於 syncAndReplaceTabs,使 [null] 與 [null,null] 皆回同一 err-key errRowInvalid,
|
|
520
|
+
//不因 wsemi isearr「僅長度 1 檢查元素」而分岔)
|
|
521
|
+
if (!isarr(rows) || rows.length === 0) {
|
|
480
522
|
console.log('payload', payload)
|
|
481
523
|
console.log(`invalid rows in payload`)
|
|
482
524
|
return Promise.reject('errRowsInvalid')
|
|
@@ -507,6 +549,12 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
507
549
|
// }
|
|
508
550
|
|
|
509
551
|
|
|
552
|
+
//各資料表同步時之必填欄位(與前端編輯表單同步檢測對稱:apis 之 name/url 必填)
|
|
553
|
+
let kpRequiredFields = {
|
|
554
|
+
apis: ['name', 'url'],
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
|
|
510
558
|
//syncAndReplaceTabs
|
|
511
559
|
let syncAndReplaceTabs = async (userId, inp, keyTable) => {
|
|
512
560
|
|
|
@@ -526,13 +574,30 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
526
574
|
let rows = get(inp, 'rows', [])
|
|
527
575
|
// console.log('rows', rows)
|
|
528
576
|
|
|
529
|
-
//check
|
|
530
|
-
if (!
|
|
577
|
+
//check(陣列層:須為非空陣列;逐列檢核見下)
|
|
578
|
+
if (!isarr(rows) || rows.length === 0) {
|
|
531
579
|
console.log('inp', inp)
|
|
532
580
|
console.log('keyTable', keyTable)
|
|
533
581
|
return Promise.reject('errRowsInvalid')
|
|
534
582
|
}
|
|
535
583
|
|
|
584
|
+
//逐列檢核:任一列無效即整批拒絕(回 err-key errRowInvalid),且一律在 delAll 之前,既有群組資料不動;
|
|
585
|
+
//列索引與欄位名入 srLog 供整合端查因(回呼叫端只給 key,同其他錯誤契約)。
|
|
586
|
+
//why:改造前只有陣列層 isearr(wsemi:僅長度 1 時檢查元素),[null,null]/[1,2] 可通過,下方 v.group= 即 TypeError,
|
|
587
|
+
//原始例外訊息經 pm2resolve 外洩給呼叫端;缺必填欄位之物件列則經 ltdtmapping 補空值後以空名稱進樹。
|
|
588
|
+
let rejectRow = (index, field) => {
|
|
589
|
+
srLog.error({ event: 'syncAndReplaceTabs', err: 'errRowInvalid', keyTable, group, index, field })
|
|
590
|
+
console.log(`[API]syncAndReplaceTabs/check rows[${index}]: invalid row (field: ${field})`)
|
|
591
|
+
return Promise.reject('errRowInvalid')
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
//逐列檢核 ①:每列須為物件(非物件列連 group 都無法寫入)
|
|
595
|
+
for (let k = 0; k < rows.length; k++) {
|
|
596
|
+
if (!iseobj(rows[k])) {
|
|
597
|
+
return rejectRow(k, '(row)')
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
536
601
|
//save from
|
|
537
602
|
rows = map(rows, (v, k) => {
|
|
538
603
|
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
@@ -559,11 +624,20 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
559
624
|
})
|
|
560
625
|
}
|
|
561
626
|
|
|
627
|
+
//逐列檢核 ②(b64 還原後):必填欄位須為非空字串(funNew/ltdtmapping 只補預設值、不驗證)
|
|
628
|
+
let ksRequired = get(kpRequiredFields, keyTable, [])
|
|
629
|
+
for (let k = 0; k < rows.length; k++) {
|
|
630
|
+
let field = ksRequired.find((f) => !isestr(rows[k][f]))
|
|
631
|
+
if (isestr(field)) {
|
|
632
|
+
return rejectRow(k, field)
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
562
636
|
//ltdtmapping
|
|
563
637
|
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
564
638
|
// console.log('ltdtmapping rows', rows)
|
|
565
639
|
|
|
566
|
-
//delAll group
|
|
640
|
+
//delAll group(逐列檢核皆已通過,此後才動既有資料;delAll→insert 非原子屬已知限制 ADR-016)
|
|
567
641
|
await woItems[keyTable].delAll({ group })
|
|
568
642
|
|
|
569
643
|
//insert
|
|
@@ -626,7 +700,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
626
700
|
|
|
627
701
|
//check
|
|
628
702
|
if (!isestr(token)) {
|
|
629
|
-
console.log('req.query', get(req, 'query'))
|
|
703
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
630
704
|
console.log('token', maskTok(token))
|
|
631
705
|
console.log('[API]getUserByToken/check token: invalid token')
|
|
632
706
|
return Promise.reject('errTokenNoPermission')
|
|
@@ -647,7 +721,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
647
721
|
}
|
|
648
722
|
|
|
649
723
|
//pm2resolve core
|
|
650
|
-
let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
|
|
724
|
+
let r = await pm2resolve(wrapRouteErrLog('api/getUserByToken', core))() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
|
|
651
725
|
if (isErr(r.msg)) {
|
|
652
726
|
r.msg = r.msg.message
|
|
653
727
|
}
|
|
@@ -767,7 +841,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
767
841
|
|
|
768
842
|
//check
|
|
769
843
|
if (!isestr(token)) {
|
|
770
|
-
console.log('req.query', get(req, 'query'))
|
|
844
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
771
845
|
console.log('token', maskTok(token))
|
|
772
846
|
console.log('[API]syncAndReplaceTabs/check token: invalid token')
|
|
773
847
|
return Promise.reject('errTokenNoPermission')
|
|
@@ -778,7 +852,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
778
852
|
|
|
779
853
|
//check
|
|
780
854
|
if (!isestr(keyTable)) {
|
|
781
|
-
console.log('req.query', get(req, 'query'))
|
|
855
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
782
856
|
console.log('keyTable', keyTable)
|
|
783
857
|
console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
|
|
784
858
|
return Promise.reject('errKeyTableInvalid')
|
|
@@ -823,7 +897,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
823
897
|
}
|
|
824
898
|
|
|
825
899
|
//pm2resolve core
|
|
826
|
-
let r = await pm2resolve(core)()
|
|
900
|
+
let r = await pm2resolve(wrapRouteErrLog('api/syncAndReplaceTabs', core))()
|
|
827
901
|
if (isErr(r.msg)) {
|
|
828
902
|
r.msg = r.msg.message
|
|
829
903
|
}
|
|
@@ -863,6 +937,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
863
937
|
console.log('apiType', apiType)
|
|
864
938
|
console.log('authorization', maskTok(authorization))
|
|
865
939
|
console.log('invalid token')
|
|
940
|
+
srLog.error({ event: 'verifyConn', err: 'errTokenNoPermission', apiType }) //認證失敗須入 log 供稽核/統計(CLAUDE.md「log 記錄要點」)
|
|
866
941
|
return false
|
|
867
942
|
}
|
|
868
943
|
|
|
@@ -870,9 +945,17 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
870
945
|
//套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
|
|
871
946
|
//或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
|
|
872
947
|
//保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
|
|
873
|
-
|
|
948
|
+
//checkToken 失敗會 throw(err-key),原本直接向上拋致認證失敗不入 log;此處攔截記錄後原樣再拋,行為不變。
|
|
949
|
+
let b
|
|
950
|
+
try {
|
|
951
|
+
b = await p.checkToken(token)
|
|
952
|
+
}
|
|
953
|
+
catch (err) {
|
|
954
|
+
srLog.error({ event: 'verifyConn', err, apiType })
|
|
955
|
+
throw err
|
|
956
|
+
}
|
|
874
957
|
|
|
875
|
-
srLog.info({ event: 'verifyConn', apiType }) //連線之 token
|
|
958
|
+
srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過
|
|
876
959
|
return b
|
|
877
960
|
},
|
|
878
961
|
getUserIdByToken: async (token) => { //可使用async或sync函數
|
|
@@ -922,7 +1005,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
|
|
|
922
1005
|
// },
|
|
923
1006
|
|
|
924
1007
|
},
|
|
925
|
-
|
|
1008
|
+
fpTableTags: 'tableTags-web-api.json', //w-serv-hapi 1.0.87 起正名 fpTableTags(同 sso / perm); 舊名 fnTableTags 於 1.0.86 傳到 w-serv-webdata 時不被讀取, 實際一直寫預設 tableTags.json
|
|
926
1009
|
})
|
|
927
1010
|
|
|
928
1011
|
|