w-web-perm 1.1.14 → 1.1.16
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 +62 -7
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.307bef4f.js +1 -0
- package/dist/js/app.307bef4f.js +1 -0
- package/dist/js/{chunk-vendors-legacy.b0bbb5ac.js → chunk-vendors-legacy.6cc4d11e.js} +3 -3
- package/dist/js/{chunk-vendors.b0bbb5ac.js → chunk-vendors.6cc4d11e.js} +3 -3
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +24 -13
- package/docs/global.html +1397 -126
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +248 -102
- package/docs/server_adminCheck.mjs.html +142 -0
- package/docs/server_cascadeWrite.mjs.html +2 -2
- package/docs/server_dataChannel.mjs.html +3 -3
- package/docs/server_keyCheck.mjs.html +35 -3
- package/docs/server_lockSave.mjs.html +2 -2
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +17 -5
- package/docs/server_routeResult.mjs.html +115 -0
- package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
- package/docs/server_syncCore.mjs.html +2 -2
- package/docs/server_syncKeep.mjs.html +2 -2
- package/docs/server_tools_authRefsAudit.mjs.html +30 -10
- package/docs/server_tools_authRefsClean.mjs.html +2 -2
- package/docs/server_tools_fromTools.mjs.html +2 -2
- package/docs/server_tools_isMain.mjs.html +2 -2
- package/docs/server_tools_nameDupReport.mjs.html +2 -2
- package/docs/server_validateRows.mjs.html +2 -2
- package/docs/server_wrapInjected.mjs.html +2 -2
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_getPermUserInfor.mjs.html +2 -2
- package/docs/src_js_applySaved.mjs.html +2 -2
- package/docs/src_js_cellEditing.mjs.html +2 -2
- package/docs/src_js_cvKpToArr.mjs.html +2 -2
- package/docs/src_js_cvKpToObj.mjs.html +2 -2
- package/docs/src_js_draftSync.mjs.html +2 -2
- package/docs/src_js_eachOwn.mjs.html +2 -2
- package/docs/src_js_editorRefRows.mjs.html +2 -2
- package/docs/src_js_externalRows.mjs.html +2 -2
- package/docs/src_js_externalUi.mjs.html +2 -2
- package/docs/src_js_findRefRows.mjs.html +2 -2
- package/docs/src_js_findTouchedDup.mjs.html +2 -2
- package/docs/src_js_fmtLossyMsg.mjs.html +2 -2
- package/docs/src_js_fromAudit.mjs.html +2 -2
- package/docs/src_js_getAddImpact.mjs.html +2 -2
- package/docs/src_js_getAuthWarn.mjs.html +2 -2
- package/docs/src_js_getCascadePreview.mjs.html +2 -2
- package/docs/src_js_getDelRefCount.mjs.html +2 -2
- package/docs/src_js_getEffMode.mjs.html +2 -2
- package/docs/src_js_getKpLossy.mjs.html +2 -2
- package/docs/src_js_getNameErrs.mjs.html +2 -2
- package/docs/src_js_getOwn.mjs.html +2 -2
- package/docs/src_js_getPropKey.mjs.html +2 -2
- package/docs/src_js_getRuleEnable.mjs.html +2 -2
- package/docs/src_js_isAuthMode.mjs.html +2 -2
- package/docs/src_js_joinNames.mjs.html +2 -2
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_js_pairRowsToSnapshot.mjs.html +2 -2
- package/docs/src_js_refAudit.mjs.html +2 -2
- package/docs/src_js_refCascade.mjs.html +2 -2
- package/docs/src_js_refIdentity.mjs.html +2 -2
- package/docs/src_js_relDialogSend.mjs.html +2 -2
- package/docs/src_js_rowDigest.mjs.html +2 -2
- package/docs/src_plugins_dataReloadGuard.mjs.html +2 -2
- package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
- package/docs/src_plugins_submitGuard.mjs.html +2 -2
- package/package.json +1 -1
- package/server/WWebPerm.mjs +246 -100
- package/server/adminCheck.mjs +70 -0
- package/server/dataChannel.mjs +1 -1
- package/server/keyCheck.mjs +33 -1
- package/server/procCore.mjs +15 -3
- package/server/procLang.mjs +8 -2
- package/server/routeResult.mjs +43 -0
- package/server/tools/authRefsAudit.mjs +28 -8
- package/src/App.vue +6 -1
- package/src/components/LayoutState.vue +12 -0
- package/src/plugins/mUI.mjs +5 -0
- package/src/store/mutations.mjs +1 -1
- package/srv.mjs +111 -3
- package/dist/js/app-legacy.033deb22.js +0 -1
- package/dist/js/app.033deb22.js +0 -1
package/README.md
CHANGED
|
@@ -51,12 +51,13 @@ let opt = {
|
|
|
51
51
|
|
|
52
52
|
let getUserByToken = async (token) => {
|
|
53
53
|
// return {} //測試無法登入
|
|
54
|
-
if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
|
|
54
|
+
if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者(應用系統token: 須轉傳上游反查結果之isApp; 此例供/api/getPermUserInfor、/syncAndReplaceTabs使用,不在users表者只看isAdmin為'y'; 若要同時使用後台與資料通道〔系統服務用途〕,id須以'app:'開頭)
|
|
55
55
|
return {
|
|
56
|
-
id: 'id-for-application',
|
|
56
|
+
id: 'id-for-application', //如w-web-sso反查回'app:<金鑰主鍵>'
|
|
57
57
|
name: 'application',
|
|
58
58
|
email: 'application@example.com',
|
|
59
59
|
isAdmin: 'y',
|
|
60
|
+
isApp: 'y', //選用欄位('y'或'n', 未給視同'n'); 見下方Security: application-system tokens
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
|
|
@@ -76,14 +77,18 @@ let verifyClientUser = (user, from) => {
|
|
|
76
77
|
console.log('verifyClientUser/user', user)
|
|
77
78
|
// return false //測試無法登入
|
|
78
79
|
console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
|
|
79
|
-
|
|
80
|
+
//本套件於此函數通過後,另要求資料通道與後台登入限系統管理員(perm users 表之 isAdmin 為 'y'),一般使用者仍可經 /api/getPerm(from 為 'getPerm')以 token 查本人權限樹,
|
|
81
|
+
//故此處不可回傳 user.isAdmin === 'y'(會擋掉一般使用者之 /api/getPerm); 僅需放行有效之瀏覽使用者,或依 from 與自家政策再收緊
|
|
82
|
+
return true
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
let verifyAppUser = (user, from) => {
|
|
83
86
|
console.log('verifyAppUser/user', user)
|
|
84
87
|
// return false //測試無法登入
|
|
85
88
|
console.log('於生產環境時得加入限制應用程式使用者身份機制')
|
|
86
|
-
|
|
89
|
+
//本套件於此函數通過後另自行要求管理員等級(users表有效之系統管理員,或系統服務用途之應用系統token),一般使用者即使本函數放行亦被拒(userNotAdmin),故不必再以isAdmin判斷;
|
|
90
|
+
//僅需放行有效之應用系統,或依from與自家政策再收緊
|
|
91
|
+
return true
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
//WWebPerm
|
|
@@ -98,13 +103,63 @@ instWWebPerm.on('error', (err) => {
|
|
|
98
103
|
|
|
99
104
|
> 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
105
|
|
|
101
|
-
- **
|
|
106
|
+
- **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:
|
|
107
|
+
|
|
108
|
+
| Entry | Who may use it | Where it is checked (`server/WWebPerm.mjs`) |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| 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'`) |
|
|
111
|
+
| Data channel, executing identity (body `__sysToken__`, looked up before every function and table operation; the one exception is the system function `[sys:polling]`, whose body identity is checked by the framework from w-serv-webdata 1.1.16 on — its header token is always checked here) | administrators | `getUserIdByToken` → `getAndVerifyAdminUser(token, 'main')` |
|
|
112
|
+
| 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`) |
|
|
113
|
+
| The four write functions | administrators (defense in depth) | `requireAdmin` (fresh read of the caller's row) |
|
|
114
|
+
| Console login check `GET /api/getUserByToken` | administrators | handler → `getAndVerifyAdminUser(token, 'getUserByToken')` |
|
|
115
|
+
| 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')` |
|
|
116
|
+
| `GET /api/getPermUserInfor`, `POST /syncAndReplaceTabs` | administrators; a caller that is in the `users` table is judged by its row (active and `isAdmin: 'y'`), a caller that is not in the table is accepted when your `getUserByToken` reports `isAdmin: 'y'` (system-service application tokens, application identities that do not forward `isApp`, tokens bound to an administrator that exists only upstream) (this package requires the level itself, then `verifyAppUser`) | handlers → `getAndVerifyAppUser` |
|
|
117
|
+
| Static front-end files | everyone (no data) | `useInert` of w-serv-hapi |
|
|
118
|
+
|
|
119
|
+
- **Application-system tokens (since perm-run01 of 2026-10-24, R-APP-TOKEN-01)**: a token whose `getUserByToken` result carries `isApp: 'y'` is an application-system token (issued by w-web-sso; this package never issues tokens). It has two purposes. **User purpose** (bound to a user by `userId`): your `getUserByToken` returns that user's real data with `isApp: 'y'`; this package judges it exactly like the user's own user token, by the user's row in the `users` table (a bound administrator gets the administrator services, a bound ordinary user only `/api/getPerm` — the tree of the bound user —, a bound user that is disabled gets nothing, and a bound user that is missing from the table is refused by the console login check and the data channel exactly like that user's own token, while the two application routes still accept it when upstream reports `isAdmin: 'y'` for that user). **System-service purpose** (not bound to a user, the external application system itself): all four conditions must hold — your `getUserByToken` returns `isApp: 'y'`, `isAdmin: 'y'` and an `id` that **starts with `app:`** (`app:<key id>`; w-web-sso refuses user ids with that prefix, so no user can look like it; name = key name; `email` may be empty), **and** the identity is **not** in the `users` table (a row found for it by `mappingBy`, active or not, makes the row the authority instead). Such an identity may use the administrator services: the console login check, every data-channel entry and the four write functions (plus the two application routes, below); the audit columns (`userId`, `userIdUpdate`, ...) record its `id` (`app:...`). It cannot use `/api/getPerm` (it has no tree of its own). It goes through `verifyAppUser(user, from)` (`from` = the entry: `getUserByToken`, `main`, `upload-controller`, ..., `getPermUserInfor`, `syncAndReplaceTabs`), **not** `verifyClientUser`; your function may tighten further. High-privilege services are the project's decision: this package adds no limit of its own beyond the two purposes. An application token bound to a user who is **not** in the `users` table (for example an administrator that exists only in w-web-sso; the token reports that user's own id, which does not start with `app:`) is **not** the system-service purpose: it is refused by the console login check and the data channel exactly like that administrator's own user token. An identity that reports `isApp: 'y'`, `isAdmin: 'y'` but an `id` without the `app:` prefix is likewise not the system-service purpose there. `getUserByToken` should **forward `isApp`** (optional field, `'y'` / `'n'`; missing = `'n'`): without it the identity is never the system-service purpose, but the two application routes still accept it as described in the next item.
|
|
120
|
+
- **Application-system routes need the administrator level (since perm-run01 of 2026-10-24; the rule for callers outside the `users` table was made compatible in perm-run02)**: `/api/getPermUserInfor` and `/syncAndReplaceTabs` used to rely on `verifyAppUser` alone. Now the caller must be (a) a row of the `users` table (looked up by `mappingBy`) that is active with `isAdmin: 'y'` — this includes an application token bound to such a user — or, **if the caller is in no row of the table**, (b) an identity for which your `getUserByToken` reports `isAdmin: 'y'`. (b) is deliberately lenient (compatible with deployments upgraded from earlier versions): it does **not** require `isApp` and does **not** require an `id` starting with `app:`, so the system-service application identity, a virtual application identity that does not forward `isApp` (the old w-web-sso application identity was simply `isAdmin: 'y'`), and an application token bound to an administrator that exists only upstream (it behaves like that administrator's own user token) are all accepted. An ordinary user (a row with `isAdmin: 'n'`, or an identity not in the table whose `isAdmin` is not `'y'`) and an application token bound to an ordinary user are refused with the fixed key `userNotAdmin` (a veto from `verifyAppUser` is `userNoPermission`, checked first); a bound user that is in the table but disabled is refused by the row (`cannotGetUserFromPerm`). The identity is verified **before** the parameters: `/syncAndReplaceTabs` no longer answers differently for a valid or invalid `keyTable` to a caller that is not verified.
|
|
121
|
+
- **`Authorization` header**: the connection gate accepts only the `Bearer` scheme (case-insensitive, exactly one space, then the token). `Basic <token>`, `Token <token>`, a bare token, two spaces or a tab count as no token (`permission denied`). Before, the first 7 characters were cut off whatever they were.
|
|
122
|
+
- **Route failures answer fixed keys**: when a route fails for a reason that is not a message key (an ORM / file-system error, an exception), the response is `anUnexpectedErrorOccurred` and the original text is written to srLog only (`api/<route>-error`, field `raw`); known keys (`userNotAdmin`, `tokenNoPermission`, `saveWriteFail`, ...) are returned as before.
|
|
123
|
+
|
|
124
|
+
- **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
125
|
- **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
|
|
104
|
-
- **Public entries**: there are none apart from the static front-end files. `/api/getUserByToken` and `/api/getPerm`
|
|
126
|
+
- **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.)*
|
|
127
|
+
- **Public entries**: there are none apart from the static front-end files. `/api/getUserByToken` verifies a client user and requires an administrator (or a system-service application token), `/api/getPerm` verifies a client user (any role), `/api/getPermUserInfor` and `/syncAndReplaceTabs` verify an application user (administrator level required, then `verifyAppUser`); the data channel only offers `select` on the tables (all writes go through the four functions above).
|
|
105
128
|
|
|
106
129
|
## Upgrade Notes
|
|
107
130
|
|
|
131
|
+
### Unreleased: application-system tokens (user purpose / system-service purpose), the administrator level on the application routes, `Bearer` only, route errors as fixed keys (2026-10-24, perm-run01 and perm-run02 of this round, R-APP-TOKEN-01) — one narrow breaking change, one behavior change and two fixes
|
|
132
|
+
|
|
133
|
+
**1. The application routes need the administrator level — an application identity that is in no row of `users` and reports `isAdmin: 'y'` keeps working, with or without `isApp` (breaking only for application identities that do not report `isAdmin: 'y'`, or that match a `users` row that is not an active administrator)**
|
|
134
|
+
- **What changes**: `GET /api/getPermUserInfor` and `POST /syncAndReplaceTabs` used to depend only on your `verifyAppUser`. Now this package also requires: (a) a caller that is in a row of the `users` table (found by `mappingBy`, active or not; an application token bound to such a user counts) is judged by that row — active with `isAdmin: 'y'`; (b) a caller that is in no row of the table is accepted when the `getUserByToken` result reports `isAdmin: 'y'`, and **nothing else is checked there**: `isApp` need not be forwarded and the `id` need not start with `app:`. This is compatible by design: the application identity of w-web-sso up to now was simply `isAdmin: 'y'`, so **a deployment whose application identities are in no row of `users` and that did not touch `getUserByToken` keeps working after the upgrade** (an identity that matches a row of `users` is judged by that row, whatever `getUserByToken` reports). Refused with `userNotAdmin`: an ordinary user (a row with `isAdmin: 'n'`), an application token bound to an ordinary user, and an identity that is not in the table and whose `isAdmin` is not `'y'`. A caller found in a disabled row is refused by the row (`cannotGetUserFromPerm`). An administrator that exists only upstream (in no row of `users`) is accepted with its own user token and, equally, with an application token bound to it. Your `verifyAppUser` is still called first and can only tighten.
|
|
135
|
+
- **What to do**: nothing is required for compatibility. Recommended: make your `getUserByToken` return the optional field `isApp` (`'y'` / `'n'`; missing = `'n'`) exactly as the upstream lookup (w-web-sso) reports it, because the system-service purpose below needs it. Your `verifyAppUser` no longer has to check `isAdmin`; the old example (`return user.isAdmin === 'y'`) and the new one (`return true`) both work, but the old one hides the difference between `userNotAdmin` and `userNoPermission`.
|
|
136
|
+
- **The system-service purpose on the console login check and the data channel (new; stricter than the application routes)**: these entries (the console login check, every data-channel entry, the four write functions) accept an identity that is in no row of `users` only when **all four** hold: your `getUserByToken` result has `isApp: 'y'`, `isAdmin: 'y'` and an `id` **starting with `app:`** (w-web-sso returns `app:<key id>` for the application key itself and refuses user ids with that prefix). It goes through `verifyAppUser` (not `verifyClientUser`) and the audit columns record its id (`app:...`). **Your `getUserByToken` must forward `isApp` for this** (the bundled `srv.mjs` shows the test tokens `{token-for-app-system}` etc.). An application token bound to an administrator that exists only upstream reports that user's own id (no `app:` prefix), so these entries refuse it exactly like the administrator's own user token; the same holds for an identity with `isApp: 'y'`, `isAdmin: 'y'` and an `id` without the prefix. These entries used to refuse every identity that is not in the `users` table, so nothing that worked before stops working.
|
|
137
|
+
- **What "in the `users` table" means**: the identity found by `mappingBy` (active or not) **or** whose `id` equals the primary key of any row of `users`. An identity that is in the table is always judged by that row (so an application token whose `id` happens to be the key of a user row is never a system-service identity, whatever else it reports); only an identity that is in no row is a candidate for the system-service purpose or for rule (b) above. Your `getUserByToken` result is copied once per request, so changing the object after returning it has no effect.
|
|
138
|
+
- **Audit columns written through `/syncAndReplaceTabs` (behavior change for identities found in `users`)**: they now record the `id` of the caller's `users` row, exactly as the data channel does (an application token bound to a user records the bound user's `id`). Before, they recorded the `id` your `getUserByToken` reported, which differs from the row's `id` when `mappingBy` is `'email'` or `'name'` or the caller is an externally imported user (found by `srcKey`); with the default `mappingBy: 'id'` and users created in the admin pages the two are the same. A caller that is in no row records the `id` your `getUserByToken` reported (`app:...` for the system-service purpose).
|
|
139
|
+
- **Check before upgrading**: count srLog `getAndVerifyAppUser-error` with `msg: 'userNotAdmin'` after the upgrade; each is an application call that is refused because the caller matches a `users` row that is not an active administrator (the row decides even when your `getUserByToken` reports `isAdmin: 'y'`), or because the caller is in no row and your `getUserByToken` result does not report `isAdmin: 'y'`.
|
|
140
|
+
|
|
141
|
+
**2. Console-login screen and the example `verifyClientUser` (behavior of the bundled example)**
|
|
142
|
+
- The bundled `srv.mjs` no longer vetoes ordinary users in `verifyClientUser` (it used to return `user.isAdmin === 'y'`, which sent an ordinary user to the generic "Login denied" screen and a redirect). It returns `true`, so an ordinary user is refused by this package with `userNotAdmin` and always sees "No permission to enter the permission console". A veto from your own `verifyClientUser` (for a reason other than the role — source network, system) answers `userNoPermission`, shows "Login denied" and redirects to `opt.urlRedirect` (after 60 seconds in development), as before.
|
|
143
|
+
|
|
144
|
+
**3. `Authorization` must use the `Bearer` scheme (fix)**
|
|
145
|
+
- The connection gate cut the first 7 characters off the header without looking at them, so any 7-character prefix followed by the token (for example `Bearer\t<token>` or `XXXXXXX<token>`) passed as a valid token, while `Basic <token>` and `Token <token>` (6-character prefixes) lost the first character of the token and were refused. Now only `Bearer <token>` (case-insensitive, exactly one space) is a token. Clients of this package's own SDK already send `Bearer`.
|
|
146
|
+
|
|
147
|
+
**4. Failures of the four hapi routes answer fixed keys (fix)**
|
|
148
|
+
- `/api/getUserByToken`, `/api/getPerm`, `/api/getPermUserInfor` and `/syncAndReplaceTabs` returned the `message` of any `Error` as-is (an ORM or file-system text could reach the caller). They now return a known message key, or `anUnexpectedErrorOccurred` for anything else, and write the original text to srLog (`api/<route>-error`, `raw`).
|
|
149
|
+
- `/syncAndReplaceTabs` verifies the caller before it looks at `keyTable`: a caller that is not verified gets the same answer whatever `keyTable` is (it no longer reveals whether a table name is valid).
|
|
150
|
+
|
|
151
|
+
### 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
|
|
152
|
+
|
|
153
|
+
**1. Only system administrators can use the data channel and log in to the console (behavior change)**
|
|
154
|
+
- **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.
|
|
155
|
+
- **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`.
|
|
156
|
+
- **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.
|
|
157
|
+
- **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`.
|
|
158
|
+
- **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").
|
|
159
|
+
|
|
160
|
+
**2. A saved batch with the same `id` twice is refused for all four tables (fix)**
|
|
161
|
+
- `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.
|
|
162
|
+
|
|
108
163
|
### 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
164
|
|
|
110
165
|
**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.
|
|
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>
|