w-web-perm 1.1.14 → 1.1.15

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 (80) hide show
  1. package/README.md +31 -4
  2. package/dist/index.tmp +1 -1
  3. package/dist/js/app-legacy.307bef4f.js +1 -0
  4. package/dist/js/app.307bef4f.js +1 -0
  5. package/dist/js/{chunk-vendors-legacy.b0bbb5ac.js → chunk-vendors-legacy.6cc4d11e.js} +3 -3
  6. package/dist/js/{chunk-vendors.b0bbb5ac.js → chunk-vendors.6cc4d11e.js} +3 -3
  7. package/dist/w-web-perm.umd.js +2 -2
  8. package/dist/w-web-perm.umd.js.map +1 -1
  9. package/docs/WWebPerm.html +11 -8
  10. package/docs/global.html +454 -2
  11. package/docs/index.html +2 -2
  12. package/docs/server_WWebPerm.mjs.html +51 -22
  13. package/docs/server_adminCheck.mjs.html +92 -0
  14. package/docs/server_cascadeWrite.mjs.html +2 -2
  15. package/docs/server_dataChannel.mjs.html +3 -3
  16. package/docs/server_keyCheck.mjs.html +2 -2
  17. package/docs/server_lockSave.mjs.html +2 -2
  18. package/docs/server_maskLog.mjs.html +2 -2
  19. package/docs/server_procCore.mjs.html +17 -5
  20. package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
  21. package/docs/server_syncCore.mjs.html +2 -2
  22. package/docs/server_syncKeep.mjs.html +2 -2
  23. package/docs/server_tools_authRefsAudit.mjs.html +30 -10
  24. package/docs/server_tools_authRefsClean.mjs.html +2 -2
  25. package/docs/server_tools_fromTools.mjs.html +2 -2
  26. package/docs/server_tools_isMain.mjs.html +2 -2
  27. package/docs/server_tools_nameDupReport.mjs.html +2 -2
  28. package/docs/server_validateRows.mjs.html +2 -2
  29. package/docs/server_wrapInjected.mjs.html +2 -2
  30. package/docs/src_fetchJson.mjs.html +2 -2
  31. package/docs/src_getPermUserInfor.mjs.html +2 -2
  32. package/docs/src_js_applySaved.mjs.html +2 -2
  33. package/docs/src_js_cellEditing.mjs.html +2 -2
  34. package/docs/src_js_cvKpToArr.mjs.html +2 -2
  35. package/docs/src_js_cvKpToObj.mjs.html +2 -2
  36. package/docs/src_js_draftSync.mjs.html +2 -2
  37. package/docs/src_js_eachOwn.mjs.html +2 -2
  38. package/docs/src_js_editorRefRows.mjs.html +2 -2
  39. package/docs/src_js_externalRows.mjs.html +2 -2
  40. package/docs/src_js_externalUi.mjs.html +2 -2
  41. package/docs/src_js_findRefRows.mjs.html +2 -2
  42. package/docs/src_js_findTouchedDup.mjs.html +2 -2
  43. package/docs/src_js_fmtLossyMsg.mjs.html +2 -2
  44. package/docs/src_js_fromAudit.mjs.html +2 -2
  45. package/docs/src_js_getAddImpact.mjs.html +2 -2
  46. package/docs/src_js_getAuthWarn.mjs.html +2 -2
  47. package/docs/src_js_getCascadePreview.mjs.html +2 -2
  48. package/docs/src_js_getDelRefCount.mjs.html +2 -2
  49. package/docs/src_js_getEffMode.mjs.html +2 -2
  50. package/docs/src_js_getKpLossy.mjs.html +2 -2
  51. package/docs/src_js_getNameErrs.mjs.html +2 -2
  52. package/docs/src_js_getOwn.mjs.html +2 -2
  53. package/docs/src_js_getPropKey.mjs.html +2 -2
  54. package/docs/src_js_getRuleEnable.mjs.html +2 -2
  55. package/docs/src_js_isAuthMode.mjs.html +2 -2
  56. package/docs/src_js_joinNames.mjs.html +2 -2
  57. package/docs/src_js_legendFit.mjs.html +2 -2
  58. package/docs/src_js_pairRowsToSnapshot.mjs.html +2 -2
  59. package/docs/src_js_refAudit.mjs.html +2 -2
  60. package/docs/src_js_refCascade.mjs.html +2 -2
  61. package/docs/src_js_refIdentity.mjs.html +2 -2
  62. package/docs/src_js_relDialogSend.mjs.html +2 -2
  63. package/docs/src_js_rowDigest.mjs.html +2 -2
  64. package/docs/src_plugins_dataReloadGuard.mjs.html +2 -2
  65. package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
  66. package/docs/src_plugins_submitGuard.mjs.html +2 -2
  67. package/package.json +1 -1
  68. package/server/WWebPerm.mjs +49 -20
  69. package/server/adminCheck.mjs +20 -0
  70. package/server/dataChannel.mjs +1 -1
  71. package/server/procCore.mjs +15 -3
  72. package/server/procLang.mjs +8 -2
  73. package/server/tools/authRefsAudit.mjs +28 -8
  74. package/src/App.vue +6 -1
  75. package/src/components/LayoutState.vue +12 -0
  76. package/src/plugins/mUI.mjs +5 -0
  77. package/src/store/mutations.mjs +1 -1
  78. package/srv.mjs +24 -0
  79. package/dist/js/app-legacy.033deb22.js +0 -1
  80. package/dist/js/app.033deb22.js +0 -1
