w-web-perm 1.1.3 → 1.1.5
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.80aa3ae8.css} +1 -1
- package/dist/index.tmp +2 -2
- package/dist/js/app-legacy.833f9566.js +1 -0
- package/dist/js/app.833f9566.js +1 -0
- package/dist/js/{chunk-vendors-legacy.77039410.js → chunk-vendors-legacy.fbf771bd.js} +16 -16
- package/dist/js/{chunk-vendors.77039410.js → chunk-vendors.fbf771bd.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 +1545 -60
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +53 -30
- package/docs/server_maskLog.mjs.html +176 -0
- package/docs/server_procCore.mjs.html +2 -2
- 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/package.json +6 -6
- package/public/index.html +2 -0
- package/server/WWebPerm.mjs +51 -28
- package/server/maskLog.mjs +104 -0
- package/server/procLang.mjs +8 -0
- package/server/procStaInfor.mjs +32 -14
- package/server/wrapInjected.mjs +91 -0
- package/src/components/Layout.vue +566 -181
- package/src/components/LayoutContent.vue +1 -0
- package/src/components/LayoutContentStaInfor.vue +74 -5
- package/src/components/RelationChips.vue +11 -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/srv.mjs +20 -1
- package/tableTags-web-perm.json +1 -1
- package/dist/css/app.506ce4d8.css +0 -1
- package/dist/js/app-legacy.27090303.js +0 -1
- package/dist/js/app.27090303.js +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
2
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//maskLog: 權杖與網址寫入 console / srLog 前之遮罩(M 契約, spec/設計要點與取捨.md ADR-023)
|
|
6
|
+
//四 repo 一致(w-web-sso server/srLog.mjs 之 maskToken 與 src/maskUrl.mjs、w-web-api / w-web-task 之 maskTok), 以同一組測資守護(test/unit-maskLog.test.mjs)
|
|
7
|
+
//why: 原 console 站點原樣印出 token / req.query / authorization(Hapi 對重複之 query 參數給陣列, 型別檢查失敗之分支正是印出處), verifyConn 之 srLog 原樣記 referer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
//safe: 露出字元之控制字元換成 '?', 避免請求端以 CR/LF 偽造 log 行
|
|
11
|
+
function safe(s) {
|
|
12
|
+
return Array.from(s, (c) => {
|
|
13
|
+
let x = c.charCodeAt(0)
|
|
14
|
+
return (x < 32 || x === 127) ? '?' : c
|
|
15
|
+
}).join('')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 權杖遮罩
|
|
21
|
+
* - 空值(undefined / null / '')回 ''
|
|
22
|
+
* - 陣列逐元素遮罩
|
|
23
|
+
* - 字串(長度 n)露出前後各 k=min(4, floor(n/8)) 字: k=0 只回 '(len=n)', 否則 '前k...後k(len=n)'; 36 字元之 session token 為前 4 後 4(與 w-web-sso 格式相同)
|
|
24
|
+
* - 其他型別只回 '(typeof)', 絕不輸出原值
|
|
25
|
+
*
|
|
26
|
+
* @param {*} token 輸入權杖
|
|
27
|
+
* @returns {String|Array} 回傳遮罩後字串, 輸入陣列時回傳陣列
|
|
28
|
+
*/
|
|
29
|
+
function maskTok(token) {
|
|
30
|
+
if (isarr(token)) {
|
|
31
|
+
return token.map(maskTok)
|
|
32
|
+
}
|
|
33
|
+
if (token === undefined || token === null || token === '') {
|
|
34
|
+
return ''
|
|
35
|
+
}
|
|
36
|
+
if (!isestr(token)) {
|
|
37
|
+
return `(${typeof token})`
|
|
38
|
+
}
|
|
39
|
+
let n = token.length
|
|
40
|
+
let k = Math.min(4, Math.floor(n / 8))
|
|
41
|
+
if (k === 0) {
|
|
42
|
+
return `(len=${n})`
|
|
43
|
+
}
|
|
44
|
+
return `${safe(token.slice(0, k))}...${safe(token.slice(-k))}(len=${n})`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 查詢參數遮罩: 淺拷貝, 鍵名小寫為 token 者以 maskTok 處理, 其餘原樣(供印出 req.query)
|
|
50
|
+
* 非物件輸入依 maskTok 處理
|
|
51
|
+
*
|
|
52
|
+
* @param {*} q 輸入查詢參數物件(Hapi 之 req.query 為 null-prototype 物件)
|
|
53
|
+
* @returns {Object|String|Array} 回傳遮罩後之淺拷貝
|
|
54
|
+
*/
|
|
55
|
+
function maskQuery(q) {
|
|
56
|
+
if (q === null || typeof q !== 'object' || isarr(q)) {
|
|
57
|
+
return maskTok(q)
|
|
58
|
+
}
|
|
59
|
+
let r = {}
|
|
60
|
+
for (let k of Object.keys(q)) {
|
|
61
|
+
r[k] = (k.toLowerCase() === 'token') ? maskTok(q[k]) : q[k]
|
|
62
|
+
}
|
|
63
|
+
return r
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 網址遮罩: http(s) 只留 origin + pathname, 捨棄 query / fragment / userinfo(其內常含權杖, 如 ?token=)
|
|
69
|
+
* - 陣列逐元素處理; 空值回 ''; 其他非字串只回 '(typeof)'
|
|
70
|
+
* - 非 http(s) 之網址(如 data:、javascript:)路徑即全部內容, 只回 protocol(同 w-web-sso src/maskUrl.mjs)
|
|
71
|
+
* - 無法解析回 '(invalid-url)'
|
|
72
|
+
*
|
|
73
|
+
* @param {*} u 輸入網址
|
|
74
|
+
* @returns {String|Array} 回傳可寫入 log 之字串, 輸入陣列時回傳陣列
|
|
75
|
+
*/
|
|
76
|
+
function maskUrl(u) {
|
|
77
|
+
if (isarr(u)) {
|
|
78
|
+
return u.map(maskUrl)
|
|
79
|
+
}
|
|
80
|
+
if (u === undefined || u === null || u === '') {
|
|
81
|
+
return ''
|
|
82
|
+
}
|
|
83
|
+
if (!isestr(u)) {
|
|
84
|
+
return `(${typeof u})`
|
|
85
|
+
}
|
|
86
|
+
let x = null
|
|
87
|
+
try {
|
|
88
|
+
x = new URL(u)
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return '(invalid-url)'
|
|
92
|
+
}
|
|
93
|
+
if (x.protocol !== 'http:' && x.protocol !== 'https:') {
|
|
94
|
+
return x.protocol
|
|
95
|
+
}
|
|
96
|
+
return `${x.origin}${x.pathname}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
maskTok,
|
|
102
|
+
maskQuery,
|
|
103
|
+
maskUrl
|
|
104
|
+
}
|
package/server/procLang.mjs
CHANGED
|
@@ -318,6 +318,14 @@ let kpLang = {
|
|
|
318
318
|
eng: `Hide menu`,
|
|
319
319
|
cht: `隱藏選單`,
|
|
320
320
|
},
|
|
321
|
+
menuUser: { //上方標題列窄版漢堡按鈕之無障礙名稱(aria-label), 其選單內含當前登入者資訊/語系切換
|
|
322
|
+
eng: `User menu`,
|
|
323
|
+
cht: `使用者選單`,
|
|
324
|
+
},
|
|
325
|
+
language: { //上方標題列窄版選單內語系切換列之標籤
|
|
326
|
+
eng: `Language`,
|
|
327
|
+
cht: `語系`,
|
|
328
|
+
},
|
|
321
329
|
|
|
322
330
|
mmTargets: {
|
|
323
331
|
eng: `Targets`,
|
package/server/procStaInfor.mjs
CHANGED
|
@@ -6,13 +6,23 @@ import staEvent from './staLogs/staEvent.callWorker.mjs'
|
|
|
6
6
|
import staEventTable from './staLogs/staEventTable.callWorker.mjs'
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
//mock 之事件名單:
|
|
10
|
+
// true(預設 5 種):既有 e2e 標準圖所用;
|
|
11
|
+
// 'many'(13 種):本系統實際會記錄之事件名(取自本機 log),供圖例列數跨門檻之 e2e(1440 寬時一般式 2 列,勾全部加總並切中文為 3 列)
|
|
12
|
+
let MOCK_EVENTS = ['verifyConn', 'updateTargets-success', 'checkUser-error', 'api/getPerm-success', 'getWebInfor-success']
|
|
13
|
+
let MOCK_EVENTS_MANY = ['api/getPermUserInfor-success', 'api/getUserByToken-success', 'getStaEvent-success', 'getStaEventTable-success', 'getTokenUser-error', 'getWebInfor-success', 'updateGrups-success', 'updatePemis-success', 'updateTabItems-pickKeysOnly', 'updateTargets-success', 'updateUsers-success', 'verifyConn', 'verifyConn-error']
|
|
14
|
+
function getMockEvents(mock) {
|
|
15
|
+
return mock === 'many' ? MOCK_EVENTS_MANY : MOCK_EVENTS
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
//mock 確定性資料集(供 e2e 統計圖穩定用):固定起點時間 + 固定 sin 計數,不依 now / log → 每次完全相同。
|
|
10
|
-
//觸發:opt.mock
|
|
11
|
-
function genMockStaEvent(timeInterval = 'hr') {
|
|
20
|
+
//觸發:opt.mock 為 true 或 'many'(由 settings.json staEventMock 經 srv.mjs → WWebPerm 傳入)。非 mock 時走真實 staEvent。
|
|
21
|
+
function genMockStaEvent(timeInterval = 'hr', mock = true) {
|
|
12
22
|
let fmt = timeInterval === 'day' ? 'YYYY-MM-DD' : 'YYYY-MM-DDTHH'
|
|
13
23
|
let unit = timeInterval === 'hr' ? 'hour' : 'day'
|
|
14
24
|
let nBuckets = timeInterval === 'hr' ? 48 : 7
|
|
15
|
-
let events =
|
|
25
|
+
let events = getMockEvents(mock)
|
|
16
26
|
let base = ot('2025-01-01T00:00:00') //固定起點,不依 now
|
|
17
27
|
let rs = []
|
|
18
28
|
for (let i = 0; i < nBuckets; i++) {
|
|
@@ -30,15 +40,23 @@ function genMockStaEvent(timeInterval = 'hr') {
|
|
|
30
40
|
|
|
31
41
|
|
|
32
42
|
//mock 確定性資料集(供 e2e 統計表穩定用):固定窗計數,不依 now / log → 每次完全相同。
|
|
33
|
-
|
|
34
|
-
function genMockStaEventTable() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
//事件名單與 genMockStaEvent 相同;各事件滿足 last1Day>last8Hour>last4Hour>last1Hour,且各事件 last1Day 互不相同(排序明確、上多下少)。
|
|
44
|
+
function genMockStaEventTable(mock = true) {
|
|
45
|
+
if (mock !== 'many') {
|
|
46
|
+
let rs = [
|
|
47
|
+
{ event: 'verifyConn', last1Day: 240, last8Hour: 90, last4Hour: 50, last1Hour: 15 },
|
|
48
|
+
{ event: 'updateTargets-success', last1Day: 180, last8Hour: 70, last4Hour: 38, last1Hour: 11 },
|
|
49
|
+
{ event: 'checkUser-error', last1Day: 120, last8Hour: 45, last4Hour: 24, last1Hour: 7 },
|
|
50
|
+
{ event: 'api/getPerm-success', last1Day: 90, last8Hour: 33, last4Hour: 18, last1Hour: 5 },
|
|
51
|
+
{ event: 'getWebInfor-success', last1Day: 60, last8Hour: 22, last4Hour: 12, last1Hour: 3 },
|
|
52
|
+
]
|
|
53
|
+
return rs
|
|
54
|
+
}
|
|
55
|
+
//'many': 依名單順序遞減, last1Day 自 400 起每事件少 20(互不相同)
|
|
56
|
+
let rs = MOCK_EVENTS_MANY.map((event, k) => {
|
|
57
|
+
let last1Day = 400 - k * 20
|
|
58
|
+
return { event, last1Day, last8Hour: Math.round(last1Day * 0.4), last4Hour: Math.round(last1Day * 0.2), last1Hour: Math.round(last1Day * 0.05) }
|
|
59
|
+
})
|
|
42
60
|
return rs
|
|
43
61
|
}
|
|
44
62
|
|
|
@@ -66,7 +84,7 @@ function proc(opt = {}) {
|
|
|
66
84
|
|
|
67
85
|
//mock 模式回固定確定性資料集
|
|
68
86
|
if (mock) {
|
|
69
|
-
return genMockStaEvent(timeInterval)
|
|
87
|
+
return genMockStaEvent(timeInterval, mock)
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
//staEvent
|
|
@@ -98,7 +116,7 @@ function proc(opt = {}) {
|
|
|
98
116
|
|
|
99
117
|
//mock 模式回固定確定性資料集
|
|
100
118
|
if (mock) {
|
|
101
|
-
return genMockStaEventTable()
|
|
119
|
+
return genMockStaEventTable(mock)
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
//staEventTable
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
3
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
4
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
//wrapInjected: 部署方注入函數(getUserByToken / verifyClientUser / verifyAppUser)之包裝(W 契約, spec/設計要點與取捨.md ADR-023)
|
|
8
|
+
//why: 注入函數拋錯或 reject 時原會被 await 原樣上拋, 其原文經 pm2resolve 回前端、經 verifyConn 之 msg 入 srLog、經 error 事件印出;
|
|
9
|
+
//部署方常以 w-web-sso 對外 helper(≤1.1.4)包裝 getUserByToken, 其 reject 字串含已代入系統介接權杖與使用者權杖之完整網址(修正前探測實證)。
|
|
10
|
+
//包裝後失敗視同否定結果(由呼叫端給 failValue), 走各呼叫點既有之查無 / 無權限分支回自家 key; 上游原文不回前端、不入 log。
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
//keysUpstream: 上游失敗可照記之 key 白名單 = w-web-sso 對外 helper 之 K 契約 14 個 key(w-web-sso ADR-068)
|
|
14
|
+
//與 sso 之同步點: sso 之 K 契約增減 key 時須同步本表(見 CLAUDE_rulebook.md「權杖不外洩」)
|
|
15
|
+
let keysUpstream = [
|
|
16
|
+
'invalidUrl',
|
|
17
|
+
'invalidTokenSelf',
|
|
18
|
+
'invalidTokenTar',
|
|
19
|
+
'invalidUserIdTar',
|
|
20
|
+
'noTokenKeyValueInUrl',
|
|
21
|
+
'noTokenKeyUserIdInUrl',
|
|
22
|
+
'noTokenInUrl',
|
|
23
|
+
'cannotGetUserByUrl',
|
|
24
|
+
'cannotGetUsersByUrl',
|
|
25
|
+
'cannotGetUserDataByUrl',
|
|
26
|
+
'cannotGetUsersDataByUrl',
|
|
27
|
+
'noUserDataByUrl',
|
|
28
|
+
'noUsersDataByUrl',
|
|
29
|
+
'noUserDataAfterConvert',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
//reName: Error.name 之名稱形狀(同 w-web-sso helper 診斷之 errName), 不符者不記(name 亦可被設為任意字串)
|
|
34
|
+
let reName = /^[A-Za-z]{1,40}$/
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 描述上游失敗, 供寫入 log: 絕不含上游原文
|
|
39
|
+
* - reject 值(字串)或其 message 屬白名單 key → { upstream: key }
|
|
40
|
+
* - 其餘 → { upstreamType, upstreamName }: 型別(null / array / typeof)與名稱形狀之 Error.name(無則 '')
|
|
41
|
+
*
|
|
42
|
+
* @param {*} err 輸入注入函數拋出或 reject 之值
|
|
43
|
+
* @returns {Object} 回傳描述物件
|
|
44
|
+
*/
|
|
45
|
+
function describeUpstream(err) {
|
|
46
|
+
let key = isestr(err) ? err : get(err, 'message')
|
|
47
|
+
if (isestr(key) && keysUpstream.includes(key)) {
|
|
48
|
+
return { upstream: key }
|
|
49
|
+
}
|
|
50
|
+
let upstreamType = (err === null) ? 'null' : (isarr(err) ? 'array' : typeof err)
|
|
51
|
+
let name = get(err, 'name')
|
|
52
|
+
let upstreamName = (isestr(name) && reName.test(name)) ? name : ''
|
|
53
|
+
return { upstreamType, upstreamName }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 包裝注入函數: 回傳(同步或 Promise)原樣回傳; 同步拋錯或 reject 回 failValue, 並以 describeUpstream(err) 呼叫 onFail
|
|
59
|
+
* 包裝後一律回 Promise(呼叫端既有之 ispm 判斷照常)
|
|
60
|
+
*
|
|
61
|
+
* @param {Function} fn 輸入注入函數
|
|
62
|
+
* @param {Object} [opt={}] 輸入設定物件
|
|
63
|
+
* @param {*} [opt.failValue=null] 輸入失敗時之回傳值(getUserByToken 為 null 等同查無, verify* 為 false 等同無權限)
|
|
64
|
+
* @param {Function} [opt.onFail=null] 輸入失敗時之紀錄函數, 只收到 describeUpstream 之結果(收不到上游原文); 其自身拋錯不影響回傳
|
|
65
|
+
* @returns {Function} 回傳包裝後之函數
|
|
66
|
+
*/
|
|
67
|
+
function wrapInjected(fn, opt = {}) {
|
|
68
|
+
let failValue = get(opt, 'failValue', null)
|
|
69
|
+
let onFail = get(opt, 'onFail', null)
|
|
70
|
+
return async (...args) => {
|
|
71
|
+
try {
|
|
72
|
+
return await fn(...args) //await 於 try 內: 同步拋錯、reject 與 thenable 之 reject 皆攔截
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (isfun(onFail)) {
|
|
76
|
+
try {
|
|
77
|
+
onFail(describeUpstream(err))
|
|
78
|
+
}
|
|
79
|
+
catch (e) {} //紀錄失敗不改變「失敗=否定結果」之語意
|
|
80
|
+
}
|
|
81
|
+
return failValue
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
keysUpstream,
|
|
89
|
+
describeUpstream
|
|
90
|
+
}
|
|
91
|
+
export default wrapInjected
|