w-web-perm 1.0.48 → 1.0.50

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 (111) hide show
  1. package/.github/workflows/ci-test.yml +2 -2
  2. package/README.md +5 -5
  3. package/dist/css/app.8781355a.css +1 -0
  4. package/dist/css/{chunk-vendors.f149e124.css → chunk-vendors.58917286.css} +1 -1
  5. package/dist/index.tmp +2 -2
  6. package/dist/js/app-legacy.b2754bb1.js +1 -0
  7. package/dist/js/app.b2754bb1.js +1 -0
  8. package/dist/js/chunk-vendors-legacy.c14a1ed1.js +21 -0
  9. package/dist/js/chunk-vendors.c14a1ed1.js +21 -0
  10. package/dist/w-web-perm.umd.js +2 -2
  11. package/dist/w-web-perm.umd.js.map +1 -1
  12. package/docs/WWebPerm.html +3 -3
  13. package/docs/WWebPerm.mjs.html +162 -210
  14. package/docs/index.html +1 -1
  15. package/logs/2026-06-19T16.log +191 -0
  16. package/logs/2026-06-19T23.log +0 -0
  17. package/logs/2026-06-20T00.log +207 -0
  18. package/logs/2026-06-20T11.log +221 -0
  19. package/logs/2026-06-20T12.log +146 -0
  20. package/logs/2026-06-20T13.log +134 -0
  21. package/logs/2026-06-20T14.log +296 -0
  22. package/logs/2026-06-20T15.log +225 -0
  23. package/logs/2026-06-20T16.log +174 -0
  24. package/logs/2026-06-21T16.log +110 -0
  25. package/logs/2026-06-21T17.log +301 -0
  26. package/logs/2026-06-21T18.log +238 -0
  27. package/logs/2026-06-21T19.log +141 -0
  28. package/logs/2026-06-21T20.log +99 -0
  29. package/logs/2026-06-21T21.log +143 -0
  30. package/logs/2026-06-21T23.log +26 -0
  31. package/logs/2026-06-22T00.log +33 -0
  32. package/logs/2026-06-22T08.log +235 -0
  33. package/logs/2026-06-22T09.log +246 -0
  34. package/logs/2026-06-22T10.log +301 -0
  35. package/logs/2026-06-22T11.log +140 -0
  36. package/logs/2026-06-22T12.log +22 -0
  37. package/logs/2026-06-22T14.log +44 -0
  38. package/logs/2026-06-22T23.log +951 -0
  39. package/logs/2026-06-23T00.log +263 -0
  40. package/logs/2026-06-23T08.log +611 -0
  41. package/logs/2026-06-30T21.log +3609 -0
  42. package/logs/2026-06-30T22.log +3682 -0
  43. package/logs/2026-06-30T23.log +592 -0
  44. package/logs/2026-07-01T12.log +0 -0
  45. package/logs/2026-07-09T22.log +2220 -0
  46. package/logs/2026-07-09T23.log +1140 -0
  47. package/logs/2026-07-10T12.log +6 -0
  48. package/logs/2026-07-10T13.log +351 -0
  49. package/logs/2026-07-10T16.log +1272 -0
  50. package/logs/2026-07-10T19.log +6 -0
  51. package/logs/2026-07-10T20.log +1883 -0
  52. package/logs/2026-07-10T21.log +3505 -0
  53. package/logs/2026-07-10T22.log +3395 -0
  54. package/logs/2026-07-11T00.log +1338 -0
  55. package/logs/2026-07-11T01.log +2492 -0
  56. package/logs/2026-07-11T17.log +19 -0
  57. package/package.json +23 -16
  58. package/public/index.html +2 -2
  59. package/server/WWebPerm.mjs +161 -209
  60. package/server/procCore.mjs +187 -0
  61. package/server/procLang.mjs +276 -60
  62. package/server/procStaInfor.mjs +128 -0
  63. package/server/provideTabs.mjs +1 -1
  64. package/server/srLog.mjs +30 -0
  65. package/server/staLogs/filterVpfsByWindow.mjs +27 -0
  66. package/server/staLogs/staEvent.callWorker.mjs +46 -0
  67. package/server/staLogs/staEvent.mjs +118 -0
  68. package/server/staLogs/staEvent.shellWorker.mjs +19 -0
  69. package/server/staLogs/staEventTable.mjs +102 -0
  70. package/settings.json +43 -0
  71. package/src/components/Common/CheckYes.vue +18 -1
  72. package/src/components/Layout.vue +1 -1
  73. package/src/components/LayoutContent.vue +13 -3
  74. package/src/components/LayoutContentGrups.vue +32 -25
  75. package/src/components/LayoutContentPemis.vue +32 -25
  76. package/src/components/LayoutContentStaInfor.vue +534 -0
  77. package/src/components/LayoutContentTargets.vue +35 -28
  78. package/src/components/LayoutContentUsers.vue +32 -25
  79. package/src/components/LayoutState.vue +5 -0
  80. package/src/components/VeCgrups.vue +19 -9
  81. package/src/components/VeCpemis.vue +19 -9
  82. package/src/components/VeCrules.vue +19 -9
  83. package/src/components/VeGrupBlngUsers.vue +35 -29
  84. package/src/components/VePemiBlngGrups.vue +35 -29
  85. package/src/getPerm.mjs +7 -17
  86. package/src/getPermUserInfor.mjs +8 -18
  87. package/src/main.js +4 -9
  88. package/src/perm.mjs +3 -4
  89. package/src/plugins/mShare.mjs +8 -13
  90. package/src/plugins/mUI.mjs +7 -44
  91. package/src/store/mutations.mjs +0 -10
  92. package/src/store/types.mjs +0 -2
  93. package/srv.mjs +76 -101
  94. package/tableTags.json +1 -1
  95. package/z/344/277/256/346/255/243/350/246/217/345/212/203/346/233/270.md +450 -0
  96. package/db/wperm/grups/data.mdb +0 -0
  97. package/db/wperm/grups/lock.mdb +0 -0
  98. package/db/wperm/pemis/data.mdb +0 -0
  99. package/db/wperm/pemis/lock.mdb +0 -0
  100. package/db/wperm/targets/data.mdb +0 -0
  101. package/db/wperm/targets/lock.mdb +0 -0
  102. package/db/wperm/users/data.mdb +0 -0
  103. package/db/wperm/users/lock.mdb +0 -0
  104. package/dist/css/app.30e81bfe.css +0 -1
  105. package/dist/js/app-legacy.3324392e.js +0 -1
  106. package/dist/js/app.3324392e.js +0 -1
  107. package/dist/js/chunk-vendors-legacy.e8703728.js +0 -21
  108. package/dist/js/chunk-vendors.e8703728.js +0 -21
  109. package/src/plugins/mDataSelectorSchema.mjs +0 -30
  110. package/tableTags-web-perm.json +0 -1
  111. package/test/getUserRules.test.mjs +0 -899