package/README.md CHANGED
@@ -76,7 +76,9 @@ let verifyClientUser = (user, from) => {
76
76
  console.log('verifyClientUser/user', user)
77
77
  // return false //測試無法登入
78
78
  console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
79
- return user.isAdmin === 'y' //測試僅系統管理者使用
79
+ //本套件於此函數通過後,另要求資料通道與後台登入限系統管理員(perm users 表之 isAdmin 為 'y'),一般使用者仍可經 /api/getPerm(from 為 'getPerm')以 token 查本人權限樹,
80
+ //故此處不可回傳 user.isAdmin === 'y'(會擋掉一般使用者之 /api/getPerm); 僅需放行有效之瀏覽使用者,或依 from 與自家政策再收緊
81
+ return true
80
82
  }
81
83
 
82
84
  let verifyAppUser = (user, from) => {
@@ -98,13 +100,38 @@ instWWebPerm.on('error', (err) => {
98
100
 
99
101
  > Caller-identity responsibility (cross-package principle `dspec/使用者身分驗證原則.md`): the frameworks (`w-converhp`, `w-serv-hapi`, `w-serv-webdata`, `w-serv-orm`) do not verify tokens; this package does, entirely. The connection gate (`verifyConn`) and the executing identity (`getUserIdByToken`) are both fully verified (valid token, user present and active in this system, deployer authorization); hapi `apis` routes verify themselves; functions check the role on their own; public entries are limited to the ones named.
100
102
 
101
- - **Connection gate and executing identity (since 2026-10-23, perm-run03)**: a data-channel request carries two tokens: the `Authorization` header (checked by `verifyConn`) and the `__sysToken__` of the request body (looked up by `getUserIdByToken`, which decides the user a function runs as). Both now go through the same full check `getAndVerifyClientUser`: your `getUserByToken` returns the user, the user must exist in the `users` table of this system (looked up by `mappingBy`) with `isActive: 'y'`, and your `verifyClientUser(user, from)` must return `true`. If the body token fails any step the function is **not executed** (nothing is written; the response carries a fixed message key such as `cannotFindUserFromToken`, `cannotGetUserFromPerm` or `userNoPermission`; srLog `getUserIdByToken-error`, never the token). The two tokens may belong to different users; each is verified, the framework does not compare them, and the function runs as the body token's user. Before this release `getUserIdByToken` only asked `getUserByToken` for an id, so a body token that was not in the `users` table, was disabled, or that `verifyClientUser` would refuse was accepted as long as the header token was valid.
103
+ - **Administrators only (since 2026-10-24, perm-run01)**: the permission console and the whole data channel are for system administrators only. A system administrator is a row of the `users` table of this system with `isAdmin: 'y'` **and** `isActive: 'y'` (this package takes its own table as the authority, never the `isAdmin` your `getUserByToken` reports). After the full check described below, the connection gate, the executing identity and the console login check (`/api/getUserByToken`) additionally require that row to be an administrator; otherwise they refuse with the fixed message key `userNotAdmin` (the connection gate answers `permission denied`, srLog `verifyConn-error` with `msg: 'userNotAdmin'`), **even when your `verifyClientUser` lets the user in**. An ordinary user (`isAdmin: 'n'`) therefore cannot reach the data channel (no `select` on the four tables, no read-only function, no system function, no upload or download entry) and cannot log in to the console; the only thing an ordinary user can do is ask for his or her own permission tree with a token through `/api/getPerm` (application systems keep using `/api/getPermUserInfor` and `/syncAndReplaceTabs` with their application token). The console shows "No permission to enter the permission console" and stays on that notice instead of redirecting. Your `verifyClientUser` still runs first and can only tighten further; it no longer needs to tell administrators from ordinary users by `from`, and it must **not** return `user.isAdmin === 'y'` (that would also refuse ordinary users on `/api/getPerm`). Entry inventory:
104
+
105
+ | Entry | Who may use it | Where it is checked (`server/WWebPerm.mjs`) |
106
+ |---|---|---|
107
+ | Data channel `POST /api/main`: RPC (`getWebInfor`, `getStaEvent`, `getStaEventTable`, `updateTargets/Pemis/Grups/Users`), table `select`, system functions (`[sys:getFuncList]`, `[sys:getTableTags]`, `[sys:polling]`), connection gate | administrators | `verifyConn` → `checkToken` → `getAndVerifyAdminUser` (`from` = `'main'`) |
108
+ | Data channel, executing identity (body `__sysToken__`, looked up before every function and table operation) | administrators | `getUserIdByToken` → `getAndVerifyAdminUser(token, 'main')` |
109
+ | Upload and download routes (`/api/ulctr`, `/api/slc`, `/api/dwgfn`, `/api/dwgf`, `/api/dw`; `apiType` `upload-controller`, `upload-slice`, `download-get-filename`, `download-get-file`, `download`) | administrators | the same `verifyConn` → `checkToken` (`from` = the `apiType`) |
110
+ | The four write functions | administrators (defense in depth) | `requireAdmin` (fresh read of the caller's row) |
111
+ | Console login check `GET /api/getUserByToken` | administrators | handler → `getAndVerifyAdminUser(token, 'getUserByToken')` |
112
+ | Own permission tree `GET /api/getPerm` | every valid user your `verifyClientUser` lets in (administrators and ordinary users); returns the caller's own tree only | handler → `getAndVerifyClientUser(token, 'getPerm')` |
113
+ | `GET /api/getPermUserInfor`, `POST /syncAndReplaceTabs` | application systems (`verifyAppUser`) | handlers → `getAndVerifyAppUser` |
114
+ | Static front-end files | everyone (no data) | `useInert` of w-serv-hapi |
115
+
116
+ - **Connection gate and executing identity (since 2026-10-23, perm-run03)**: a data-channel request carries two tokens: the `Authorization` header (checked by `verifyConn`) and the `__sysToken__` of the request body (looked up by `getUserIdByToken`, which decides the user a function runs as). Both go through the same full check (`getAndVerifyClientUser`, plus the administrator requirement above since perm-run01): your `getUserByToken` returns the user, the user must exist in the `users` table of this system (looked up by `mappingBy`) with `isActive: 'y'`, and your `verifyClientUser(user, from)` must return `true`. If the body token fails any step the function is **not executed** (nothing is written; the response carries a fixed message key such as `cannotFindUserFromToken`, `cannotGetUserFromPerm` or `userNoPermission`; srLog `getUserIdByToken-error`, never the token). The two tokens may belong to different users; each is verified, the framework does not compare them, and the function runs as the body token's user. Before this release `getUserIdByToken` only asked `getUserByToken` for an id, so a body token that was not in the `users` table, was disabled, or that `verifyClientUser` would refuse was accepted as long as the header token was valid.
102
117
  - **What `verifyClientUser` sees**: for the body token it is called with `from = 'main'` (the same `apiType` as the connection gate, the only channel on which the framework looks up the executing identity), so a policy that branches on `from` behaves the same for both. The id returned to the framework (and written to `userId` / `userIdUpdate`) is the id **of the user's row in the `users` table of this system**, not the id your `getUserByToken` reported; they are the same with the default `mappingBy: 'id'` for users created in the admin pages (and for external rows migrated from the old synchronization), and differ for users imported by `/syncAndReplaceTabs` after the upgrade (their `id` is generated; they are found by `srcKey`, see Upgrade Notes section 4) and whenever `mappingBy` is `'email'` or `'name'`.
103
- - **Function-level role check (behavior change)**: `updateTargets`, `updatePemis`, `updateGrups` and `updateUsers` now refuse a caller whose row in the `users` table is not active **and** does not have `isAdmin: 'y'`, with the fixed message key `userNotAdmin`, before anything is read or written (srLog `requireAdmin-error`: function name and user id only). The `users` table of this system is the authority; the `isAdmin` that your `getUserByToken` reports is not used for this check (as for `isAdmin` elsewhere). Until now these functions relied entirely on your `verifyClientUser`; if it let non-administrators in (a read-only viewer, for example), they could write. The read-only functions (`getWebInfor`, `getStaEvent`, `getStaEventTable`) do not change state and are not restricted by this check. The bundled pages are administrator pages and are unaffected. Count `requireAdmin-error` in srLog to see whether anyone was relying on the old behavior.
104
- - **Public entries**: there are none apart from the static front-end files. `/api/getUserByToken` and `/api/getPerm` verify a client user, `/api/getPermUserInfor` and `/syncAndReplaceTabs` verify an application user (`verifyAppUser`); the data channel only offers `select` on the tables (all writes go through the four functions above).
118
+ - **Function-level role check (behavior change)**: `updateTargets`, `updatePemis`, `updateGrups` and `updateUsers` now refuse a caller whose row in the `users` table is not active **and** does not have `isAdmin: 'y'`, with the fixed message key `userNotAdmin`, before anything is read or written (srLog `requireAdmin-error`: function name and user id only). The `users` table of this system is the authority; the `isAdmin` that your `getUserByToken` reports is not used for this check (as for `isAdmin` elsewhere). Until now these functions relied entirely on your `verifyClientUser`; if it let non-administrators in (a read-only viewer, for example), they could write. The bundled pages are administrator pages and are unaffected. Count `requireAdmin-error` in srLog to see whether anyone was relying on the old behavior. *(Since perm-run01 an ordinary user is already refused at the connection gate, before this check; `requireAdmin` remains as defense in depth and is only reachable if the caller's role is lowered between the gate and the function. The read-only functions are restricted as well, see "Administrators only" above.)*
119
+ - **Public entries**: there are none apart from the static front-end files. `/api/getUserByToken` verifies a client user and requires an administrator, `/api/getPerm` verifies a client user (any role), `/api/getPermUserInfor` and `/syncAndReplaceTabs` verify an application user (`verifyAppUser`); the data channel only offers `select` on the tables (all writes go through the four functions above).
105
120
 
106
121
  ## Upgrade Notes
107
122
 
123
+ ### Unreleased: the permission console and the data channel are for system administrators only; duplicate ids in one saved batch are refused (2026-10-24, perm-run01 of this round) — one behavior change and one fix
124
+
125
+ **1. Only system administrators can use the data channel and log in to the console (behavior change)**
126
+ - **What changes**: a user whose row in the `users` table of this system is not an active administrator (`isAdmin` not `'y'`, or `isActive` not `'y'`) is now refused by the connection gate (all data-channel entries, including `select` on the four tables, `getWebInfor`, `getStaEvent`, `getStaEventTable`, system functions and the upload and download routes), by the executing identity check of the request body token, and by the console login check `GET /api/getUserByToken`, **even when your `verifyClientUser` lets that user in**. The refusal key is `userNotAdmin` (the connection gate answers `permission denied`). In srLog each refusal is recorded as `getAndVerifyAdminUser-error` with `msg: 'userNotAdmin'` and the caller's `from` (`getUserByToken` for the console login check, `main` or the route name for the data channel); a refusal at the connection gate also adds `verifyConn-error`, and one of the request body token adds `getUserIdByToken-error`. These entries carry neither the token nor the user's identity. `api/getUserByToken-error` is only written when the token is missing or cannot be resolved to a user, not for the role check. Before this release the read access was only as strict as your `verifyClientUser`: an ordinary user your function let in could read all four tables; since 2026-10-23 (perm-run03) only the four write functions checked the role.
127
+ - **What stays the same**: `/api/getPerm` stays open to every valid user your `verifyClientUser` lets in and returns the caller's own permission tree only; `/api/getPermUserInfor` and `/syncAndReplaceTabs` (application systems, `verifyAppUser`) are unchanged; your `verifyClientUser` is still called first and can still tighten; `isAdmin` is read from the `users` table of this system, never from your `getUserByToken`.
128
+ - **What your `verifyClientUser` receives**: a shallow copy of the user's row of the `users` table of this system. Whatever it changes on that copy (`id`, `isAdmin`, ...) has no effect on the identity a data-channel request runs as, on the role check, or on the user returned by `/api/getUserByToken` and `/api/getPerm`: the value that was checked is the value that is used. `/api/getPerm` also builds the permission tree from the row it verified instead of reading the row again by `id`, so a row that is edited (or deleted and re-created for someone else) while an asynchronous `verifyClientUser` is waiting cannot hand someone else's tree to the caller.
129
+ - **What you can drop**: your `verifyClientUser` no longer has to tell administrators from ordinary users by `from` (it may still tighten). Do **not** make it return `user.isAdmin === 'y'` unless you want to refuse ordinary users on `/api/getPerm` as well (the old example did exactly that; the JSDoc and the example above now show `return true`). Ordinary users cannot use the console: it shows "No permission to enter the permission console" (eng / cht) and stays on it, without redirecting to `urlRedirect`.
130
+ - **Check before upgrading**: list the rows of `users` with `isAdmin: 'n'` that currently use the console through your `verifyClientUser`; they will be refused after the upgrade. Count `getAndVerifyAdminUser-error` with `msg: 'userNotAdmin'` in srLog afterwards to see how often ordinary users still try (an ordinary user opening the console is refused at the login check and never reaches the data channel, so `verifyConn-error` alone undercounts; srLog records how often, not who). The message key `userNotAdmin` now reads "Only system administrators can access or change this data" (it was "...can change this data").
131
+
132
+ **2. A saved batch with the same `id` twice is refused for all four tables (fix)**
133
+ - `updateUsers` used to merge two rows with the same `id` into one and answer success, silently losing one of them (groups, permissions and targets already refused such a batch). Now `updateUsers`, `updateGrups`, `updatePemis` and `updateTargets` refuse the whole batch with `saveRowFieldDuplicate` and write nothing when two rows carry the same non-empty `id` (a number and the string of the same value count as the same `id`). With `modeEditUsers: 'for:grups'` the batch is checked before it is merged with the stored rows. Rows without an `id` are new rows and are not affected. The pages never produce such a batch (ids are generated and cannot be edited); only direct calls do.
134
+
108
135
  ### Unreleased: external import is a `srcKey` difference sync, identity declarations are mandatory (2026-10-23, perm-run03, ADR-038; login mapping `mappingBy: 'id'` decided 2026-10-24, perm-run04, tightened in perm-run05) — two breaking changes and one behavior change
109
136
 
110
137
  **1. `POST /syncAndReplaceTabs` is now a difference sync keyed by `srcKey` (breaking; no compatibility with the old call)**
package/dist/index.tmp CHANGED
@@ -1,4 +1,4 @@
1
1
  <!doctype html><html xmlns="http://www.w3.org/1999/xhtml" lang="zh-tw"><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,minimal-ui"><meta http-equiv="X-UA-Compatible" content="ie=edge"><meta name="referrer" content="strict-origin"><title></title><link _href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet"><link _href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.2/css/all.min.css" rel="stylesheet"><script>window.___pmwperm___={
2
2
  urlRedirect: '{urlRedirect}',
3
3
  language: '{language}',
4
- }</script><script defer="defer" src="{sfd}/js/chunk-vendors.b0bbb5ac.js" type="module"></script><script defer="defer" src="{sfd}/js/app.033deb22.js" type="module"></script><link href="{sfd}/css/chunk-vendors.3c580e72.css" rel="stylesheet"><link href="{sfd}/css/app.f24c5e50.css" rel="stylesheet"><script defer="defer" src="{sfd}/js/chunk-vendors-legacy.b0bbb5ac.js" nomodule></script><script defer="defer" src="{sfd}/js/app-legacy.033deb22.js" nomodule></script></head><body style="font-family:'Microsoft JhengHei','Avenir','Helvetica', 'Arial', 'sans-serif'; padding:0px; margin:0px;"><noscript><strong>We're sorry but w-component-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
4
+ }</script><script defer="defer" src="{sfd}/js/chunk-vendors.6cc4d11e.js" type="module"></script><script defer="defer" src="{sfd}/js/app.307bef4f.js" type="module"></script><link href="{sfd}/css/chunk-vendors.3c580e72.css" rel="stylesheet"><link href="{sfd}/css/app.f24c5e50.css" rel="stylesheet"><script defer="defer" src="{sfd}/js/chunk-vendors-legacy.6cc4d11e.js" nomodule></script><script defer="defer" src="{sfd}/js/app-legacy.307bef4f.js" nomodule></script></head><body style="font-family:'Microsoft JhengHei','Avenir','Helvetica', 'Arial', 'sans-serif'; padding:0px; margin:0px;"><noscript><strong>We're sorry but w-component-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>