w-web-sso 1.1.11 → 1.1.13
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 +28 -2
- package/dist/css/{app.b17b0ba0.css → app.f000459f.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/{app-legacy.bee6840f.js → app-legacy.7c006893.js} +1 -1
- package/dist/js/{app.bee6840f.js → app.7c006893.js} +1 -1
- package/dist/js/{chunk-vendors.0492b6f5.js → chunk-vendors-legacy.97013ddc.js} +5 -5
- package/dist/js/{chunk-vendors-legacy.0492b6f5.js → chunk-vendors.97013ddc.js} +5 -5
- package/dist/w-web-sso.umd.js +4 -4
- package/dist/w-web-sso.umd.js.map +1 -1
- package/docs/WWebSso.html +5 -4
- package/docs/global.html +3 -3
- package/docs/index.html +1 -1
- package/docs/server_WWebSso.mjs.html +24 -16
- package/docs/server_getIgnoredOrmKeys.mjs.html +1 -1
- package/docs/server_lockSave.mjs.html +1 -1
- package/docs/server_ormWrite.mjs.html +1 -1
- package/docs/server_staLogs_resolveShellWorker.mjs.html +1 -1
- package/docs/server_wrapTimer.mjs.html +1 -1
- package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
- package/docs/src_fetchSsoMsg.mjs.html +15 -2
- package/docs/src_httpGetJson.mjs.html +1 -1
- package/docs/src_maskUrl.mjs.html +1 -1
- package/docs/src_plugins_staUsage.mjs.html +1 -1
- package/docs/src_plugins_submitGuard.mjs.html +1 -1
- package/package.json +2 -2
- package/server/WWebSso.mjs +23 -15
- package/server/procCore.mjs +800 -382
- package/server/procLang.mjs +23 -0
- package/server/procProtect.mjs +300 -117
- package/server/srLog.mjs +31 -8
- package/src/baselineOf.mjs +76 -0
- package/src/components/LayoutContentIps.vue +24 -2
- package/src/components/LayoutContentTokens.vue +24 -2
- package/src/components/LayoutContentUsers.vue +9 -3
- package/src/fetchSsoMsg.mjs +14 -1
- package/src/getUserByToken.mjs +2 -2
- package/src/getUserByUserId.mjs +2 -2
- package/src/getUsersByToken.mjs +2 -2
- package/src/schema/tables/ips.mjs +4 -0
- package/src/schema/tables/tokens.mjs +4 -0
- package/src/writeContracts/index.mjs +9 -0
- package/src/writeContracts/ips.mjs +9 -0
- package/src/writeContracts/tokens.mjs +12 -0
- package/src/writeContracts/tokensSession.mjs +10 -0
- package/src/writeContracts/users.mjs +15 -0
package/docs/WWebSso.html
CHANGED
|
@@ -77,15 +77,16 @@
|
|
|
77
77
|
<dd class="tag-description"><ul class="dummy"><li><p>WWebSso 單一登入(SSO)伺服器</p>
|
|
78
78
|
<p>建立SSO web服務: 使用者登入/自動登入/登出, 自助註冊與email驗證, 變更/重設密碼與通知信,
|
|
79
79
|
token發放/驗證/更新, 後台使用者/金鑰/IP管理與統計資訊, 以及供外部系統查驗token與使用者資訊之API</p>
|
|
80
|
-
<p>來源IP(IP封鎖、調用頻率限制、新IP登錄與統計)取自資料通道請求標頭X-Forwarded-For之第一段(
|
|
81
|
-
|
|
80
|
+
<p>來源IP(IP封鎖、調用頻率限制、新IP登錄與統計)取自資料通道請求標頭X-Forwarded-For之第一段(最左):須為合法IP(net.isIP)才採用且原樣不改寫,
|
|
81
|
+
非IP字串(含標記、超長等)退回連線位址並限流記一筆warn;無此標頭或為空亦取連線位址。
|
|
82
|
+
信任邊界:本套件信任該標頭且不做防偽造(偽造防護屬防火牆/反向代理之責)——直接對外提供服務(前方無反向代理)時,用戶端可任意填寫合法格式之IP(規避封鎖、冒用他人IP);
|
|
82
83
|
部署於反向代理後時,請由最外層受信任之代理覆寫(而非附加)該標頭。僅/api/cleanKpIpCallApi與/api/cleanKpAccountLoginFailed取連線位址,不受影響</p></li></ul></dd>
|
|
83
84
|
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
<dt class="tag-source">Source:</dt>
|
|
87
88
|
<dd class="tag-source"><ul class="dummy"><li>
|
|
88
|
-
<a href="server_WWebSso.mjs.html">server/WWebSso.mjs</a>, <a href="server_WWebSso.mjs.html#
|
|
89
|
+
<a href="server_WWebSso.mjs.html">server/WWebSso.mjs</a>, <a href="server_WWebSso.mjs.html#line109">line 109</a>
|
|
89
90
|
</li></ul></dd>
|
|
90
91
|
|
|
91
92
|
|
|
@@ -2491,7 +2492,7 @@ token發放/驗證/更新, 後台使用者/金鑰/IP管理與統計資訊, 以
|
|
|
2491
2492
|
<br class="clear">
|
|
2492
2493
|
|
|
2493
2494
|
<footer>
|
|
2494
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
2495
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
2495
2496
|
</footer>
|
|
2496
2497
|
|
|
2497
2498
|
<script>prettyPrint();</script>
|
package/docs/global.html
CHANGED
|
@@ -388,7 +388,7 @@ console.log(calcUsageSummary(freq, 'ip', ot('2026-10-20T10:30:00+08:00')))
|
|
|
388
388
|
|
|
389
389
|
<dt class="tag-source">Source:</dt>
|
|
390
390
|
<dd class="tag-source"><ul class="dummy"><li>
|
|
391
|
-
<a href="src_fetchSsoMsg.mjs.html">src/fetchSsoMsg.mjs</a>, <a href="src_fetchSsoMsg.mjs.html#
|
|
391
|
+
<a href="src_fetchSsoMsg.mjs.html">src/fetchSsoMsg.mjs</a>, <a href="src_fetchSsoMsg.mjs.html#line134">line 134</a>
|
|
392
392
|
</li></ul></dd>
|
|
393
393
|
|
|
394
394
|
|
|
@@ -734,7 +734,7 @@ operatorId 可為操作者 id 或「操作者 id + 列識別」(只擋同一列
|
|
|
734
734
|
|
|
735
735
|
<dt class="tag-source">Source:</dt>
|
|
736
736
|
<dd class="tag-source"><ul class="dummy"><li>
|
|
737
|
-
<a href="src_fetchSsoMsg.mjs.html">src/fetchSsoMsg.mjs</a>, <a href="src_fetchSsoMsg.mjs.html#
|
|
737
|
+
<a href="src_fetchSsoMsg.mjs.html">src/fetchSsoMsg.mjs</a>, <a href="src_fetchSsoMsg.mjs.html#line71">line 71</a>
|
|
738
738
|
</li></ul></dd>
|
|
739
739
|
|
|
740
740
|
|
|
@@ -3312,7 +3312,7 @@ why: 計時器之 async 回呼 reject 時無人接手, Node 預設即結束行
|
|
|
3312
3312
|
<br class="clear">
|
|
3313
3313
|
|
|
3314
3314
|
<footer>
|
|
3315
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
3315
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
3316
3316
|
</footer>
|
|
3317
3317
|
|
|
3318
3318
|
<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 Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
|
@@ -72,7 +72,7 @@ import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
|
72
72
|
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
73
73
|
import ds from '../src/schema/index.mjs'
|
|
74
74
|
import * as s from '../src/plugins/mShare.mjs'
|
|
75
|
-
import srLogInit, { maskToken, maskUrl, maskKv } from './srLog.mjs'
|
|
75
|
+
import srLogInit, { maskToken, maskUrl, maskKv, capText } from './srLog.mjs'
|
|
76
76
|
import srEmailInit from './srEmail.mjs'
|
|
77
77
|
import procCore from './procCore.mjs'
|
|
78
78
|
import procLang from './procLang.mjs'
|
|
@@ -89,8 +89,9 @@ import { defaultPasswordPolicy } from './defaultPasswordPolicy.mjs'
|
|
|
89
89
|
* 建立SSO web服務: 使用者登入/自動登入/登出, 自助註冊與email驗證, 變更/重設密碼與通知信,
|
|
90
90
|
* token發放/驗證/更新, 後台使用者/金鑰/IP管理與統計資訊, 以及供外部系統查驗token與使用者資訊之API
|
|
91
91
|
*
|
|
92
|
-
* 來源IP(IP封鎖、調用頻率限制、新IP登錄與統計)取自資料通道請求標頭X-Forwarded-For之第一段(
|
|
93
|
-
*
|
|
92
|
+
* 來源IP(IP封鎖、調用頻率限制、新IP登錄與統計)取自資料通道請求標頭X-Forwarded-For之第一段(最左):須為合法IP(net.isIP)才採用且原樣不改寫,
|
|
93
|
+
* 非IP字串(含標記、超長等)退回連線位址並限流記一筆warn;無此標頭或為空亦取連線位址。
|
|
94
|
+
* 信任邊界:本套件信任該標頭且不做防偽造(偽造防護屬防火牆/反向代理之責)——直接對外提供服務(前方無反向代理)時,用戶端可任意填寫合法格式之IP(規避封鎖、冒用他人IP);
|
|
94
95
|
* 部署於反向代理後時,請由最外層受信任之代理覆寫(而非附加)該標頭。僅/api/cleanKpIpCallApi與/api/cleanKpAccountLoginFailed取連線位址,不受影響
|
|
95
96
|
*
|
|
96
97
|
* @class
|
|
@@ -509,7 +510,7 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
509
510
|
console.log('WServOrm init failed', err)
|
|
510
511
|
throw err
|
|
511
512
|
}
|
|
512
|
-
let { woItems, procOrm } = wp
|
|
513
|
+
let { woItems, procOrm, withTabLocks, guardWrite } = wp //withTabLocks / guardWrite: 每表一鍵之表鎖(多人同時編輯之寫入契約, 原則 10), procCore 與 procProtect 共用同一組
|
|
513
514
|
|
|
514
515
|
//getWebInfor
|
|
515
516
|
let getWebInfor = () => {
|
|
@@ -586,8 +587,10 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
586
587
|
verifyEmailResultContent = fs.readFileSync(fpTpl, 'utf8')
|
|
587
588
|
}
|
|
588
589
|
|
|
589
|
-
let p = procCore(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, passwordPolicy, allowUserRegistration, siteUrl, verifyBaseUrl })
|
|
590
|
+
let p = procCore(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, passwordPolicy, allowUserRegistration, siteUrl, verifyBaseUrl, withTabLocks, guardWrite })
|
|
590
591
|
let pp = procProtect(woItems, p, {
|
|
592
|
+
procOrm, //系統直寫(自動封鎖、計時器登錄 IP)經 procOrm 且在表鎖內, 更新者記 'system'
|
|
593
|
+
withTabLocks,
|
|
591
594
|
srLog, //app token 跳過速率封鎖時記 debug (ADR-052)
|
|
592
595
|
minForAccountLoginFailed,
|
|
593
596
|
numForAccountLoginFailed,
|
|
@@ -961,7 +964,7 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
961
964
|
// console.log('us', us)
|
|
962
965
|
|
|
963
966
|
//對外介接契約不輸出憑證欄(ADR-068): 介接端常以本清單同步使用者, 憑證會擴散至其資料庫與 log;
|
|
964
|
-
|
|
967
|
+
//只在對外路由剝除(後台 kpfun getUsersList 同樣不回傳 tokenVerify, T3; 存檔時伺服器專有欄位取庫內值, 不再依賴前端整列存回)
|
|
965
968
|
us = us.map(p.omitUserCredential)
|
|
966
969
|
|
|
967
970
|
return us
|
|
@@ -1007,8 +1010,8 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1007
1010
|
//valueLog: key 非識別欄(id / account / email / name)時 value 可能即憑證(token / tokenVerify 等), 記 log 前需遮罩 (ADR-068)
|
|
1008
1011
|
let valueLog = maskKv(key, value)
|
|
1009
1012
|
|
|
1010
|
-
//info
|
|
1011
|
-
srLog.info({ event: 'api/getSsoUserInfor', token: maskToken(token), key, value: valueLog })
|
|
1013
|
+
//info, 先於欄位名白名單 (R-QKEY-01: 白名單於 p.checkTokenAndGetUserInfor 內、checkToken 之後, 被拒之查詢仍留稽核紀錄且 value 已遮罩)
|
|
1014
|
+
srLog.info({ event: 'api/getSsoUserInfor', token: maskToken(token), key: capText(key), value: valueLog })
|
|
1012
1015
|
|
|
1013
1016
|
//callApiByToken
|
|
1014
1017
|
pp.callApiByToken(token)
|
|
@@ -1263,7 +1266,8 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1263
1266
|
if (!_strictStr(token, key, value)) return Promise.reject('tokenExpired')
|
|
1264
1267
|
//valueLog: 與 HTTP getSsoUserInfor 同規則, key 非識別欄時 value 可能即憑證, 記 log 前需遮罩 (ADR-068)
|
|
1265
1268
|
let valueLog = maskKv(key, value)
|
|
1266
|
-
|
|
1269
|
+
//(欄位名白名單於 p.checkTokenAndGetUserInfor 內、checkToken 之後, 見 R-QKEY-01; 此記錄先於白名單)
|
|
1270
|
+
srLog.info({ event: 'kpfun-getUserInfor', token: maskToken(token), key: capText(key), value: valueLog })
|
|
1267
1271
|
//console.log('call getUserInfor...')
|
|
1268
1272
|
let r = await p.checkTokenAndGetUserInfor(token, key, value, { fun: funCheckAdmin, perm: 'readUsers' })
|
|
1269
1273
|
//console.log('call getUserInfor end')
|
|
@@ -1306,13 +1310,15 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1306
1310
|
return rs
|
|
1307
1311
|
},
|
|
1308
1312
|
|
|
1309
|
-
|
|
1313
|
+
//optSave: 選用之第 4 參數 { baseline }(前端以 buildBaseline 對載入時畫面所見之列自算, 多人同時編輯之寫入契約 R-CONC-05); 未給即不帶基準(app 權杖直呼、已開啟之舊版頁面, 本 major 允許),
|
|
1314
|
+
//格式 / 白名單由 procCore.parseSaveOpt 驗證, 不識者 reject saveBaselineFormat, 不降級為不帶基準; 此處只轉交, 不展開給 procOrm
|
|
1315
|
+
updateUsersList: async (_t, token, lang, rows, optSave) => {
|
|
1310
1316
|
if (!_strictStr(token)) return Promise.reject('tokenExpired')
|
|
1311
1317
|
if (!isearr(rows) || !rows.every((r) => isestr(get(r, 'id', '')))) return Promise.reject('invalidRows')
|
|
1312
1318
|
srLog.info({ event: 'kpfun-updateUsersList', token: maskToken(token), lang })
|
|
1313
1319
|
//console.log('call updateUsersList...')
|
|
1314
1320
|
//內層 reject key 字串 (含 checkToken 之 tokenExpired) 直接透出回前端 (前端 $t 顯示)
|
|
1315
|
-
let rs = await p.checkTokenAndUpdateUsersList(token, lang, rows, { fun: funCheckAdmin, perm: 'writeUsers' })
|
|
1321
|
+
let rs = await p.checkTokenAndUpdateUsersList(token, lang, rows, { fun: funCheckAdmin, perm: 'writeUsers' }, optSave)
|
|
1316
1322
|
//console.log('call updateUsersList end')
|
|
1317
1323
|
return rs
|
|
1318
1324
|
},
|
|
@@ -1326,12 +1332,13 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1326
1332
|
return rs
|
|
1327
1333
|
},
|
|
1328
1334
|
|
|
1329
|
-
|
|
1335
|
+
//optSave: { baseline, baselineSession }(app 列嚴格整表基準、session 列逐列寬鬆基準; 見 users 同名參數說明)
|
|
1336
|
+
updateTokensList: async (_t, token, lang, rows, optSave) => {
|
|
1330
1337
|
if (!_strictStr(token)) return Promise.reject('tokenExpired')
|
|
1331
1338
|
if (!isearr(rows) || !rows.every((r) => isestr(get(r, 'id', '')))) return Promise.reject('invalidRows')
|
|
1332
1339
|
srLog.info({ event: 'kpfun-updateTokensList', token: maskToken(token), lang })
|
|
1333
1340
|
//console.log('call updateTokensList...')
|
|
1334
|
-
let rs = await p.checkTokenAndUpdateTokensList(token, rows, { fun: funCheckAdmin, perm: 'writeTokens' })
|
|
1341
|
+
let rs = await p.checkTokenAndUpdateTokensList(token, rows, { fun: funCheckAdmin, perm: 'writeTokens' }, optSave)
|
|
1335
1342
|
//console.log('call updateTokensList end')
|
|
1336
1343
|
return rs
|
|
1337
1344
|
},
|
|
@@ -1345,12 +1352,13 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1345
1352
|
return rs
|
|
1346
1353
|
},
|
|
1347
1354
|
|
|
1348
|
-
|
|
1355
|
+
//optSave: { baseline }(見 users 同名參數說明)
|
|
1356
|
+
updateIpsList: async (_t, token, lang, rows, optSave) => {
|
|
1349
1357
|
if (!_strictStr(token)) return Promise.reject('tokenExpired')
|
|
1350
1358
|
if (!isearr(rows) || !rows.every((r) => isestr(get(r, 'id', '')))) return Promise.reject('invalidRows')
|
|
1351
1359
|
srLog.info({ event: 'kpfun-updateIpsList', token: maskToken(token), lang })
|
|
1352
1360
|
//console.log('call updateIpsList...')
|
|
1353
|
-
let rs = await p.checkTokenAndUpdateIpsList(token, rows, { fun: funCheckAdmin, perm: 'writeIps' })
|
|
1361
|
+
let rs = await p.checkTokenAndUpdateIpsList(token, rows, { fun: funCheckAdmin, perm: 'writeIps' }, optSave)
|
|
1354
1362
|
//console.log('call updateIpsList end')
|
|
1355
1363
|
return rs
|
|
1356
1364
|
},
|
|
@@ -1449,7 +1457,7 @@ export default WWebSso
|
|
|
1449
1457
|
<br class="clear">
|
|
1450
1458
|
|
|
1451
1459
|
<footer>
|
|
1452
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
1460
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
1453
1461
|
</footer>
|
|
1454
1462
|
|
|
1455
1463
|
<script>prettyPrint();</script>
|
|
@@ -93,7 +93,7 @@ export default getIgnoredOrmKeys
|
|
|
93
93
|
<br class="clear">
|
|
94
94
|
|
|
95
95
|
<footer>
|
|
96
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
96
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
97
97
|
</footer>
|
|
98
98
|
|
|
99
99
|
<script>prettyPrint();</script>
|
|
@@ -102,7 +102,7 @@ export default createLockSave
|
|
|
102
102
|
<br class="clear">
|
|
103
103
|
|
|
104
104
|
<footer>
|
|
105
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
105
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
106
106
|
</footer>
|
|
107
107
|
|
|
108
108
|
<script>prettyPrint();</script>
|
|
@@ -108,7 +108,7 @@ export default ormWrite
|
|
|
108
108
|
<br class="clear">
|
|
109
109
|
|
|
110
110
|
<footer>
|
|
111
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
111
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
112
112
|
</footer>
|
|
113
113
|
|
|
114
114
|
<script>prettyPrint();</script>
|
|
@@ -167,7 +167,7 @@ export default resolveShellWorker
|
|
|
167
167
|
<br class="clear">
|
|
168
168
|
|
|
169
169
|
<footer>
|
|
170
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
170
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
171
171
|
</footer>
|
|
172
172
|
|
|
173
173
|
<script>prettyPrint();</script>
|
|
@@ -117,7 +117,7 @@ export default wrapTimer
|
|
|
117
117
|
<br class="clear">
|
|
118
118
|
|
|
119
119
|
<footer>
|
|
120
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
120
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
121
121
|
</footer>
|
|
122
122
|
|
|
123
123
|
<script>prettyPrint();</script>
|
|
@@ -188,7 +188,7 @@ export default {
|
|
|
188
188
|
<br class="clear">
|
|
189
189
|
|
|
190
190
|
<footer>
|
|
191
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
191
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
192
192
|
</footer>
|
|
193
193
|
|
|
194
194
|
<script>prettyPrint();</script>
|
|
@@ -89,6 +89,19 @@ function fill(url, kvs) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
//hasUrlParam: 網址範本是否含「恰一個」完整的 name=value 參數(R-QKEY-01 規則 5)
|
|
93
|
+
//以參數分隔字元定界, 而非以參數字元排除: 參數名之前須為字串起點或 ? & " 空白, 參數值之後須為字串終點或 & # " 空白
|
|
94
|
+
// (" 與空白供單元測試以 data: 網址模擬 SSO 時, 範本位於 JSON 字串或文字內);
|
|
95
|
+
// key=tokenVerify、key=token+ (server 實得 "token ")、xkey=token、a=key=token、&amp;key=token、/key=token、#key=token、token={sysToken}x 等
|
|
96
|
+
// 僅子字串相符者不通過(原 indexOf 子字串檢查會通過); 同名參數出現兩次以上(key=token&key=id)亦不通過(server 收陣列而拒)
|
|
97
|
+
function hasUrlParam(url, name, value) {
|
|
98
|
+
let esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
99
|
+
let nAny = (url.match(new RegExp(`(?<=^|[?&"\\s])${esc(name)}=`, 'g')) || []).length
|
|
100
|
+
let nOne = (url.match(new RegExp(`(?<=^|[?&"\\s])${esc(name)}=${esc(value)}(?=$|[&#"\\s])`, 'g')) || []).length
|
|
101
|
+
return nAny === 1 && nOne === 1
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
92
105
|
/**
|
|
93
106
|
* 代入 → 以 GET 打 SSO → 判 state → 驗資料型別, 成功回 SSO 之 msg
|
|
94
107
|
*
|
|
@@ -183,7 +196,7 @@ async function convertUser(funConvertUser, u) {
|
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
|
|
186
|
-
export { fetchSsoMsg, convertUser }
|
|
199
|
+
export { fetchSsoMsg, convertUser, hasUrlParam }
|
|
187
200
|
</code></pre>
|
|
188
201
|
</article>
|
|
189
202
|
</section>
|
|
@@ -198,7 +211,7 @@ export { fetchSsoMsg, convertUser }
|
|
|
198
211
|
<br class="clear">
|
|
199
212
|
|
|
200
213
|
<footer>
|
|
201
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
214
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
202
215
|
</footer>
|
|
203
216
|
|
|
204
217
|
<script>prettyPrint();</script>
|
|
@@ -100,7 +100,7 @@ export default httpGetJson
|
|
|
100
100
|
<br class="clear">
|
|
101
101
|
|
|
102
102
|
<footer>
|
|
103
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
103
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
104
104
|
</footer>
|
|
105
105
|
|
|
106
106
|
<script>prettyPrint();</script>
|
|
@@ -99,7 +99,7 @@ export default maskUrl
|
|
|
99
99
|
<br class="clear">
|
|
100
100
|
|
|
101
101
|
<footer>
|
|
102
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
102
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
103
103
|
</footer>
|
|
104
104
|
|
|
105
105
|
<script>prettyPrint();</script>
|
|
@@ -237,7 +237,7 @@ export { getFreqVal, sumFreqVal, groupFreq, calcUsageSummary }
|
|
|
237
237
|
<br class="clear">
|
|
238
238
|
|
|
239
239
|
<footer>
|
|
240
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
240
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
241
241
|
</footer>
|
|
242
242
|
|
|
243
243
|
<script>prettyPrint();</script>
|
|
@@ -150,7 +150,7 @@ export default submitGuard
|
|
|
150
150
|
<br class="clear">
|
|
151
151
|
|
|
152
152
|
<footer>
|
|
153
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
153
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 11:30:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
154
154
|
</footer>
|
|
155
155
|
|
|
156
156
|
<script>prettyPrint();</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-web-sso",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.13",
|
|
4
4
|
"main": "dist/w-web-sso.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"json5": "^2.2.3",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"w-echarts-vue": "^1.0.19",
|
|
11
11
|
"w-email": "^1.1.8",
|
|
12
12
|
"w-serv-hapi": "^1.1.7",
|
|
13
|
-
"w-serv-orm": "^1.1.
|
|
13
|
+
"w-serv-orm": "^1.1.17",
|
|
14
14
|
"w-syslog": "^1.0.34",
|
|
15
15
|
"wsemi": "^1.9.21"
|
|
16
16
|
},
|
package/server/WWebSso.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
|
25
25
|
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
26
26
|
import ds from '../src/schema/index.mjs'
|
|
27
27
|
import * as s from '../src/plugins/mShare.mjs'
|
|
28
|
-
import srLogInit, { maskToken, maskUrl, maskKv } from './srLog.mjs'
|
|
28
|
+
import srLogInit, { maskToken, maskUrl, maskKv, capText } from './srLog.mjs'
|
|
29
29
|
import srEmailInit from './srEmail.mjs'
|
|
30
30
|
import procCore from './procCore.mjs'
|
|
31
31
|
import procLang from './procLang.mjs'
|
|
@@ -42,8 +42,9 @@ import { defaultPasswordPolicy } from './defaultPasswordPolicy.mjs'
|
|
|
42
42
|
* 建立SSO web服務: 使用者登入/自動登入/登出, 自助註冊與email驗證, 變更/重設密碼與通知信,
|
|
43
43
|
* token發放/驗證/更新, 後台使用者/金鑰/IP管理與統計資訊, 以及供外部系統查驗token與使用者資訊之API
|
|
44
44
|
*
|
|
45
|
-
* 來源IP(IP封鎖、調用頻率限制、新IP登錄與統計)取自資料通道請求標頭X-Forwarded-For之第一段(
|
|
46
|
-
*
|
|
45
|
+
* 來源IP(IP封鎖、調用頻率限制、新IP登錄與統計)取自資料通道請求標頭X-Forwarded-For之第一段(最左):須為合法IP(net.isIP)才採用且原樣不改寫,
|
|
46
|
+
* 非IP字串(含標記、超長等)退回連線位址並限流記一筆warn;無此標頭或為空亦取連線位址。
|
|
47
|
+
* 信任邊界:本套件信任該標頭且不做防偽造(偽造防護屬防火牆/反向代理之責)——直接對外提供服務(前方無反向代理)時,用戶端可任意填寫合法格式之IP(規避封鎖、冒用他人IP);
|
|
47
48
|
* 部署於反向代理後時,請由最外層受信任之代理覆寫(而非附加)該標頭。僅/api/cleanKpIpCallApi與/api/cleanKpAccountLoginFailed取連線位址,不受影響
|
|
48
49
|
*
|
|
49
50
|
* @class
|
|
@@ -462,7 +463,7 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
462
463
|
console.log('WServOrm init failed', err)
|
|
463
464
|
throw err
|
|
464
465
|
}
|
|
465
|
-
let { woItems, procOrm } = wp
|
|
466
|
+
let { woItems, procOrm, withTabLocks, guardWrite } = wp //withTabLocks / guardWrite: 每表一鍵之表鎖(多人同時編輯之寫入契約, 原則 10), procCore 與 procProtect 共用同一組
|
|
466
467
|
|
|
467
468
|
//getWebInfor
|
|
468
469
|
let getWebInfor = () => {
|
|
@@ -539,8 +540,10 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
539
540
|
verifyEmailResultContent = fs.readFileSync(fpTpl, 'utf8')
|
|
540
541
|
}
|
|
541
542
|
|
|
542
|
-
let p = procCore(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, passwordPolicy, allowUserRegistration, siteUrl, verifyBaseUrl })
|
|
543
|
+
let p = procCore(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, passwordPolicy, allowUserRegistration, siteUrl, verifyBaseUrl, withTabLocks, guardWrite })
|
|
543
544
|
let pp = procProtect(woItems, p, {
|
|
545
|
+
procOrm, //系統直寫(自動封鎖、計時器登錄 IP)經 procOrm 且在表鎖內, 更新者記 'system'
|
|
546
|
+
withTabLocks,
|
|
544
547
|
srLog, //app token 跳過速率封鎖時記 debug (ADR-052)
|
|
545
548
|
minForAccountLoginFailed,
|
|
546
549
|
numForAccountLoginFailed,
|
|
@@ -914,7 +917,7 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
914
917
|
// console.log('us', us)
|
|
915
918
|
|
|
916
919
|
//對外介接契約不輸出憑證欄(ADR-068): 介接端常以本清單同步使用者, 憑證會擴散至其資料庫與 log;
|
|
917
|
-
|
|
920
|
+
//只在對外路由剝除(後台 kpfun getUsersList 同樣不回傳 tokenVerify, T3; 存檔時伺服器專有欄位取庫內值, 不再依賴前端整列存回)
|
|
918
921
|
us = us.map(p.omitUserCredential)
|
|
919
922
|
|
|
920
923
|
return us
|
|
@@ -960,8 +963,8 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
960
963
|
//valueLog: key 非識別欄(id / account / email / name)時 value 可能即憑證(token / tokenVerify 等), 記 log 前需遮罩 (ADR-068)
|
|
961
964
|
let valueLog = maskKv(key, value)
|
|
962
965
|
|
|
963
|
-
//info
|
|
964
|
-
srLog.info({ event: 'api/getSsoUserInfor', token: maskToken(token), key, value: valueLog })
|
|
966
|
+
//info, 先於欄位名白名單 (R-QKEY-01: 白名單於 p.checkTokenAndGetUserInfor 內、checkToken 之後, 被拒之查詢仍留稽核紀錄且 value 已遮罩)
|
|
967
|
+
srLog.info({ event: 'api/getSsoUserInfor', token: maskToken(token), key: capText(key), value: valueLog })
|
|
965
968
|
|
|
966
969
|
//callApiByToken
|
|
967
970
|
pp.callApiByToken(token)
|
|
@@ -1216,7 +1219,8 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1216
1219
|
if (!_strictStr(token, key, value)) return Promise.reject('tokenExpired')
|
|
1217
1220
|
//valueLog: 與 HTTP getSsoUserInfor 同規則, key 非識別欄時 value 可能即憑證, 記 log 前需遮罩 (ADR-068)
|
|
1218
1221
|
let valueLog = maskKv(key, value)
|
|
1219
|
-
|
|
1222
|
+
//(欄位名白名單於 p.checkTokenAndGetUserInfor 內、checkToken 之後, 見 R-QKEY-01; 此記錄先於白名單)
|
|
1223
|
+
srLog.info({ event: 'kpfun-getUserInfor', token: maskToken(token), key: capText(key), value: valueLog })
|
|
1220
1224
|
//console.log('call getUserInfor...')
|
|
1221
1225
|
let r = await p.checkTokenAndGetUserInfor(token, key, value, { fun: funCheckAdmin, perm: 'readUsers' })
|
|
1222
1226
|
//console.log('call getUserInfor end')
|
|
@@ -1259,13 +1263,15 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1259
1263
|
return rs
|
|
1260
1264
|
},
|
|
1261
1265
|
|
|
1262
|
-
|
|
1266
|
+
//optSave: 選用之第 4 參數 { baseline }(前端以 buildBaseline 對載入時畫面所見之列自算, 多人同時編輯之寫入契約 R-CONC-05); 未給即不帶基準(app 權杖直呼、已開啟之舊版頁面, 本 major 允許),
|
|
1267
|
+
//格式 / 白名單由 procCore.parseSaveOpt 驗證, 不識者 reject saveBaselineFormat, 不降級為不帶基準; 此處只轉交, 不展開給 procOrm
|
|
1268
|
+
updateUsersList: async (_t, token, lang, rows, optSave) => {
|
|
1263
1269
|
if (!_strictStr(token)) return Promise.reject('tokenExpired')
|
|
1264
1270
|
if (!isearr(rows) || !rows.every((r) => isestr(get(r, 'id', '')))) return Promise.reject('invalidRows')
|
|
1265
1271
|
srLog.info({ event: 'kpfun-updateUsersList', token: maskToken(token), lang })
|
|
1266
1272
|
//console.log('call updateUsersList...')
|
|
1267
1273
|
//內層 reject key 字串 (含 checkToken 之 tokenExpired) 直接透出回前端 (前端 $t 顯示)
|
|
1268
|
-
let rs = await p.checkTokenAndUpdateUsersList(token, lang, rows, { fun: funCheckAdmin, perm: 'writeUsers' })
|
|
1274
|
+
let rs = await p.checkTokenAndUpdateUsersList(token, lang, rows, { fun: funCheckAdmin, perm: 'writeUsers' }, optSave)
|
|
1269
1275
|
//console.log('call updateUsersList end')
|
|
1270
1276
|
return rs
|
|
1271
1277
|
},
|
|
@@ -1279,12 +1285,13 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1279
1285
|
return rs
|
|
1280
1286
|
},
|
|
1281
1287
|
|
|
1282
|
-
|
|
1288
|
+
//optSave: { baseline, baselineSession }(app 列嚴格整表基準、session 列逐列寬鬆基準; 見 users 同名參數說明)
|
|
1289
|
+
updateTokensList: async (_t, token, lang, rows, optSave) => {
|
|
1283
1290
|
if (!_strictStr(token)) return Promise.reject('tokenExpired')
|
|
1284
1291
|
if (!isearr(rows) || !rows.every((r) => isestr(get(r, 'id', '')))) return Promise.reject('invalidRows')
|
|
1285
1292
|
srLog.info({ event: 'kpfun-updateTokensList', token: maskToken(token), lang })
|
|
1286
1293
|
//console.log('call updateTokensList...')
|
|
1287
|
-
let rs = await p.checkTokenAndUpdateTokensList(token, rows, { fun: funCheckAdmin, perm: 'writeTokens' })
|
|
1294
|
+
let rs = await p.checkTokenAndUpdateTokensList(token, rows, { fun: funCheckAdmin, perm: 'writeTokens' }, optSave)
|
|
1288
1295
|
//console.log('call updateTokensList end')
|
|
1289
1296
|
return rs
|
|
1290
1297
|
},
|
|
@@ -1298,12 +1305,13 @@ function WWebSso(WOrm, url, db, pathSettings, optExt = {}) {
|
|
|
1298
1305
|
return rs
|
|
1299
1306
|
},
|
|
1300
1307
|
|
|
1301
|
-
|
|
1308
|
+
//optSave: { baseline }(見 users 同名參數說明)
|
|
1309
|
+
updateIpsList: async (_t, token, lang, rows, optSave) => {
|
|
1302
1310
|
if (!_strictStr(token)) return Promise.reject('tokenExpired')
|
|
1303
1311
|
if (!isearr(rows) || !rows.every((r) => isestr(get(r, 'id', '')))) return Promise.reject('invalidRows')
|
|
1304
1312
|
srLog.info({ event: 'kpfun-updateIpsList', token: maskToken(token), lang })
|
|
1305
1313
|
//console.log('call updateIpsList...')
|
|
1306
|
-
let rs = await p.checkTokenAndUpdateIpsList(token, rows, { fun: funCheckAdmin, perm: 'writeIps' })
|
|
1314
|
+
let rs = await p.checkTokenAndUpdateIpsList(token, rows, { fun: funCheckAdmin, perm: 'writeIps' }, optSave)
|
|
1307
1315
|
//console.log('call updateIpsList end')
|
|
1308
1316
|
return rs
|
|
1309
1317
|
},
|