@@ -48,10 +48,8 @@
48
48
  <pre class="prettyprint source linenums"><code>import path from 'path'
49
49
  import fs from 'fs'
50
50
  import get from 'lodash-es/get.js'
51
- import each from 'lodash-es/each.js'
52
51
  import map from 'lodash-es/map.js'
53
52
  import keys from 'lodash-es/keys.js'
54
- import size from 'lodash-es/size.js'
55
53
  import join from 'lodash-es/join.js'
56
54
  import iseobj from 'wsemi/src/iseobj.mjs'
57
55
  import isestr from 'wsemi/src/isestr.mjs'
@@ -70,15 +68,16 @@ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
70
68
  import fsIsFile from 'wsemi/src/fsIsFile.mjs'
71
69
  import replace from 'wsemi/src/replace.mjs'
72
70
  import strdelleft from 'wsemi/src/strdelleft.mjs'
73
- import haskey from 'wsemi/src/haskey.mjs'
74
- import arrHas from 'wsemi/src/arrHas.mjs'
75
- import ltdtDiffByKey from 'wsemi/src/ltdtDiffByKey.mjs'
76
71
  import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
72
+ import pmKeyMutex from 'wsemi/src/pmKeyMutex.mjs'
77
73
  import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
78
74
  import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
79
75
  import ds from '../src/schema/index.mjs'
80
76
  import { getUserRules } from '../src/plugins/mShare.mjs'
77
+ import procCore from './procCore.mjs'
81
78
  import procLang from './procLang.mjs'
79
+ import srLogInit from './srLog.mjs'
80
+ import procStaInfor from './procStaInfor.mjs'
82
81
 
83
82
 
