w-web-perm 1.1.4 → 1.1.6
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/README.md +1 -1
- package/dist/css/app.6918ec45.css +1 -0
- package/dist/css/{chunk-vendors.2bcfce08.css → chunk-vendors.3c580e72.css} +1 -1
- package/dist/index.tmp +2 -2
- package/dist/js/app-legacy.5f15a58f.js +1 -0
- package/dist/js/app.5f15a58f.js +1 -0
- package/dist/js/{chunk-vendors-legacy.fc37f076.js → chunk-vendors-legacy.38f515f9.js} +16 -16
- package/dist/js/{chunk-vendors.fc37f076.js → chunk-vendors.38f515f9.js} +16 -16
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +7 -7
- package/docs/global.html +2068 -111
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +63 -31
- package/docs/server_lockSave.mjs.html +115 -0
- package/docs/server_maskLog.mjs.html +176 -0
- package/docs/server_procCore.mjs.html +31 -5
- package/docs/server_wrapInjected.mjs.html +163 -0
- package/docs/src_fetchJson.mjs.html +31 -5
- package/docs/src_js_legendFit.mjs.html +225 -0
- package/docs/src_plugins_reuseDeletedIds.mjs.html +136 -0
- package/docs/src_plugins_submitGuard.mjs.html +163 -0
- package/package.json +13 -13
- package/public/index.html +2 -0
- package/server/WWebPerm.mjs +61 -29
- package/server/lockSave.mjs +43 -0
- package/server/maskLog.mjs +104 -0
- package/server/procCore.mjs +29 -3
- package/server/procLang.mjs +8 -0
- package/server/procStaInfor.mjs +32 -14
- package/server/wrapInjected.mjs +91 -0
- package/src/components/Common/CheckYes.vue +6 -0
- package/src/components/Common/CheckYesNo.vue +6 -0
- package/src/components/LayoutContentGrups.vue +30 -21
- package/src/components/LayoutContentPemis.vue +30 -21
- package/src/components/LayoutContentStaInfor.vue +74 -5
- package/src/components/LayoutContentTargets.vue +36 -22
- package/src/components/LayoutContentUsers.vue +31 -21
- package/src/components/RelationChips.vue +11 -31
- package/src/components/VeCgrups.vue +8 -1
- package/src/components/VeCpemis.vue +8 -1
- package/src/components/VeCrules.vue +8 -1
- package/src/components/VeGrupBlngUsers.vue +37 -31
- package/src/components/VePemiBlngGrups.vue +37 -31
- package/src/fetchJson.mjs +28 -2
- package/src/getPerm.mjs +9 -6
- package/src/getPermUserInfor.mjs +12 -9
- package/src/js/legendFit.mjs +153 -0
- package/src/plugins/mUI.mjs +20 -0
- package/src/plugins/reuseDeletedIds.mjs +64 -0
- package/src/plugins/submitGuard.mjs +91 -0
- package/srv.mjs +20 -1
- package/dist/css/app.506ce4d8.css +0 -1
- package/dist/js/app-legacy.8e615442.js +0 -1
- package/dist/js/app.8e615442.js +0 -1
- package/tableTags-web-perm.json +0 -1
package/docs/index.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -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 Fri Oct 02 2026 20:53:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -70,14 +70,18 @@ import replace from 'wsemi/src/replace.mjs'
|
|
|
70
70
|
import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
71
71
|
import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
|
|
72
72
|
import pmKeyMutex from 'wsemi/src/pmKeyMutex.mjs'
|
|
73
|
+
import cacheSt from 'wsemi/src/cacheSt.mjs'
|
|
73
74
|
import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
74
75
|
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
75
76
|
import ds from '../src/schema/index.mjs'
|
|
76
77
|
import { getUserRules } from '../src/plugins/mShare.mjs'
|
|
77
78
|
import procCore from './procCore.mjs'
|
|
79
|
+
import createLockSave from './lockSave.mjs'
|
|
78
80
|
import procLang from './procLang.mjs'
|
|
79
81
|
import srLogInit from './srLog.mjs'
|
|
80
82
|
import procStaInfor from './procStaInfor.mjs'
|
|
83
|
+
import { maskTok, maskQuery, maskUrl } from './maskLog.mjs'
|
|
84
|
+
import wrapInjected from './wrapInjected.mjs'
|
|
81
85
|
|
|
82
86
|
|
|
83
87
|
/**
|
|
@@ -87,9 +91,9 @@ import procStaInfor from './procStaInfor.mjs'
|
|
|
87
91
|
* @param {Function} WOrm 輸入資料庫ORM函數
|
|
88
92
|
* @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
|
|
89
93
|
* @param {String} db 輸入資料庫名稱字串
|
|
90
|
-
* @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token
|
|
91
|
-
* @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件(perm users 表之列)與來源字串from,通過此函數識別後回傳布林值(可為Promise),允許使用者回傳true,反之回傳false。from 值域: 'getUserByToken'(登入)、'getPerm'(api/getPerm)、以及資料通道之 apiType 'main'(RPC 與 ORM 執行)、'upload-controller'、'upload-slice'、'download-get-filename'、'download-get-file'、'download';資料通道與登入採同一授權強度(反查 perm users 且 isActive='y' 後再經本函數)
|
|
92
|
-
* @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
|
|
94
|
+
* @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token 宣稱之值複寫。函數拋錯或reject時視同查無使用者(回自家錯誤key,錯誤原文不回前端、不入log,只於srLog記warn)
|
|
95
|
+
* @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件(perm users 表之列)與來源字串from,通過此函數識別後回傳布林值(可為Promise),允許使用者回傳true,反之回傳false。from 值域: 'getUserByToken'(登入)、'getPerm'(api/getPerm)、以及資料通道之 apiType 'main'(RPC 與 ORM 執行)、'upload-controller'、'upload-slice'、'download-get-filename'、'download-get-file'、'download';資料通道與登入採同一授權強度(反查 perm users 且 isActive='y' 後再經本函數);函數拋錯或reject時視同回傳false
|
|
96
|
+
* @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false;函數拋錯或reject時視同回傳false
|
|
93
97
|
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
94
98
|
* @param {Integer} [opt.serverPort=11006] 輸入伺服器通訊port,預設11006
|
|
95
99
|
* @param {Boolean} [opt.useCheckUser=false] 輸入是否檢查使用者資訊布林值,預設false
|
|
@@ -157,7 +161,7 @@ import procStaInfor from './procStaInfor.mjs'
|
|
|
157
161
|
* isAdmin: 'y',
|
|
158
162
|
* }
|
|
159
163
|
* }
|
|
160
|
-
* console.log('invalid token'
|
|
164
|
+
* console.log('invalid token') //勿印權杖值
|
|
161
165
|
* console.log('於生產環境時得加入SSO等驗證token機制')
|
|
162
166
|
* return {}
|
|
163
167
|
* }
|
|
@@ -321,6 +325,27 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
321
325
|
let srLog = srLogInit(opt)
|
|
322
326
|
|
|
323
327
|
|
|
328
|
+
//注入函數一律經包裝(W 契約, spec/設計要點與取捨.md ADR-023), 以包裝版取代三個參數
|
|
329
|
+
//why: 注入函數拋錯或 reject 時原會被 await 原樣上拋, 其原文經 pm2resolve 回前端、經 verifyConn 之 msg 入 srLog、經 error 事件印出;
|
|
330
|
+
//部署方常以 w-web-sso 對外 helper(≤1.1.4)包裝 getUserByToken, 其 reject 字串含已代入系統介接權杖與使用者權杖之完整網址.
|
|
331
|
+
//包裝後失敗視同否定結果(getUserByToken → null 等同查無, verify* → false 等同無權限), 走各呼叫點既有分支回自家 key;
|
|
332
|
+
//上游原文不回前端、不入 log, 只於此記 warn(白名單 key, 或型別與 Error.name). 同一次失敗 log 2~3 筆: 此處 warn(因) + 呼叫點 error(果).
|
|
333
|
+
//以下取代後, 本函數內除此三行外不得再直接使用原始參數(盤點見 CLAUDE_rulebook.md「權杖不外洩」, 測試 unit-wrapInjected WRAP-SITE-*)
|
|
334
|
+
let logInjectFail = (name) => {
|
|
335
|
+
return (d) => {
|
|
336
|
+
srLog.warn({ event: `inject-${name}-fail`, ...d })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
getUserByToken = wrapInjected(getUserByToken, { failValue: null, onFail: logInjectFail('getUserByToken') })
|
|
340
|
+
verifyClientUser = wrapInjected(verifyClientUser, { failValue: false, onFail: logInjectFail('verifyClientUser') })
|
|
341
|
+
verifyAppUser = wrapInjected(verifyAppUser, { failValue: false, onFail: logInjectFail('verifyAppUser') })
|
|
342
|
+
//getUserById: 第四個注入函數(選填; useCheckUser=true 時由 WServOrm 於 execute 內呼叫, 原 reject 原文經 pm2resolve 回前端), 有給才包裝;
|
|
343
|
+
//失敗 → null 等同查無, 由 WServOrm 既有之查無分支拒絕(fail-closed). 須於 WServOrm 建構前取代(建構時即取走此函數)
|
|
344
|
+
if (isfun(getUserById)) {
|
|
345
|
+
getUserById = wrapInjected(getUserById, { failValue: null, onFail: logInjectFail('getUserById') })
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
324
349
|
//WServOrm
|
|
325
350
|
let optWServOrm = {
|
|
326
351
|
useCheckUser,
|
|
@@ -344,8 +369,14 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
344
369
|
let kmx = pmKeyMutex()
|
|
345
370
|
|
|
346
371
|
|
|
372
|
+
//lockSave: 後台整表寫入(updateTabItems)之雙擊防護(後端, 見 lockSave.mjs, ADR-025): 以「updateTabItems:{表名}:{操作者 id}」於 cacheSt 原子占位,
|
|
373
|
+
//同一操作者之同表寫入處理中再送出即 reject 'saveInProgress'(不排隊); 包在 kmx 之外層, 不同操作者之並行仍由 kmx 序列化(ADR-012)
|
|
374
|
+
let cst = cacheSt()
|
|
375
|
+
let lockSave = createLockSave(cst)
|
|
376
|
+
|
|
377
|
+
|
|
347
378
|
//procCore: 整表批次 diff 寫入邏輯 (updateTabItems + updateTargets/Pemis/Grups/Users)
|
|
348
|
-
let pc = procCore(woItems, procOrm, { srLog, kmx })
|
|
379
|
+
let pc = procCore(woItems, procOrm, { srLog, kmx, lockSave })
|
|
349
380
|
|
|
350
381
|
|
|
351
382
|
//procStaInfor: 統計資訊, fdLog 對齊 srLog 使用同一目錄(讀 settings.logFd, fallback 邏輯同 srLog.mjs); mock 由 settings.staEventMock 注入(e2e 統計圖穩定用)
|
|
@@ -443,7 +474,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
443
474
|
|
|
444
475
|
//check
|
|
445
476
|
if (!iseobj(userSelf)) {
|
|
446
|
-
console.log(`token`, token)
|
|
477
|
+
console.log(`token`, maskTok(token))
|
|
447
478
|
srLog.error({ event: 'getTokenUser-error', msg: 'cannotFindUserFromToken' })
|
|
448
479
|
return Promise.reject(`cannotFindUserFromToken`)
|
|
449
480
|
}
|
|
@@ -525,7 +556,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
525
556
|
|
|
526
557
|
//check
|
|
527
558
|
if (!iseobj(user)) {
|
|
528
|
-
console.log(`token`, token)
|
|
559
|
+
console.log(`token`, maskTok(token))
|
|
529
560
|
srLog.error({ event: 'getAndVerifyAppUser-error', msg: 'cannotFindUserFromToken' })
|
|
530
561
|
return Promise.reject(`cannotFindUserFromToken`)
|
|
531
562
|
}
|
|
@@ -797,8 +828,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
797
828
|
|
|
798
829
|
//check
|
|
799
830
|
if (!isestr(token)) {
|
|
800
|
-
console.log('req.query', get(req, 'query'))
|
|
801
|
-
console.log('token', token)
|
|
831
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
832
|
+
console.log('token', maskTok(token))
|
|
802
833
|
console.log('[API]getUserByToken/check token: invalid token')
|
|
803
834
|
srLog.error({ event: 'api/getUserByToken-error', msg: 'tokenNoPermission' })
|
|
804
835
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -810,7 +841,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
810
841
|
|
|
811
842
|
//check
|
|
812
843
|
if (!iseobj(user)) {
|
|
813
|
-
console.log('token', token)
|
|
844
|
+
console.log('token', maskTok(token))
|
|
814
845
|
console.log('[API]getUserByToken/check user: invalid user')
|
|
815
846
|
srLog.error({ event: 'api/getUserByToken-error', msg: 'tokenNoPermission' })
|
|
816
847
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -847,8 +878,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
847
878
|
|
|
848
879
|
//check
|
|
849
880
|
if (!isestr(token)) {
|
|
850
|
-
console.log('req.query', get(req, 'query'))
|
|
851
|
-
console.log('token', token)
|
|
881
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
882
|
+
console.log('token', maskTok(token))
|
|
852
883
|
console.log('[API]getPerm/check token: invalid token')
|
|
853
884
|
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
854
885
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -860,7 +891,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
860
891
|
|
|
861
892
|
//check
|
|
862
893
|
if (!iseobj(userSelf)) {
|
|
863
|
-
console.log('token', token)
|
|
894
|
+
console.log('token', maskTok(token))
|
|
864
895
|
console.log('[API]getPerm/check userSelf: invalid userSelf')
|
|
865
896
|
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
866
897
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -872,7 +903,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
872
903
|
|
|
873
904
|
//check
|
|
874
905
|
if (!isestr(userIdSelf)) {
|
|
875
|
-
console.log('token', token)
|
|
906
|
+
console.log('token', maskTok(token))
|
|
876
907
|
console.log('userSelf', userSelf)
|
|
877
908
|
console.log('[API]getPerm/check userIdSelf: invalid userIdSelf')
|
|
878
909
|
srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
|
|
@@ -885,7 +916,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
885
916
|
|
|
886
917
|
//check
|
|
887
918
|
if (!iseobj(userWithRulesSelf)) {
|
|
888
|
-
console.log('token', token)
|
|
919
|
+
console.log('token', maskTok(token))
|
|
889
920
|
console.log('userSelf', userSelf)
|
|
890
921
|
console.log('userIdSelf', userIdSelf)
|
|
891
922
|
console.log('[API]getPerm/check userWithRulesSelf: invalid userWithRulesSelf')
|
|
@@ -924,7 +955,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
924
955
|
|
|
925
956
|
//check
|
|
926
957
|
if (!isestr(tokenSelf)) {
|
|
927
|
-
console.log('req.query', get(req, 'query'))
|
|
958
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
928
959
|
console.log('[API]getPermUserInfor/check tokenSelf: invalid tokenSelf')
|
|
929
960
|
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
|
|
930
961
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -936,8 +967,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
936
967
|
|
|
937
968
|
//check
|
|
938
969
|
if (!iseobj(userSelf)) {
|
|
939
|
-
console.log('req.query', get(req, 'query'))
|
|
940
|
-
console.log('tokenSelf', tokenSelf)
|
|
970
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
971
|
+
console.log('tokenSelf', maskTok(tokenSelf))
|
|
941
972
|
console.log('[API]getPermUserInfor/check userSelf: invalid userSelf')
|
|
942
973
|
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
|
|
943
974
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -949,8 +980,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
949
980
|
|
|
950
981
|
//check
|
|
951
982
|
if (!isestr(userIdFind)) {
|
|
952
|
-
console.log('req.query', get(req, 'query'))
|
|
953
|
-
console.log('tokenSelf', tokenSelf)
|
|
983
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
984
|
+
console.log('tokenSelf', maskTok(tokenSelf))
|
|
954
985
|
console.log('[API]getPermUserInfor/check userIdFind: invalid userIdFind')
|
|
955
986
|
srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
|
|
956
987
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -999,8 +1030,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
999
1030
|
|
|
1000
1031
|
//check
|
|
1001
1032
|
if (!isestr(token)) {
|
|
1002
|
-
console.log('req.query', get(req, 'query'))
|
|
1003
|
-
console.log('token', token)
|
|
1033
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
1034
|
+
console.log('token', maskTok(token))
|
|
1004
1035
|
console.log('[API]syncAndReplaceTabs/check token: invalid token')
|
|
1005
1036
|
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
1006
1037
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -1011,7 +1042,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1011
1042
|
|
|
1012
1043
|
//check
|
|
1013
1044
|
if (!isestr(keyTable)) {
|
|
1014
|
-
console.log('req.query', get(req, 'query'))
|
|
1045
|
+
console.log('req.query', maskQuery(get(req, 'query')))
|
|
1015
1046
|
console.log('keyTable', keyTable)
|
|
1016
1047
|
console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
|
|
1017
1048
|
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
@@ -1031,7 +1062,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1031
1062
|
|
|
1032
1063
|
//check user
|
|
1033
1064
|
if (!iseobj(user)) {
|
|
1034
|
-
console.log('token', token)
|
|
1065
|
+
console.log('token', maskTok(token))
|
|
1035
1066
|
console.log('[API]syncAndReplaceTabs/check user: invalid user')
|
|
1036
1067
|
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
1037
1068
|
return Promise.reject(`tokenNoPermission`)
|
|
@@ -1042,7 +1073,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1042
1073
|
|
|
1043
1074
|
//check userId
|
|
1044
1075
|
if (!isestr(userId)) {
|
|
1045
|
-
console.log('token', token)
|
|
1076
|
+
console.log('token', maskTok(token))
|
|
1046
1077
|
console.log('user', user)
|
|
1047
1078
|
console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
|
|
1048
1079
|
srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
|
|
@@ -1099,8 +1130,9 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1099
1130
|
//check
|
|
1100
1131
|
if (!isestr(token)) {
|
|
1101
1132
|
console.log('apiType', apiType)
|
|
1102
|
-
console.log('authorization', authorization)
|
|
1133
|
+
console.log('authorization', maskTok(authorization))
|
|
1103
1134
|
console.log('invalid token')
|
|
1135
|
+
srLog.error({ event: 'verifyConn-error', ok: false, apiType, msg: 'tokenNoPermission' }) //認證失敗須入 log 供稽核/統計(CLAUDE.md「log 記錄要點」; 同 w-web-api、w-web-task); 只記 key, 不記 authorization 原值
|
|
1104
1136
|
return false
|
|
1105
1137
|
}
|
|
1106
1138
|
|
|
@@ -1125,8 +1157,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1125
1157
|
//origin
|
|
1126
1158
|
let origin = get(headers, 'origin', '')
|
|
1127
1159
|
|
|
1128
|
-
//referer
|
|
1129
|
-
let referer = get(headers, 'referer', '')
|
|
1160
|
+
//referer, 只記 origin + pathname(ADR-023): 前端以 ?token= 開站(w-ui-loginout 讀完不清網址), 同源請求之 Referer 預設帶完整網址, 原樣記錄即把使用者權杖寫入 srLog
|
|
1161
|
+
let referer = maskUrl(get(headers, 'referer', ''))
|
|
1130
1162
|
|
|
1131
1163
|
//info, 失敗時 msg 為 err key(cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等), 供資安稽核與統計
|
|
1132
1164
|
if (b) {
|
|
@@ -1143,7 +1175,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1143
1175
|
let user = await getUserByToken(token)
|
|
1144
1176
|
let userId = get(user, 'id', '')
|
|
1145
1177
|
if (!isestr(userId)) {
|
|
1146
|
-
console.log('token', token)
|
|
1178
|
+
console.log('token', maskTok(token))
|
|
1147
1179
|
console.log('userId', userId)
|
|
1148
1180
|
srLog.error({ event: 'getUserIdByToken-error', msg: 'cannotFindUserId' })
|
|
1149
1181
|
return Promise.reject(`cannotFindUserId`)
|
|
@@ -1221,7 +1253,7 @@ export default WWebPerm
|
|
|
1221
1253
|
<br class="clear">
|
|
1222
1254
|
|
|
1223
1255
|
<footer>
|
|
1224
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
1256
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 02 2026 20:53:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
1225
1257
|
</footer>
|
|
1226
1258
|
|
|
1227
1259
|
<script>prettyPrint();</script>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>server/lockSave.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">server/lockSave.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
|
|
49
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 有副作用之寫入路徑(如清單儲存 updateUsersList / updateTokensList / updateIpsList)之雙擊防護(後端, ADR-025)
|
|
54
|
+
*
|
|
55
|
+
* 以「操作:操作者 id」於 wsemi cacheSt 原子占位執行 fn, 同一操作者之同一操作處理中再送出即 reject 錯誤 key
|
|
56
|
+
* (預設 'saveInProgress', 可以 opt.errKey 指定; 皆為 i18n key, 見 procLang); 不同操作者、不同操作互不影響;
|
|
57
|
+
* fn 之 reject 原樣 bubble; fn 結束(含失敗)即釋放.
|
|
58
|
+
* 第 2 次不排隊執行: 其內容與第 1 次相同, 排隊後再寫一次會把第 1 次剛建立之列當修改覆寫
|
|
59
|
+
* (依序重送另由各專案之資料層檢查擋, 如 SSO procCore.updateTabItems 之新增列檢查回 'saveNewRowExists').
|
|
60
|
+
* operatorId 可為操作者 id 或「操作者 id + 列識別」(只擋同一列之連發, 同批其他列不受影響).
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} cst 輸入 wsemi cacheSt 實例
|
|
63
|
+
* @returns {Function} 回傳 async (op, operatorId, fn, opt = {}) => fn 之結果, opt.errKey 為占位衝突時 reject 之 key
|
|
64
|
+
*/
|
|
65
|
+
function createLockSave(cst) {
|
|
66
|
+
|
|
67
|
+
let lockSave = async (op, operatorId, fn, opt = {}) => {
|
|
68
|
+
|
|
69
|
+
//errKey
|
|
70
|
+
let errKey = get(opt, 'errKey', '')
|
|
71
|
+
if (!isestr(errKey)) {
|
|
72
|
+
errKey = 'saveInProgress'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let key = `${op}:${operatorId}`
|
|
76
|
+
return await cst.setWithFree([key], fn)
|
|
77
|
+
.catch((err) => {
|
|
78
|
+
//remap setWithFree 之占位衝突 → i18n key; fn 內之 reject 原樣 bubble
|
|
79
|
+
if (err === `setWithFree: key in use: ${key}`) {
|
|
80
|
+
return Promise.reject(errKey)
|
|
81
|
+
}
|
|
82
|
+
return Promise.reject(err)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return lockSave
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
export default createLockSave
|
|
91
|
+
</code></pre>
|
|
92
|
+
</article>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<br class="clear">
|
|
103
|
+
|
|
104
|
+
<footer>
|
|
105
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 02 2026 20:53:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
106
|
+
</footer>
|
|
107
|
+
|
|
108
|
+
<script>prettyPrint();</script>
|
|
109
|
+
<script src="scripts/polyfill.js"></script>
|
|
110
|
+
<script src="scripts/linenumber.js"></script>
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>server/maskLog.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">server/maskLog.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import isestr from 'wsemi/src/isestr.mjs'
|
|
49
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
//maskLog: 權杖與網址寫入 console / srLog 前之遮罩(M 契約, spec/設計要點與取捨.md ADR-023)
|
|
53
|
+
//四 repo 一致(w-web-sso server/srLog.mjs 之 maskToken 與 src/maskUrl.mjs、w-web-api / w-web-task 之 maskTok), 以同一組測資守護(test/unit-maskLog.test.mjs)
|
|
54
|
+
//why: 原 console 站點原樣印出 token / req.query / authorization(Hapi 對重複之 query 參數給陣列, 型別檢查失敗之分支正是印出處), verifyConn 之 srLog 原樣記 referer
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
//safe: 露出字元之控制字元換成 '?', 避免請求端以 CR/LF 偽造 log 行
|
|
58
|
+
function safe(s) {
|
|
59
|
+
return Array.from(s, (c) => {
|
|
60
|
+
let x = c.charCodeAt(0)
|
|
61
|
+
return (x < 32 || x === 127) ? '?' : c
|
|
62
|
+
}).join('')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 權杖遮罩
|
|
68
|
+
* - 空值(undefined / null / '')回 ''
|
|
69
|
+
* - 陣列逐元素遮罩
|
|
70
|
+
* - 字串(長度 n)露出前後各 k=min(4, floor(n/8)) 字: k=0 只回 '(len=n)', 否則 '前k...後k(len=n)'; 36 字元之 session token 為前 4 後 4(與 w-web-sso 格式相同)
|
|
71
|
+
* - 其他型別只回 '(typeof)', 絕不輸出原值
|
|
72
|
+
*
|
|
73
|
+
* @param {*} token 輸入權杖
|
|
74
|
+
* @returns {String|Array} 回傳遮罩後字串, 輸入陣列時回傳陣列
|
|
75
|
+
*/
|
|
76
|
+
function maskTok(token) {
|
|
77
|
+
if (isarr(token)) {
|
|
78
|
+
return token.map(maskTok)
|
|
79
|
+
}
|
|
80
|
+
if (token === undefined || token === null || token === '') {
|
|
81
|
+
return ''
|
|
82
|
+
}
|
|
83
|
+
if (!isestr(token)) {
|
|
84
|
+
return `(${typeof token})`
|
|
85
|
+
}
|
|
86
|
+
let n = token.length
|
|
87
|
+
let k = Math.min(4, Math.floor(n / 8))
|
|
88
|
+
if (k === 0) {
|
|
89
|
+
return `(len=${n})`
|
|
90
|
+
}
|
|
91
|
+
return `${safe(token.slice(0, k))}...${safe(token.slice(-k))}(len=${n})`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 查詢參數遮罩: 淺拷貝, 鍵名小寫為 token 者以 maskTok 處理, 其餘原樣(供印出 req.query)
|
|
97
|
+
* 非物件輸入依 maskTok 處理
|
|
98
|
+
*
|
|
99
|
+
* @param {*} q 輸入查詢參數物件(Hapi 之 req.query 為 null-prototype 物件)
|
|
100
|
+
* @returns {Object|String|Array} 回傳遮罩後之淺拷貝
|
|
101
|
+
*/
|
|
102
|
+
function maskQuery(q) {
|
|
103
|
+
if (q === null || typeof q !== 'object' || isarr(q)) {
|
|
104
|
+
return maskTok(q)
|
|
105
|
+
}
|
|
106
|
+
let r = {}
|
|
107
|
+
for (let k of Object.keys(q)) {
|
|
108
|
+
r[k] = (k.toLowerCase() === 'token') ? maskTok(q[k]) : q[k]
|
|
109
|
+
}
|
|
110
|
+
return r
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 網址遮罩: http(s) 只留 origin + pathname, 捨棄 query / fragment / userinfo(其內常含權杖, 如 ?token=)
|
|
116
|
+
* - 陣列逐元素處理; 空值回 ''; 其他非字串只回 '(typeof)'
|
|
117
|
+
* - 非 http(s) 之網址(如 data:、javascript:)路徑即全部內容, 只回 protocol(同 w-web-sso src/maskUrl.mjs)
|
|
118
|
+
* - 無法解析回 '(invalid-url)'
|
|
119
|
+
*
|
|
120
|
+
* @param {*} u 輸入網址
|
|
121
|
+
* @returns {String|Array} 回傳可寫入 log 之字串, 輸入陣列時回傳陣列
|
|
122
|
+
*/
|
|
123
|
+
function maskUrl(u) {
|
|
124
|
+
if (isarr(u)) {
|
|
125
|
+
return u.map(maskUrl)
|
|
126
|
+
}
|
|
127
|
+
if (u === undefined || u === null || u === '') {
|
|
128
|
+
return ''
|
|
129
|
+
}
|
|
130
|
+
if (!isestr(u)) {
|
|
131
|
+
return `(${typeof u})`
|
|
132
|
+
}
|
|
133
|
+
let x = null
|
|
134
|
+
try {
|
|
135
|
+
x = new URL(u)
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
return '(invalid-url)'
|
|
139
|
+
}
|
|
140
|
+
if (x.protocol !== 'http:' && x.protocol !== 'https:') {
|
|
141
|
+
return x.protocol
|
|
142
|
+
}
|
|
143
|
+
return `${x.origin}${x.pathname}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
maskTok,
|
|
149
|
+
maskQuery,
|
|
150
|
+
maskUrl
|
|
151
|
+
}
|
|
152
|
+
</code></pre>
|
|
153
|
+
</article>
|
|
154
|
+
</section>
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<br class="clear">
|
|
164
|
+
|
|
165
|
+
<footer>
|
|
166
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 02 2026 20:53:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
167
|
+
</footer>
|
|
168
|
+
|
|
169
|
+
<script>prettyPrint();</script>
|
|
170
|
+
<script src="scripts/polyfill.js"></script>
|
|
171
|
+
<script src="scripts/linenumber.js"></script>
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
</body>
|
|
176
|
+
</html>
|