w-web-sso 1.1.13 → 1.1.14
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 +13 -0
- package/dist/index.tmp +1 -1
- package/dist/js/{app-legacy.7c006893.js → app-legacy.19dcf609.js} +1 -1
- package/dist/js/{app.7c006893.js → app.19dcf609.js} +1 -1
- package/dist/w-web-sso.umd.js +4 -4
- package/dist/w-web-sso.umd.js.map +1 -1
- package/docs/WWebSso.html +1 -1
- package/docs/global.html +1 -1
- package/docs/index.html +1 -1
- package/docs/server_WWebSso.mjs.html +1 -1
- 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 +1 -1
- 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 +1 -1
- package/server/procCore.mjs +79 -7
- package/src/components/LayoutContentUsers.vue +22 -17
- package/src/usersRedirCheck.mjs +68 -0
package/docs/WWebSso.html
CHANGED
|
@@ -2492,7 +2492,7 @@ token發放/驗證/更新, 後台使用者/金鑰/IP管理與統計資訊, 以
|
|
|
2492
2492
|
<br class="clear">
|
|
2493
2493
|
|
|
2494
2494
|
<footer>
|
|
2495
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026
|
|
2495
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
2496
2496
|
</footer>
|
|
2497
2497
|
|
|
2498
2498
|
<script>prettyPrint();</script>
|
package/docs/global.html
CHANGED
|
@@ -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 Fri Oct 23 2026
|
|
3315
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
74
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
|
@@ -1457,7 +1457,7 @@ export default WWebSso
|
|
|
1457
1457
|
<br class="clear">
|
|
1458
1458
|
|
|
1459
1459
|
<footer>
|
|
1460
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026
|
|
1460
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
1461
1461
|
</footer>
|
|
1462
1462
|
|
|
1463
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 Fri Oct 23 2026
|
|
96
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
105
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
111
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
170
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
120
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
191
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
192
192
|
</footer>
|
|
193
193
|
|
|
194
194
|
<script>prettyPrint();</script>
|
|
@@ -211,7 +211,7 @@ export { fetchSsoMsg, convertUser, hasUrlParam }
|
|
|
211
211
|
<br class="clear">
|
|
212
212
|
|
|
213
213
|
<footer>
|
|
214
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026
|
|
214
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
215
215
|
</footer>
|
|
216
216
|
|
|
217
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 Fri Oct 23 2026
|
|
103
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
102
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
240
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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 Fri Oct 23 2026
|
|
153
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 17:28:25 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
package/server/procCore.mjs
CHANGED
|
@@ -1312,9 +1312,14 @@ function proc(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, pass
|
|
|
1312
1312
|
//'incorrect old password' reject 路徑, 自然不會多寄信.
|
|
1313
1313
|
return await kmx(`changeUserPassword:${userId}`, async () => {
|
|
1314
1314
|
|
|
1315
|
-
|
|
1316
|
-
//驗證所依之 DB 雜湊與寫入間不被其他寫入者(
|
|
1317
|
-
|
|
1315
|
+
//讀取、驗舊密碼、撤銷其他 session 與寫入於 [tokens, users] 表鎖內(S07; kmx 在外層、表鎖在內層): password 為伺服器專有欄位、isForceChangePw 為雙方欄位(系統直寫須取表鎖),
|
|
1316
|
+
//驗證所依之 DB 雜湊與寫入間不被其他寫入者(重設密碼、後台存檔、登入建立 session)插隊; 只改不增, 列已被刪則拒絕而不新增殘缺列 (X5)
|
|
1317
|
+
//改密碼後之 session 處置 (R-SESS-01, ADR-093): 保留發出此請求之目前 session(token), 撤銷該使用者其他所有 session 權杖; app 權杖不是 session, 不撤銷
|
|
1318
|
+
let { u, email } = await withTabLocks(['tokens', 'users'], async () => {
|
|
1319
|
+
|
|
1320
|
+
//鎖內再驗呼叫所用之權杖仍有效 (外審 sol#1): 鎖外之 checkToken 通過後、等鎖期間該 session 可能已被同使用者另一個改密碼請求或管理員重設撤銷,
|
|
1321
|
+
//被撤銷之權杖不得再改密碼(否則「保留發出請求之目前 session」不成立, 該請求還會撤銷對方保留之 session); checkToken 只讀不取鎖, 可在鎖內呼叫
|
|
1322
|
+
await checkToken(token)
|
|
1318
1323
|
|
|
1319
1324
|
//getGenUserByUserId (with password)
|
|
1320
1325
|
let u = await _getGenUserByKV('id', userId, { deletePassword: false })
|
|
@@ -1348,6 +1353,21 @@ function proc(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, pass
|
|
|
1348
1353
|
//hash newPassword
|
|
1349
1354
|
let passwordNew = hashPassword(newPassword, salt)
|
|
1350
1355
|
|
|
1356
|
+
//撤銷其他 session(先於密碼寫入, 驗證皆已通過): 撤銷失敗即 reject 且密碼不變, 不會出現「密碼已變更但舊 session 仍有效」(R-SESS-01)
|
|
1357
|
+
//(以布林旗標判失敗而非 errRevoke 是否為 null: reject 值可能為 null / undefined, 外審 sol#2)
|
|
1358
|
+
let revokeFailed = false
|
|
1359
|
+
let errRevoke = null
|
|
1360
|
+
let nRevoked = await revokeSessionTokensLocked(userId, token)
|
|
1361
|
+
.catch((err) => {
|
|
1362
|
+
revokeFailed = true
|
|
1363
|
+
errRevoke = err
|
|
1364
|
+
})
|
|
1365
|
+
if (revokeFailed) {
|
|
1366
|
+
srLog.error({ event: 'fun-changePassword-revokeSessions', key: 'userChangePasswordFail', userId, err: getErrorMessage(errRevoke) })
|
|
1367
|
+
return Promise.reject('userChangePasswordFail')
|
|
1368
|
+
}
|
|
1369
|
+
srLog.info({ event: 'fun-changePassword-revokeSessions', userId, nRevoked })
|
|
1370
|
+
|
|
1351
1371
|
//save (清 isForceChangePw='n', 變更密碼成功後即解除強制變更), 更新者記該使用者本人 (R-CONC-04)
|
|
1352
1372
|
//逐筆檢查(ADR-080): 寫入失敗即 reject, 不寄「密碼已變更」信
|
|
1353
1373
|
let errTemp = null
|
|
@@ -1468,9 +1488,10 @@ function proc(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, pass
|
|
|
1468
1488
|
return Promise.reject('adminResetPasswordAlreadyTriggered')
|
|
1469
1489
|
}
|
|
1470
1490
|
|
|
1471
|
-
//目標 user
|
|
1491
|
+
//目標 user 查找、撤銷其全部 session 與寫入於 [tokens, users] 表鎖內(S08; kmx 在外層、表鎖在內層): password 為伺服器專有欄位、isForceChangePw 為雙方欄位(系統直寫須取表鎖);
|
|
1472
1492
|
//只改不增, 目標已被刪則拒絕而不新增殘缺列 (X5); 更新者記操作管理員 (R-CONC-04)
|
|
1473
|
-
|
|
1493
|
+
//改密碼後之 session 處置 (R-SESS-01, ADR-093): 撤銷對方全部 session 權杖(不含 app 權杖); 呼叫者(管理員 / app 權杖)自己之權杖不在撤銷範圍(目標不得為自己)
|
|
1494
|
+
let { targetAccount, targetName, targetEmail, newPassword } = await withTabLocks(['tokens', 'users'], async () => {
|
|
1474
1495
|
|
|
1475
1496
|
//目標 user (含 password 拿到也不用, 只是確認存在)
|
|
1476
1497
|
let uTarget = null
|
|
@@ -1491,9 +1512,26 @@ function proc(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, pass
|
|
|
1491
1512
|
//產生隨機新密碼 (符合 passwordPolicy + account 限制)
|
|
1492
1513
|
let newPassword = genRandomPassword(passwordPolicy, targetAccount)
|
|
1493
1514
|
|
|
1494
|
-
//hash
|
|
1495
|
-
//逐筆檢查(ADR-080): 寫入失敗即 reject, 不記節流、不寄出未生效之新密碼
|
|
1515
|
+
//hash
|
|
1496
1516
|
let hashed = hashPassword(newPassword, salt)
|
|
1517
|
+
|
|
1518
|
+
//撤銷對方全部 session(先於密碼寫入): 撤銷失敗即 reject 且密碼不變, 不會出現「密碼已重設但舊 session 仍有效」(R-SESS-01)
|
|
1519
|
+
//(以布林旗標判失敗而非 errRevoke 是否為 null: reject 值可能為 null / undefined, 外審 sol#2)
|
|
1520
|
+
let revokeFailed = false
|
|
1521
|
+
let errRevoke = null
|
|
1522
|
+
let nRevoked = await revokeSessionTokensLocked(targetUserId)
|
|
1523
|
+
.catch((err) => {
|
|
1524
|
+
revokeFailed = true
|
|
1525
|
+
errRevoke = err
|
|
1526
|
+
})
|
|
1527
|
+
if (revokeFailed) {
|
|
1528
|
+
srLog.error({ event: 'fun-adminResetUserPassword-revokeSessions', key: 'adminResetPasswordFailed', token: maskToken(token), targetUserId, err: getErrorMessage(errRevoke) })
|
|
1529
|
+
return Promise.reject('adminResetPasswordFailed')
|
|
1530
|
+
}
|
|
1531
|
+
srLog.info({ event: 'fun-adminResetUserPassword-revokeSessions', targetUserId, nRevoked })
|
|
1532
|
+
|
|
1533
|
+
//save (含 isForceChangePw='y')
|
|
1534
|
+
//逐筆檢查(ADR-080): 寫入失敗即 reject, 不記節流、不寄出未生效之新密碼
|
|
1497
1535
|
let errTemp = null
|
|
1498
1536
|
await procOrm(operatorId, 'users', 'save', [{
|
|
1499
1537
|
id: targetUserId,
|
|
@@ -1595,6 +1633,40 @@ function proc(woItems, procOrm, { srLog, srEmail, salt, minExpired, kpLang, pass
|
|
|
1595
1633
|
}
|
|
1596
1634
|
|
|
1597
1635
|
|
|
1636
|
+
//revokeSessionTokensLocked: 改密碼後之 session 處置 (R-SESS-01, ADR-093), 須於 [tokens, users] 表鎖內呼叫(與密碼寫入同一組鎖, 不可重入).
|
|
1637
|
+
//只撤銷 userId 名下之 session 權杖(isApp !== 'y', 即使用者登入所發); app 權杖(isApp === 'y')不是 session, 其 userId 為擁有者, 不撤銷
|
|
1638
|
+
//(與 revokeTokensByUserIdLocked 之刪除 / 停用 / 封鎖情境不同: 該情境是使用者本身失效, 名下一切權杖皆撤).
|
|
1639
|
+
//exceptToken: 發出請求之目前 session 權杖字串, 該筆保留(使用者自行改密碼); 管理員重設不給, 全撤.
|
|
1640
|
+
//逐筆檢查(ormWrite), 單筆失敗不中斷其餘筆, 有失敗即 reject(含失敗筆主鍵), resolve 回傳撤銷筆數; 冪等(無可撤者回 0)
|
|
1641
|
+
let revokeSessionTokensLocked = async (userId, exceptToken = '') => {
|
|
1642
|
+
|
|
1643
|
+
//check
|
|
1644
|
+
if (!isestr(userId)) {
|
|
1645
|
+
return Promise.reject(`invalid userId`)
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
//ts
|
|
1649
|
+
let ts = await woItems.tokens.select({ userId })
|
|
1650
|
+
|
|
1651
|
+
//tsDel, session 權杖且非目前 session
|
|
1652
|
+
let tsDel = filter(ts, (t) => {
|
|
1653
|
+
return get(t, 'isApp', '') !== 'y' && !(isestr(exceptToken) && get(t, 'token', '') === exceptToken)
|
|
1654
|
+
})
|
|
1655
|
+
|
|
1656
|
+
//check
|
|
1657
|
+
if (size(tsDel) === 0) {
|
|
1658
|
+
return 0
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
//del
|
|
1662
|
+
await ormWrite(woItems.tokens, 'del', map(tsDel, (t) => {
|
|
1663
|
+
return { id: t.id }
|
|
1664
|
+
}))
|
|
1665
|
+
|
|
1666
|
+
return size(tsDel)
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
|
|
1598
1670
|
//updateTabItems
|
|
1599
1671
|
//後台清單整表存檔(users / tokens / ips), 多人同時編輯之寫入契約 (原則 10; 規格 dspec/多人同時編輯之寫入契約.md; 規則帳本 rulebook_concurrency R-CONC-01~05):
|
|
1600
1672
|
//- 讀取、比對、驗證依賴庫內之項、diff、寫入、撤銷 token、清快取全程在該表表鎖內(users 為 [tokens, users], 因寫入後同鎖撤銷 token); 讀取為鎖內新鮮讀取(無 ORM 快取).
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
</template>
|
|
252
252
|
<!-- redir -->
|
|
253
253
|
<template v-else-if="props.key === 'redir'">
|
|
254
|
-
<span v-if="
|
|
254
|
+
<span v-if="cellRedirErr($ui.gv(props.row, 'id'))" :title="cellRedirErr($ui.gv(props.row, 'id'))">
|
|
255
255
|
<img style="padding-right:4px; vertical-align:sub; width:16px; height:16px;" :src="$ui.getIcon('warning')" />
|
|
256
256
|
<span style="color:#F57C00;">{{ $ui.cstr(props.value) }}</span>
|
|
257
257
|
</span>
|
|
@@ -424,6 +424,7 @@ import WAggridVue from 'w-aggrid-vue/src/components/WAggridVue.vue'
|
|
|
424
424
|
import WTimeminute from 'w-component-vue/src/components/WTimeminute.vue'
|
|
425
425
|
import WButtonChip from 'w-component-vue/src/components/WButtonChip.vue'
|
|
426
426
|
import { baselineOfUsers } from '../baselineOf.mjs'
|
|
427
|
+
import { redirOrigOfUsers, redirErrIdsOfUsers } from '../usersRedirCheck.mjs'
|
|
427
428
|
|
|
428
429
|
|
|
429
430
|
export default {
|
|
@@ -460,6 +461,7 @@ export default {
|
|
|
460
461
|
|
|
461
462
|
firstLoading: true,
|
|
462
463
|
baseline: null, //載入時(及存檔成功後重新讀取時)畫面所見之列的有序基準 [[id, 列摘要]], 存檔時送後端比對(多人同時編輯, 先動先贏); null 為未載入或算不出, 送出時被後端拒收
|
|
464
|
+
redirOrig: null, //與 baseline 同一快照各列之 redir 原值 Map(id → redir; 用 Map 以免被 Vue 響應式處理覆蓋 `__ob__` 等鍵), 供判定「載入時即為空之既有列(如自助註冊者)」不擋存檔 (R-VAL-02); null 為算不出(此時 baseline 亦為 null, 後端無基準一律拒收), 只檢查新增列
|
|
463
465
|
errMsg: '', //清單載入失敗之訊息(getDataError), 有值時以訊息取代表格
|
|
464
466
|
firstSetting: true,
|
|
465
467
|
systemProcing: false, //程式端載入/重載清單資料期間為true, 用於排除非使用者操作之rowsChange
|
|
@@ -576,6 +578,7 @@ export default {
|
|
|
576
578
|
.then((res) => {
|
|
577
579
|
// console.log(res)
|
|
578
580
|
vo.baseline = baselineOfUsers(res) //對畫面所見之同一快照自算基準(須在排序、變動之前; buildBaseline 不改輸入)
|
|
581
|
+
vo.redirOrig = redirOrigOfUsers(res)
|
|
579
582
|
res = sortBy(res, 'order')
|
|
580
583
|
vo.users = res
|
|
581
584
|
vo.markDataReload() //程式端寫入資料, 其後續 rowsChange 非使用者變更
|
|
@@ -747,29 +750,22 @@ export default {
|
|
|
747
750
|
return kpErr
|
|
748
751
|
},
|
|
749
752
|
|
|
753
|
+
//redir(登入後轉址)檢查, 鍵為列主鍵 id (R-VAL-02, ADR-094): 只攔下管理員本次新增或由非空改為空之列;
|
|
754
|
+
//載入時即為空之既有列(如自助註冊者)不擋存檔、不標警示——一列之既有資料問題不得擋住整份清單之存檔
|
|
750
755
|
errItemsByRedir: function() {
|
|
751
756
|
let vo = this
|
|
752
757
|
|
|
753
758
|
//rows
|
|
754
759
|
let rows = get(vo, 'opt.rows', [])
|
|
755
760
|
|
|
761
|
+
//ids
|
|
762
|
+
let ids = redirErrIdsOfUsers(rows, vo.redirOrig)
|
|
763
|
+
|
|
756
764
|
//kpErr
|
|
757
765
|
let kpErr = Object.create(null)
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
let redir = get(v, 'redir', '')
|
|
762
|
-
|
|
763
|
-
//check
|
|
764
|
-
if (!isestr(redir)) {
|
|
765
|
-
|
|
766
|
-
//kpErr
|
|
767
|
-
kpErr[redir] = vo.$t(`userRedirEmpty`)
|
|
768
|
-
|
|
769
|
-
return true //跳出換下一個
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
})
|
|
766
|
+
for (let id in ids) {
|
|
767
|
+
kpErr[id] = vo.$t(`userRedirEmpty`)
|
|
768
|
+
}
|
|
773
769
|
|
|
774
770
|
return kpErr
|
|
775
771
|
},
|
|
@@ -929,7 +925,6 @@ export default {
|
|
|
929
925
|
let errMaps = {
|
|
930
926
|
'account': vo.errItemsByAccount,
|
|
931
927
|
'email': vo.errItemsByEmail,
|
|
932
|
-
'redir': vo.errItemsByRedir,
|
|
933
928
|
}
|
|
934
929
|
//鍵為使用者輸入之 account / email / redir 值: 自有鍵查值, 不把輸入值當 lodash 路徑 (含 . 或 [ 者會被解析為路徑而取到他列之錯誤文字; R-KEY-01)
|
|
935
930
|
let kp = errMaps[field]
|
|
@@ -937,6 +932,14 @@ export default {
|
|
|
937
932
|
return isestr(err) ? err : ''
|
|
938
933
|
},
|
|
939
934
|
|
|
935
|
+
//redir 欄位錯誤查詢, 鍵為 row.id (空值不能當鍵: 否則所有 redir 為空之儲存格會同時被標警示, 含載入時即為空而不擋存檔之既有列)
|
|
936
|
+
cellRedirErr: function(id) {
|
|
937
|
+
let vo = this
|
|
938
|
+
let kp = vo.errItemsByRedir
|
|
939
|
+
let err = (kp !== null && typeof kp === 'object' && Object.prototype.hasOwnProperty.call(kp, id)) ? kp[id] : ''
|
|
940
|
+
return isestr(err) ? err : ''
|
|
941
|
+
},
|
|
942
|
+
|
|
940
943
|
//新增列密碼欄位錯誤查詢, 鍵為 row.id (其他欄位以 value 當鍵, 但 password 不適合做鍵)
|
|
941
944
|
cellPasswordErr: function(id) {
|
|
942
945
|
let vo = this
|
|
@@ -1577,6 +1580,7 @@ export default {
|
|
|
1577
1580
|
await vo.$fapi.getUsersList(token)
|
|
1578
1581
|
.then((res) => {
|
|
1579
1582
|
vo.baseline = baselineOfUsers(res)
|
|
1583
|
+
vo.redirOrig = redirOrigOfUsers(res)
|
|
1580
1584
|
res = sortBy(res, 'order')
|
|
1581
1585
|
vo.users = res
|
|
1582
1586
|
vo.markDataReload() //程式端寫入資料, 其後續 rowsChange 非使用者變更
|
|
@@ -1584,6 +1588,7 @@ export default {
|
|
|
1584
1588
|
.catch((err) => {
|
|
1585
1589
|
console.log('refresh getUsersList catch', err)
|
|
1586
1590
|
vo.baseline = null //重新讀取失敗: 舊基準已過期, 不沿用; 其後再存檔被後端拒收並提示重新載入(不降級為不帶基準)
|
|
1591
|
+
vo.redirOrig = null //同上, redir 比對基準亦不沿用(null 時只檢查新增列; 後端無基準之存檔一律拒收並提示重新載入)
|
|
1587
1592
|
})
|
|
1588
1593
|
|
|
1589
1594
|
//isModified
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//後台使用者清單之 redir(登入後轉址)檢查 (R-VAL-02, ADR-094; 第 116 項):
|
|
6
|
+
//- redir 不可由管理員在本次編輯中設為空: 新增列、或既有列之 redir 由非空改為空者攔下(提示「尚未給予有效登入後轉址」, 該列 redir 格顯示警示)
|
|
7
|
+
//- 載入時即為空的既有列不擋存檔、不標警示: 自助註冊者即如此(註冊流程不收 redir, 預設空字串; 使用者於 view=user / view=backstage 登入不需 redir,
|
|
8
|
+
// view=login 登入時以「無有效轉址」提示, 見 spec/流程_使用者一般登入.md E2E-016、spec/流程_使用者自動登入.md E2E-005); 一列之既有資料問題不得擋住整份清單之存檔
|
|
9
|
+
//- 比對基準 redirOrig 為 Map(列主鍵 → 載入(及存檔成功後重新讀取)時畫面所見之 redir 原值): 用 Map 而非物件, 因基準存於 Vue data,
|
|
10
|
+
// Vue 2 會把純物件轉為響應式並以 `__ob__` 鍵掛 Observer(列主鍵恰為 `__ob__` 時會覆蓋其原值), Map 不被響應式處理, 且任何主鍵字串(`__proto__` 等)皆無原型鍵衝突(R-KEY-01)
|
|
11
|
+
//- 基準算不出(null, 如存檔成功後重新讀取失敗)時: 只檢查新增列; 既有列不檢查——此時 baseline 亦為 null, 後端對無基準之存檔一律拒收(saveBaselineFormat, 提示重新載入),
|
|
12
|
+
// 不會有任何列被寫入, 不必在前端對既有列先擋(先擋反而使使用者先看到不相干之 redir 訊息而到不了重新載入提示); 列不在基準內且非新增者(基準有效時)視為管理員所設, 嚴格檢查
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
//redirOrigOfUsers: rows 為 getUsersList 之回傳(未排序、未變動); 回傳 Map(id → redir 原值; 非字串者記為空字串)
|
|
16
|
+
function redirOrigOfUsers(rows) {
|
|
17
|
+
let mp = new Map()
|
|
18
|
+
for (let r of Array.isArray(rows) ? rows : []) {
|
|
19
|
+
let id = get(r, 'id', '')
|
|
20
|
+
if (typeof id !== 'string') {
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
let redir = get(r, 'redir', '')
|
|
24
|
+
mp.set(id, typeof redir === 'string' ? redir : '')
|
|
25
|
+
}
|
|
26
|
+
return mp
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
//redirErrIdsOfUsers: rows 為畫面當前之列(含 _isNew 標記), redirOrig 為 redirOrigOfUsers 之結果或 null; 回傳 { [id]: true }(無原型物件)為不合規則之列
|
|
31
|
+
function redirErrIdsOfUsers(rows, redirOrig) {
|
|
32
|
+
let kp = Object.create(null)
|
|
33
|
+
let known = redirOrig instanceof Map
|
|
34
|
+
for (let r of Array.isArray(rows) ? rows : []) {
|
|
35
|
+
|
|
36
|
+
//redir 非空即合規
|
|
37
|
+
if (isestr(get(r, 'redir', ''))) {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//id
|
|
42
|
+
let id = get(r, 'id', '')
|
|
43
|
+
|
|
44
|
+
//新增列: 一律嚴格(標記優先於基準, 即使主鍵碰巧與基準內某列相同)
|
|
45
|
+
let isNew = get(r, '_isNew') === true
|
|
46
|
+
|
|
47
|
+
if (!isNew) {
|
|
48
|
+
|
|
49
|
+
//基準算不出: 既有列不檢查(後端無基準一律拒收, 見檔頭)
|
|
50
|
+
if (!known) {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//載入時即為空之既有列: 既有資料之狀態, 不屬本次編輯所致
|
|
55
|
+
if (typeof id === 'string' && redirOrig.has(id) && !isestr(redirOrig.get(id))) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
kp[typeof id === 'string' ? id : String(id)] = true
|
|
62
|
+
}
|
|
63
|
+
return kp
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
export { redirOrigOfUsers, redirErrIdsOfUsers }
|
|
68
|
+
export default { redirOrigOfUsers, redirErrIdsOfUsers }
|