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.
Files changed (45) hide show
  1. package/README.md +28 -2
  2. package/dist/css/{app.b17b0ba0.css → app.f000459f.css} +1 -1
  3. package/dist/index.tmp +1 -1
  4. package/dist/js/{app-legacy.bee6840f.js → app-legacy.7c006893.js} +1 -1
  5. package/dist/js/{app.bee6840f.js → app.7c006893.js} +1 -1
  6. package/dist/js/{chunk-vendors.0492b6f5.js → chunk-vendors-legacy.97013ddc.js} +5 -5
  7. package/dist/js/{chunk-vendors-legacy.0492b6f5.js → chunk-vendors.97013ddc.js} +5 -5
  8. package/dist/w-web-sso.umd.js +4 -4
  9. package/dist/w-web-sso.umd.js.map +1 -1
  10. package/docs/WWebSso.html +5 -4
  11. package/docs/global.html +3 -3
  12. package/docs/index.html +1 -1
  13. package/docs/server_WWebSso.mjs.html +24 -16
  14. package/docs/server_getIgnoredOrmKeys.mjs.html +1 -1
  15. package/docs/server_lockSave.mjs.html +1 -1
  16. package/docs/server_ormWrite.mjs.html +1 -1
  17. package/docs/server_staLogs_resolveShellWorker.mjs.html +1 -1
  18. package/docs/server_wrapTimer.mjs.html +1 -1
  19. package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
  20. package/docs/src_fetchSsoMsg.mjs.html +15 -2
  21. package/docs/src_httpGetJson.mjs.html +1 -1
  22. package/docs/src_maskUrl.mjs.html +1 -1
  23. package/docs/src_plugins_staUsage.mjs.html +1 -1
  24. package/docs/src_plugins_submitGuard.mjs.html +1 -1
  25. package/package.json +2 -2
  26. package/server/WWebSso.mjs +23 -15
  27. package/server/procCore.mjs +800 -382
  28. package/server/procLang.mjs +23 -0
  29. package/server/procProtect.mjs +300 -117
  30. package/server/srLog.mjs +31 -8
  31. package/src/baselineOf.mjs +76 -0
  32. package/src/components/LayoutContentIps.vue +24 -2
  33. package/src/components/LayoutContentTokens.vue +24 -2
  34. package/src/components/LayoutContentUsers.vue +9 -3
  35. package/src/fetchSsoMsg.mjs +14 -1
  36. package/src/getUserByToken.mjs +2 -2
  37. package/src/getUserByUserId.mjs +2 -2
  38. package/src/getUsersByToken.mjs +2 -2
  39. package/src/schema/tables/ips.mjs +4 -0
  40. package/src/schema/tables/tokens.mjs +4 -0
  41. package/src/writeContracts/index.mjs +9 -0
  42. package/src/writeContracts/ips.mjs +9 -0
  43. package/src/writeContracts/tokens.mjs +12 -0
  44. package/src/writeContracts/tokensSession.mjs +10 -0
  45. 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
- 本套件信任該標頭:直接對外提供服務(前方無反向代理)時,用戶端可任意填寫(規避封鎖、冒用他人IP、以任意字串登錄IP與統計);
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#line108">line 108</a>
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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#line121">line 121</a>
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#line58">line 58</a>
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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
- * 本套件信任該標頭:直接對外提供服務(前方無反向代理)時,用戶端可任意填寫(規避封鎖、冒用他人IP、以任意字串登錄IP與統計);
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
- //只在對外路由剝除, 後台 kpfun getUsersList 之整列存回依賴 tokenVerify(updateTabItems 以 ltdtmapping 補齊全欄)
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
- srLog.info({ event: 'kpfun-getUserInfor', token: maskToken(token), key, value: valueLog })
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
- updateUsersList: async (_t, token, lang, rows) => {
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
- updateTokensList: async (_t, token, lang, rows) => {
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
- updateIpsList: async (_t, token, lang, rows) => {
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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
+ //以參數分隔字元定界, 而非以參數字元排除: 參數名之前須為字串起點或 ? &amp; " 空白, 參數值之後須為字串終點或 &amp; # " 空白
94
+ // (" 與空白供單元測試以 data: 網址模擬 SSO 時, 範本位於 JSON 字串或文字內);
95
+ // key=tokenVerify、key=token+ (server 實得 "token ")、xkey=token、a=key=token、&amp;amp;key=token、/key=token、#key=token、token={sysToken}x 等
96
+ // 僅子字串相符者不通過(原 indexOf 子字串檢查會通過); 同名參數出現兩次以上(key=token&amp;key=id)亦不通過(server 收陣列而拒)
97
+ function hasUrlParam(url, name, value) {
98
+ let esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&amp;')
99
+ let nAny = (url.match(new RegExp(`(?&lt;=^|[?&amp;"\\s])${esc(name)}=`, 'g')) || []).length
100
+ let nOne = (url.match(new RegExp(`(?&lt;=^|[?&amp;"\\s])${esc(name)}=${esc(value)}(?=$|[&amp;#"\\s])`, 'g')) || []).length
101
+ return nAny === 1 &amp;&amp; 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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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 Tue Oct 20 2026 15:03:56 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
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.11",
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.15",
13
+ "w-serv-orm": "^1.1.17",
14
14
  "w-syslog": "^1.0.34",
15
15
  "wsemi": "^1.9.21"
16
16
  },
@@ -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
- * 本套件信任該標頭:直接對外提供服務(前方無反向代理)時,用戶端可任意填寫(規避封鎖、冒用他人IP、以任意字串登錄IP與統計);
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
- //只在對外路由剝除, 後台 kpfun getUsersList 之整列存回依賴 tokenVerify(updateTabItems 以 ltdtmapping 補齊全欄)
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
- srLog.info({ event: 'kpfun-getUserInfor', token: maskToken(token), key, value: valueLog })
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
- updateUsersList: async (_t, token, lang, rows) => {
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
- updateTokensList: async (_t, token, lang, rows) => {
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
- updateIpsList: async (_t, token, lang, rows) => {
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
  },