toga-ai 1.0.996 → 1.0.997
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.
|
@@ -250,14 +250,14 @@ The reverse direction: not "queue a worker from an interceptor" but **"run the p
|
|
|
250
250
|
### Own-row guards in a PRE interceptor
|
|
251
251
|
|
|
252
252
|
A PRE hook that limits a user to their own rows must close four bypasses — each found by review on the `user-global-settings` guard (TRUE-82613):
|
|
253
|
-
1. **Alias keys.** api2 maps `user` / `setting` (FK objects) onto `userId` / `settingId` **after** PRE runs, so a hook that only checks `$payload->userId` is skipped by sending `user: {uuid}`. **Allowlist the payload keys** and
|
|
253
|
+
1. **Alias keys.** api2 maps `user` / `setting` (FK objects) onto `userId` / `settingId` **after** PRE runs, so a hook that only checks `$payload->userId` is skipped by sending `user: {uuid}`. **Allowlist the payload keys, verify every alias that is sent** (`user` must resolve to the caller; `setting` must resolve to an allowed setting, and on PUT/PATCH to the row's own setting), then `unset` all of `userId`, `settingId`, `user`, `setting` so api2 cannot remap them. Reject any other key. Refusing the aliases outright breaks real clients (TOGa Commerce sends `user: {uuid}`, `setting: {slug}`).
|
|
254
254
|
2. **Parent routes.** On `POST /v2/users/<uuid>/user-global-settings` api2 overwrites the FK the hook stamped with the parent's id **after** the hook. Refuse any POST with a parent segment.
|
|
255
255
|
3. **PRE runs BEFORE the field-level write ACL check** — the hook cannot count on the ACL to have rejected a field yet.
|
|
256
256
|
4. **PUT/PATCH carry no owner in the body.** Load the row by `$api->recordUuid`, refuse not-own rows, refuse changes to the owner/type columns, then `unset` them before the save.
|
|
257
257
|
|
|
258
258
|
Refuse API-key callers when the rule is "the signed-in user" (no user on the token). Throw `_Exception_Validation` (400); use **one** message for "not yours" and "not allowed" so the response does not reveal whether a row exists.
|
|
259
259
|
|
|
260
|
-
**Reference implementation:** `_Model_Client_UserGlobalSetting::prePost/prePut/prePatch` (Core.Records **279**), rows in `dbchanges2/Core/2026-10-09a - UserGlobalSettingsInterceptors.sql` (`clientId` + `apiId` NULL → **every tenant, every API**). Keys:
|
|
260
|
+
**Reference implementation:** `_Model_Client_UserGlobalSetting::prePost/prePut/prePatch` (Core.Records **279**), rows in `dbchanges2/Core/2026-10-09a - UserGlobalSettingsInterceptors.sql` (`clientId` + `apiId` NULL → **every tenant, every API**). Keys: `userId`/`user`, `settingId`/`setting`, `value` (aliases verified, then unset). Writable settings (by Core.Settings slug): **language** (value = a code in the **client** `Languages` table, `_Model_Client_Language`), **persona** (value = `Personas.number` of a persona assigned to the caller in `Users_Personas`), **currency** (value = uuid of a currency linked to one of the caller's personas via `Personas_Currencies`). `setting` may be sent as slug, uuid, id, or an object holding one of them. POST stamps `userId` to the caller and refuses a second row for the same user and setting. No ACL grant ships with it yet.
|
|
261
261
|
|
|
262
262
|
**Known own-row holes (open, not fixed — 2026-10-09):**
|
|
263
263
|
- `Client_CompassCanada/2026-01-15 - UserGlobalSettingsAcl.sql` grants UserGlobalSettings writes with no own-row check.
|
package/package.json
CHANGED