84
83
  /**
@@ -88,7 +87,7 @@ import procLang from './procLang.mjs'
88
87
  * @param {Function} WOrm 輸入資料庫ORM函數
89
88
  * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
90
89
  * @param {String} db 輸入資料庫名稱字串
91
- * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
90
+ * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token 宣稱之值複寫
92
91
  * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
93
92
  * @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
94
93
  * @param {Object} [opt={}] 輸入設定物件,預設{}
@@ -315,6 +314,10 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
315
314
  let kpLang = procLang({ kpLangExt, webName, webDescription })
316
315
 
317
316
 
317
+ //srLog
318
+ let srLog = srLogInit(opt)
319
+
320
+
318
321
  //WServOrm
319
322
  let optWServOrm = {
320
323
  useCheckUser,
@@ -327,12 +330,35 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
327
330
  }
328
331
  catch (err) {
329
332
  console.log(err)
333
+ srLog.error({ event: 'WServOrm-init-error', msg: (err &amp;&amp; err.message) || 'WServOrmInitFailed' })
334
+ throw err //fail-fast: ORM 初始化失敗直接終止啟動, 不以 wp={} 繼續組裝出「port 起得來但 woItems/procOrm 為 undefined、CRUD 全 crash」的半死伺服器
330
335
  }
331
336
  let { woItems, procOrm } = wp
332
337
 
333
338
 
339
+ //pmKeyMutex: per-key in-memory mutex, 同 key 序列化、不同 key 並行.
340
+ //perm 為「整表批次 diff」寫入, key 粒度為 {op}:{keyTable}, 防同表並行 race (lost update).
341
+ let kmx = pmKeyMutex()
342
+
343
+
344
+ //procCore: 整表批次 diff 寫入邏輯 (updateTabItems + updateTargets/Pemis/Grups/Users)
345
+ let pc = procCore(woItems, procOrm, { srLog, kmx })
346
+
347
+
348
+ //procStaInfor: 統計資訊, fdLog 對齊 srLog 使用同一目錄(讀 settings.logFd, fallback 邏輯同 srLog.mjs); mock 由 settings.staEventMock 注入(e2e 統計圖穩定用)
349
+ let logFd = get(opt, 'logFd', '')
350
+ if (!isestr(logFd)) {
351
+ logFd = './_logs'
352
+ }
353
+ let psi = procStaInfor({ fdLog: logFd, mock: get(opt, 'staEventMock', false) })
354
+
355
+
334
356
  //getWebInfor
335
357
  let getWebInfor = (userId) => {
358
+
359
+ //info
360
+ srLog.info({ event: 'getWebInfor-success', userId })
361
+
336
362
  return {
337
363
 
338
364
  // webName, //已併入kpLang
@@ -395,162 +421,32 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
395
421
  // }
396
422
 
397
423
 
398
- //updateTabItems
399
- let updateTabItems = async (keyTable, userId, rows, keyDetect) => {
400
- // console.log('updateTabItems', keyTable, userId, rows.length, keyDetect)
401
-
402
- //ltdtmapping
403
- rows = ltdtmapping(rows, ds[keyTable].keys)
404
- // console.log('ltdtmapping rows', rows)
405
-
406
- //重給order
407
- rows = map(rows, (r, k) => {
408
- r.order = k + 1
409
- return r
410
- })
411
-
412
- //ckKey
413
- let ckKey = (rows, key) => {
414
- let err = null
415
-
416
- //check
417
- let kp = {}
418
- each(rows, (v, k) => {
419
-
420
- //value
421
- let value = get(v, key, '')
422
-
423
- //check
424
- if (!isestr(value)) {
425
- err = `rows[${k}].${key} is not an effective string`
426
- return false //跳出
427
- }
428
-
429
- //check
430
- if (haskey(kp, value)) {
431
- err = `rows[${k}].${key}[${value}] is duplicate`
432
- return false //跳出
433
- }
434
-
435
- //kp
436
- kp[value] = true
437
-
438
- })
439
-
440
- return err
441
- }
442
-
443
- //偵測未給予或重複
444
- let err = null
445
- if (true) {
446
- if (arrHas(keyTable, ['targets'])) {
447
- err = ckKey(rows, 'id')
448
- if (err !== null) {
449
- return Promise.reject(err)
450
- }
451
- }
452
- if (arrHas(keyTable, ['pemis', 'grups'])) { //users可重複name故不列入
453
- err = ckKey(rows, 'name')
454
- if (err !== null) {
455
- return Promise.reject(err)
456
- }
457
- }
458
- if (arrHas(keyTable, ['users'])) {
459
- err = ckKey(rows, 'email')
460
- if (err !== null) {
461
- return Promise.reject(err)
462
- }
463
- }
464
- }
465
-
466
- //ltdtDiffByKey
467
- let ltdtOld = await woItems[keyTable].select()
468
- let ltdtNew = rows
469
- let r = ltdtDiffByKey(ltdtOld, ltdtNew, keyDetect)
470
- // console.log('ltdtDiffByKey r', r)
471
-
472
- //del
473
- if (size(r.del) > 0) {
474
- await procOrm(userId, keyTable, 'del', r.del) //須使用procOrm才有辦法自動給予相關欄位
475
- // .catch((err) => {
476
- // console.log('woItems[keyTable].del err', err)
477
- // })
478
- }
479
-
480
- //add
481
- if (size(r.add) > 0) {
482
- await procOrm(userId, keyTable, 'insert', r.add) //須使用procOrm才有辦法自動給予相關欄位
483
- // .catch((err) => {
484
- // console.log('woItems[keyTable].insert err', err)
485
- // })
486
- }
487
-
488
- //diff
489
- if (size(r.diff) > 0) {
490
- await procOrm(userId, keyTable, 'save', r.diff) //須使用procOrm才有辦法自動給予相關欄位
491
- // .catch((err) => {
492
- // console.log('woItems[keyTable].save err', err)
493
- // })
494
- }
495
-
496
- return ltdtNew
497
- }
498
-
499
-
500
- //updateTargets
501
- let updateTargets = async (userId, rows) => {
502
- rows = await updateTabItems('targets', userId, rows, 'id')
503
- return rows
504
- }
505
-
506
-
507
- //updatePemis
508
- let updatePemis = async (userId, rows) => {
509
- rows = await updateTabItems('pemis', userId, rows, 'name')
510
- return rows
511
- }
512
-
513
-
514
- //updateGrups
515
- let updateGrups = async (userId, rows) => {
516
- rows = await updateTabItems('grups', userId, rows, 'name')
517
- return rows
518
- }
519
-
520
-
521
- //updateUsers
522
- let updateUsers = async (userId, rows) => {
523
- rows = await updateTabItems('users', userId, rows, 'id')
524
- return rows
525
- }
526
-
527
-
528
424
  //checkUser
529
425
  let checkUser = async(user) => {
530
426
  let id = get(user, 'id', '')
531
427
  if (!isestr(id)) {
532
428
  console.log('user', user)
533
- console.log('can not get the userId')
534
- return Promise.reject(`can not get the userId`)
429
+ srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserId' })
430
+ return Promise.reject(`cannotGetUserId`)
535
431
  }
536
432
  let email = get(user, 'email', '')
537
433
  if (!isestr(email)) {
538
434
  console.log('user', user)
539
- console.log('can not get the email of user')
540
- return Promise.reject(`can not get the email of user`)
435
+ srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserEmail' })
436
+ return Promise.reject(`cannotGetUserEmail`)
541
437
  }
542
438
  let name = get(user, 'name', '')
543
439
  if (!isestr(name)) {
544
440
  console.log('user', user)
545
- console.log('can not get userName')
546
- return Promise.reject(`can not get userName`)
441
+ srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserName' })
442
+ return Promise.reject(`cannotGetUserName`)
547
443
  }
548
444
  let isAdmin = get(user, 'isAdmin', '')
549
445
  if (isAdmin !== 'y' &amp;&amp; isAdmin !== 'n') {
550
446
  console.log('user', user)
551
447
  console.log('user.isAdmin is not y or n', user.isAdmin)
552
- console.log('can not get the role of user')
553
- return Promise.reject(`can not get the role of user`)
448
+ srLog.error({ event: 'checkUser-error', msg: 'cannotGetUserRole' })
449
+ return Promise.reject(`cannotGetUserRole`)
554
450
  }
555
451
  return true
556
452
  }
@@ -573,8 +469,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
573
469
  //check
574
470
  if (!iseobj(userSelf)) {
575
471
  console.log(`token`, token)
576
- console.log(`can not find the user from token`)
577
- return Promise.reject(`can not find the user from token`)
472
+ srLog.error({ event: 'getTokenUser-error', msg: 'cannotFindUserFromToken' })
473
+ return Promise.reject(`cannotFindUserFromToken`)
578
474
  }
579
475
 
580
476
  //check userSelf
@@ -588,8 +484,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
588
484
  if (!isestr(vSelf)) {
589
485
  console.log('userSelf', userSelf)
590
486
  console.log('mappingBy', mappingBy)
591
- console.log('can not get the prop of user by mappingBy')
592
- return Promise.reject(`can not get the prop of user by mappingBy`)
487
+ srLog.error({ event: 'getTokenUser-error', msg: 'cannotGetUserProp', mappingBy })
488
+ return Promise.reject(`cannotGetUserProp`)
593
489
  }
594
490
 
595
491
  //userFind
@@ -601,17 +497,14 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
601
497
  if (!iseobj(userFind)) {
602
498
  console.log('userSelf', userSelf)
603
499
  console.log('mappingBy', mappingBy)
604
- console.log('can not get the user from perm')
605
- return Promise.reject(`can not get the user from perm`)
500
+ srLog.error({ event: 'getTokenUser-error', msg: 'cannotGetUserFromPerm', mappingBy })
501
+ return Promise.reject(`cannotGetUserFromPerm`)
606
502
  }
607
503
 
608
- //複寫isAdmin
609
- let isAdminSrc = get(userSelf, 'isAdmin', '')
610
- let isAdminSelf = get(userFind, 'isAdmin', '')
611
- if (isAdminSrc !== isAdminSelf) {
612
- userFind.isAdmin = isAdminSelf
613
- }
614
- // console.log('userFind(isAdmin)', userFind)
504
+ //isAdmin 權威為 perm users 表之欄位值, 不以 token(getUserByToken)宣稱之 isAdmin 複寫。
505
+ //(2026-07-11 稽核 A-C1 裁決(b): 原此處有一段恆為 no-op 之「複寫isAdmin」死碼
506
+ // (userFind.isAdmin = isAdminSelf, 把自己指派給自己), 與 JSDoc 舊文「輸入'y'時會複寫」互斥;
507
+ // 依業主裁決採保守解: 刪除死碼、文件對齊實際行為, 不啟用 token→admin 提權路徑。)
615
508
 
616
509
  return userFind
617
510
  }
@@ -649,8 +542,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
649
542
  //check
650
543
  if (!b) {
651
544
  console.log('user', user)
652
- console.log(`user does not have permission`)
653
- return Promise.reject(`user does not have permission`)
545
+ srLog.error({ event: 'getAndVerifyClientUser-error', msg: 'userNoPermission', from })
546
+ return Promise.reject(`userNoPermission`)
654
547
  }
655
548
 
656
549
  return user
@@ -674,8 +567,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
674
567
  //check
675
568
  if (!iseobj(user)) {
676
569
  console.log(`token`, token)
677
- console.log(`can not find the user from token`)
678
- return Promise.reject(`can not find the user from token`)
570
+ srLog.error({ event: 'getAndVerifyAppUser-error', msg: 'cannotFindUserFromToken' })
571
+ return Promise.reject(`cannotFindUserFromToken`)
679
572
  }
680
573
 
681
574
  //verifyAppUser
@@ -687,8 +580,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
687
580
  //check
688
581
  if (!b) {
689
582
  console.log('user', user)
690
- console.log(`user does not have permission`)
691
- return Promise.reject(`user does not have permission`)
583
+ srLog.error({ event: 'getAndVerifyAppUser-error', msg: 'userNoPermission', from })
584
+ return Promise.reject(`userNoPermission`)
692
585
  }
693
586
 
694
587
  return user
@@ -709,8 +602,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
709
602
  //check
710
603
  if (!iseobj(payload)) {
711
604
  console.log('payload', payload)
712
- console.log('invalid payload in req')
713
- return Promise.reject(`invalid payload in req`)
605
+ srLog.error({ event: 'parsePayload-error', msg: 'invalidPayload' })
606
+ return Promise.reject(`invalidPayload`)
714
607
  }
715
608
 
716
609
  //from
@@ -719,8 +612,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
719
612
  //check
720
613
  if (!isestr(from)) {
721
614
  console.log('payload', payload)
722
- console.log('invalid from in payload')
723
- return Promise.reject(`invalid from in payload`)
615
+ srLog.error({ event: 'parsePayload-error', msg: 'invalidFromInPayload' })
616
+ return Promise.reject(`invalidFromInPayload`)
724
617
  }
725
618
 
726
619
  //rows
@@ -729,8 +622,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
729
622
  //check
730
623
  if (!isearr(rows)) {
731
624
  console.log('payload', payload)
732
- console.log(`invalid rows in payload`)
733
- return Promise.reject(`invalid rows in payload`)
625
+ srLog.error({ event: 'parsePayload-error', msg: 'invalidRowsInPayload' })
626
+ return Promise.reject(`invalidRowsInPayload`)
734
627
  }
735
628
 
736
629
  //inp
@@ -746,6 +639,9 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
746
639
  //syncAndReplaceTabs
747
640
  let syncAndReplaceTabs = async (userId, inp, keyTable) => {
748
641
 
642
+ //序列化同表之並行整表 delAll+insert: 防 lost update (同 keyTable 序列化, 不同表並行)
643
+ return await kmx('syncAndReplaceTabs:' + keyTable, async () => {
644
+
749
645
  //from
750
646
  let from = get(inp, 'from', '')
751
647
  // console.log('from', from)
@@ -753,8 +649,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
753
649
  //check
754
650
  if (!isestr(from)) {
755
651
  console.log('inp', inp)
756
- console.log(`invalid from`)
757
- return Promise.reject(`invalid from`)
652
+ srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidFrom', keyTable })
653
+ return Promise.reject(`invalidFrom`)
758
654
  }
759
655
 
760
656
  //rows
@@ -765,7 +661,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
765
661
  if (!isearr(rows)) {
766
662
  console.log('inp', inp)
767
663
  console.log('keyTable', keyTable)
768
- return Promise.reject(`invalid rows`)
664
+ srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', keyTable })
665
+ return Promise.reject(`invalidRows`)
769
666
  }
770
667
 
771
668
  //save from
@@ -788,6 +685,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
788
685
  // console.log('r', r)
789
686
 
790
687
  return r
688
+
689
+ })
791
690
  }
792
691
 
793
692
 
@@ -848,8 +747,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
848
747
  //check
849
748
  if (!iseobj(kur)) {
850
749
  console.log('userId', userId)
851
- console.log(`can not get rules of user`)
852
- return Promise.reject(`can not get rules of user`)
750
+ srLog.error({ event: 'getGenUserAndRulesByUserId-error', msg: 'cannotGetUserRules', userId })
751
+ return Promise.reject(`cannotGetUserRules`)
853
752
  }
854
753
 
855
754
  //grupsNames
@@ -923,8 +822,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
923
822
  console.log('req.query', get(req, 'query'))
924
823
  console.log('token', token)
925
824
  console.log('[API]getUserByToken/check token: invalid token')
926
- console.log(`token does not have permission`)
927
- return Promise.reject(`token does not have permission`)
825
+ srLog.error({ event: 'api/getUserByToken-error', msg: 'tokenNoPermission' })
826
+ return Promise.reject(`tokenNoPermission`)
928
827
  }
929
828
 
930
829
  //getAndVerifyClientUser
@@ -935,10 +834,13 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
935
834
  if (!iseobj(user)) {
936
835
  console.log('token', token)
937
836
  console.log('[API]getUserByToken/check user: invalid user')
938
- console.log(`token does not have permission`)
939
- return Promise.reject(`token does not have permission`)
837
+ srLog.error({ event: 'api/getUserByToken-error', msg: 'tokenNoPermission' })
838
+ return Promise.reject(`tokenNoPermission`)
940
839
  }
941
840
 
841
+ //info
842
+ srLog.info({ event: 'api/getUserByToken-success', userId: get(user, 'id', '') })
843
+
942
844
  return user
943
845
  }
944
846
 
@@ -970,8 +872,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
970
872
  console.log('req.query', get(req, 'query'))
971
873
  console.log('token', token)
972
874
  console.log('[API]getPerm/check token: invalid token')
973
- console.log(`token does not have permission`)
974
- return Promise.reject(`token does not have permission`)
875
+ srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
876
+ return Promise.reject(`tokenNoPermission`)
975
877
  }
976
878
 
977
879
  //getAndVerifyClientUser, 發起token的所屬使用者, 驗證僅對client使用者, 不得使用app使用者
@@ -982,8 +884,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
982
884
  if (!iseobj(userSelf)) {
983
885
  console.log('token', token)
984
886
  console.log('[API]getPerm/check userSelf: invalid userSelf')
985
- console.log(`token does not have permission`)
986
- return Promise.reject(`token does not have permission`)
887
+ srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
888
+ return Promise.reject(`tokenNoPermission`)
987
889
  }
988
890
 
989
891
  //userIdSelf, client使用者的userId
@@ -995,8 +897,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
995
897
  console.log('token', token)
996
898
  console.log('userSelf', userSelf)
997
899
  console.log('[API]getPerm/check userIdSelf: invalid userIdSelf')
998
- console.log(`token does not have permission`)
999
- return Promise.reject(`token does not have permission`)
900
+ srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
901
+ return Promise.reject(`tokenNoPermission`)
1000
902
  }
1001
903
 
1002
904
  //getGenUserAndRulesByUserId, 查找到的client使用者與權限
@@ -1009,10 +911,13 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1009
911
  console.log('userSelf', userSelf)
1010
912
  console.log('userIdSelf', userIdSelf)
1011
913
  console.log('[API]getPerm/check userWithRulesSelf: invalid userWithRulesSelf')
1012
- console.log(`token does not have permission`)
1013
- return Promise.reject(`token does not have permission`)
914
+ srLog.error({ event: 'api/getPerm-error', msg: 'tokenNoPermission' })
915
+ return Promise.reject(`tokenNoPermission`)
1014
916
  }
1015
917
 
918
+ //info
919
+ srLog.info({ event: 'api/getPerm-success', userId: userIdSelf })
920
+
1016
921
  return userWithRulesSelf
1017
922
  }
1018
923
 
@@ -1043,8 +948,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1043
948
  if (!isestr(tokenSelf)) {
1044
949
  console.log('req.query', get(req, 'query'))
1045
950
  console.log('[API]getPermUserInfor/check tokenSelf: invalid tokenSelf')
1046
- console.log(`token does not have permission`)
1047
- return Promise.reject(`token does not have permission`)
951
+ srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
952
+ return Promise.reject(`tokenNoPermission`)
1048
953
  }
1049
954
 
1050
955
  //getAndVerifyAppUser, 發起token的所屬使用者, 驗證須為app使用者
@@ -1056,8 +961,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1056
961
  console.log('req.query', get(req, 'query'))
1057
962
  console.log('tokenSelf', tokenSelf)
1058
963
  console.log('[API]getPermUserInfor/check userSelf: invalid userSelf')
1059
- console.log(`token does not have permission`)
1060
- return Promise.reject(`token does not have permission`)
964
+ srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
965
+ return Promise.reject(`tokenNoPermission`)
1061
966
  }
1062
967
 
1063
968
  //userIdFind, 欲查找使用者的userId
@@ -1069,8 +974,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1069
974
  console.log('req.query', get(req, 'query'))
1070
975
  console.log('tokenSelf', tokenSelf)
1071
976
  console.log('[API]getPermUserInfor/check userIdFind: invalid userIdFind')
1072
- console.log(`token does not have permission`)
1073
- return Promise.reject(`token does not have permission`)
977
+ srLog.error({ event: 'api/getPermUserInfor-error', msg: 'tokenNoPermission' })
978
+ return Promise.reject(`tokenNoPermission`)
1074
979
  }
1075
980
 
1076
981
  //getGenUserAndRulesByUserId, 查找到的使用者與權限
@@ -1081,10 +986,13 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1081
986
  if (!iseobj(userWithRulesFind)) {
1082
987
  console.log('userIdFind', userIdFind)
1083
988
  console.log('[API]getPermUserInfor/check userWithRulesFind: invalid userWithRulesFind')
1084
- console.log(`userId does not have permrules`)
1085
- return Promise.reject(`userId does not have permrules`)
989
+ srLog.error({ event: 'api/getPermUserInfor-error', msg: 'userIdNoPermrules' })
990
+ return Promise.reject(`userIdNoPermrules`)
1086
991
  }
1087
992
 
993
+ //info
994
+ srLog.info({ event: 'api/getPermUserInfor-success', userId: userIdFind })
995
+
1088
996
  return userWithRulesFind
1089
997
  }
1090
998
 
@@ -1116,8 +1024,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1116
1024
  console.log('req.query', get(req, 'query'))
1117
1025
  console.log('token', token)
1118
1026
  console.log('[API]syncAndReplaceTabs/check token: invalid token')
1119
- console.log(`token does not have permission`)
1120
- return Promise.reject(`token does not have permission`)
1027
+ srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
1028
+ return Promise.reject(`tokenNoPermission`)
1121
1029
  }
1122
1030
 
1123
1031
  //keyTable
@@ -1128,15 +1036,16 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1128
1036
  console.log('req.query', get(req, 'query'))
1129
1037
  console.log('keyTable', keyTable)
1130
1038
  console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
1131
- console.log(`token does not have permission`)
1132
- return Promise.reject(`token does not have permission`)
1039
+ srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
1040
+ return Promise.reject(`tokenNoPermission`)
1133
1041
  }
1134
1042
 
1135
1043
  //check keyTable
1136
1044
  let allowedTables = ['targets', 'pemis', 'grups', 'users']
1137
1045
  if (!allowedTables.includes(keyTable)) {
1138
1046
  console.log('[API]syncAndReplaceTabs/check keyTable: unexpected keyTable', keyTable)
1139
- return Promise.reject(`invalid keyTable: ${keyTable}`)
1047
+ srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'invalidKeyTable', keyTable })
1048
+ return Promise.reject(`invalidKeyTable`)
1140
1049
  }
1141
1050
 
1142
1051
  //getAndVerifyAppUser
@@ -1146,8 +1055,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1146
1055
  if (!iseobj(user)) {
1147
1056
  console.log('token', token)
1148
1057
  console.log('[API]syncAndReplaceTabs/check user: invalid user')
1149
- console.log(`token does not have permission`)
1150
- return Promise.reject(`token does not have permission`)
1058
+ srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
1059
+ return Promise.reject(`tokenNoPermission`)
1151
1060
  }
1152
1061
 
1153
1062
  //userId
@@ -1158,8 +1067,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1158
1067
  console.log('token', token)
1159
1068
  console.log('user', user)
1160
1069
  console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
1161
- console.log(`token does not have permission`)
1162
- return Promise.reject(`token does not have permission`)
1070
+ srLog.error({ event: 'api/syncAndReplaceTabs-error', msg: 'tokenNoPermission' })
1071
+ return Promise.reject(`tokenNoPermission`)
1163
1072
  }
1164
1073
 
1165
1074
  //parsePayload
@@ -1168,6 +1077,9 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1168
1077
  //syncAndReplaceTabs
1169
1078
  let r = await syncAndReplaceTabs(userId, inp, keyTable)
1170
1079
 
1080
+ //info
1081
+ srLog.info({ event: 'api/syncAndReplaceTabs-success', userId, keyTable })
1082
+
1171
1083
  return r
1172
1084
  }
1173
1085
 
@@ -1194,7 +1106,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1194
1106
 
1195
1107
  //WServHapiServer
1196
1108
  instWServHapiServer = new WServHapiServer({
1197
- port: opt.serverPort,
1109
+ port: serverPort,
1198
1110
  pathStaticFiles,
1199
1111
  apiName: 'api',
1200
1112
  apis,
@@ -1218,6 +1130,21 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1218
1130
  let b = await p.checkToken(token)
1219
1131
  // console.log('b', b)
1220
1132
 
1133
+ //ip, 對齊sso慣例x-forwarded-for取第1個, fallback到req remoteAddress, 取不到則略過
1134
+ let ip = get(headers, 'x-forwarded-for', '').split(',')[0].trim()
1135
+ if (!isestr(ip)) {
1136
+ ip = get(req, 'info.remoteAddress', '') || get(req, 'socket.remoteAddress', '') || get(req, 'connection.remoteAddress', '')
1137
+ }
1138
+
1139
+ //origin
1140
+ let origin = get(headers, 'origin', '')
1141
+
1142
+ //referer
1143
+ let referer = get(headers, 'referer', '')
1144
+
1145
+ //info
1146
+ srLog.info({ event: 'verifyConn', ok: b, apiType, ip, origin, referer })
1147
+
1221
1148
  return b
1222
1149
  },
1223
1150
  getUserIdByToken: async (token) => { //可使用async或sync函數
@@ -1227,7 +1154,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1227
1154
  if (!isestr(userId)) {
1228
1155
  console.log('token', token)
1229
1156
  console.log('userId', userId)
1230
- return Promise.reject(`can not find user.id`)
1157
+ srLog.error({ event: 'getUserIdByToken-error', msg: 'cannotFindUserId' })
1158
+ return Promise.reject(`cannotFindUserId`)
1231
1159
  }
1232
1160
  return userId
1233
1161
  },
@@ -1247,10 +1175,34 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1247
1175
  // getGrupsList,
1248
1176
  // getUsersList,
1249
1177
 
1250
- updateTargets,
1251
- updatePemis,
1252
- updateGrups,
1253
- updateUsers,
1178
+ updateTargets: pc.updateTargets,
1179
+ updatePemis: pc.updatePemis,
1180
+ updateGrups: pc.updateGrups,
1181
+ updateUsers: pc.updateUsers,
1182
+
1183
+ getStaEvent: async (userId, timeLength, timeInterval) => {
1184
+ try {
1185
+ let rs = await psi.getStaEvent(userId, timeLength, timeInterval)
1186
+ srLog.info({ event: 'getStaEvent-success', userId })
1187
+ return rs
1188
+ }
1189
+ catch (err) {
1190
+ srLog.error({ event: 'getStaEvent-error', userId, msg: (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || 'cannotGetStaEvent') })
1191
+ throw err
1192
+ }
1193
+ },
1194
+
1195
+ getStaEventTable: async (userId) => {
1196
+ try {
1197
+ let rs = await psi.getStaEventTable(userId)
1198
+ srLog.info({ event: 'getStaEventTable-success', userId })
1199
+ return rs
1200
+ }
1201
+ catch (err) {
1202
+ srLog.error({ event: 'getStaEventTable-error', userId, msg: (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || 'cannotGetStaEventTable') })
1203
+ throw err
1204
+ }
1205
+ },
1254
1206
 
1255
1207
  },
1256
1208
  fnTableTags: 'tableTags-web-perm.json',
@@ -1276,7 +1228,7 @@ export default WWebPerm
1276
1228
  <br class="clear">
1277
1229
 
1278
1230
  <footer>
1279
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.4</a> on Sun Mar 29 2026 22:04:12 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1231
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Jul 23 2026 11:17:11 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1280
1232
  </footer>
1281
1233
 
1282
1234
  <script>prettyPrint();</script>