Repository navigation
Commit 2ea6042
fix(plugin-auth)!: a user preference's owner is fixed when it is saved (#29685)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ82oFrZdNaDiqbKbJU8Jh3ja)_
Fixes #29630
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill or doc teaches changing a saved
preference's owner; the one page that names the rule,
`content/docs/permissions/system-context.mdx` row 26c, is updated here
## 维护者速读
- 改了什么:`sys_user_preference` 加一条保存规则:更新时若把 `user_id` 改成另一个用户,一律 403
`PERMISSION_DENIED`(系统写除外),与 #29575 给 `sys_personal_view` 的规则同形;两个对象现在共用
plugin-auth 里的一份检查 `owner-fixed-rule.ts`。
- 为什么改:用户偏好属于保存它的那个用户,规则此前没有对这个对象成立。
- 风险与回滚:只收窄一种写入(改偏好的属主);只改 `value` 的写入、回显原属主的写入、系统写入都不受影响。回滚即 revert 本
PR。
-
先看:`packages/plugins/plugin-auth/src/owner-fixed-rule.ts`(共用检查)、`user-preference-hooks.ts`(注册与对象判定)、`personal-view-hooks.ts`(改为调用共用检查,文案不变)。
- 两分钟验证:`pnpm --filter @objectstack/dogfood exec vitest run
--maxWorkers=2 test/user-preference-owner-fixed.dogfood.test.ts`,预期 7/7
通过,三种调用者各一条 403。
- 需要你做的:无,席位落地。
## What changes
- **New rule on `sys_user_preference`.** A non-system update that sends
a `user_id` other than the stored one is refused `403
PERMISSION_DENIED`, for a member, an organization administrator and a
platform administrator alike. The refusal names the row and the
`user_id` column and echoes no other id. An update that echoes the
stored `user_id`, or does not send it, saves as before; a system write
is not judged.
- **One shared check.**
`packages/plugins/plugin-auth/src/owner-fixed-rule.ts` (`isOwnerChange`,
`ownerFixedError`, `assertOwnerUnchanged`) now holds the rule for both
`sys_personal_view` and `sys_user_preference`. `personal-view-hooks.ts`
calls it instead of its inline copy; its behaviour and message are
byte-identical. Not exported from a published entry.
- **Registration.** `user-preference-hooks.ts` registers one
`beforeUpdate` hook on `sys_user_preference`, in `AuthPlugin` and in
`IdentityObjectsPlugin`, beside the personal-view hooks, so the rule
travels with the table.
- **Docs.** `content/docs/permissions/system-context.mdx` row 26c covers
both objects and anchors on the shared check (register regenerated).
- **Changeset.** `.changeset/29630-user-preference-owner-fixed.md`:
`@objectstack/plugin-auth` patch, BREAKING banner, both declaration
lines, ADR-0087 `not-required`.
Resumed from `claude/project-thread-rq4y7m@34568e1` (the previous card
thread's hand-off), merged with `origin/main` `7dae3b3c`. The last
commit trims the shared check's header and the changeset's "Who sees it"
line to the rule alone.
## Why
The card asks for the `sys_personal_view` shape (#29575) on this object:
a user's preferences are their own UI state, and their owner is fixed
when they are saved.
## Tests
On `424e9295`:
- `packages/plugins/plugin-auth/src/user-preference-hooks.test.ts`:
registration; a member, an org admin and a platform admin are refused
403; the message names the row and column and no other id; clearing the
owner is a change of owner; controls: echo, value-only update, system
write, another object.
- `packages/qa/dogfood/test/user-preference-owner-fixed.dogfood.test.ts`
(REST): member, org admin and platform admin cannot hand their own
preference to another user (403, the row keeps its owner); controls:
echo saves; the owner reads, lists and updates their own preference.
- `pnpm --filter @objectstack/plugin-auth build && test && typecheck`
under the lock: VERDICT command-exit 0, Test Files 216 passed (216),
Tests 3703 passed, 10 skipped.
- Full dogfood battery (`pnpm --filter @objectstack/dogfood test`, the
`(narrowing)` arm) under the lock on the full build: VERDICT
command-exit 0, Test Files 400 passed, 3 skipped (403); Tests 3304
passed, 28 skipped (3332).
- Ablations (previous card thread, on `3bbd5da8`, each red then restored
with the blob equal to HEAD and `git diff HEAD` empty): dropping the
rule call reds the 5 unit refusal pins and keeps the 5 controls green;
making the shared check always false reds 6 pins across both objects;
guarding the registration (plugin-auth rebuilt, dist-preflight marker
present, then `--absent` after restore) reds the member and org-admin
REST pins. The platform-admin REST pin stays green under that last
ablation because an existing write check already refuses that caller's
write there; the unit pin holds the rule on its own for that caller.
Since then only comments and the changeset changed in tested files.
## Acceptance notes
- `Clause-②: no (narrowing)`: one accepted write is now refused, and no
public export or published type moves.
- The ablation readings are carried from the hand-off report on the
card; the tested code is unchanged since.
## Verification
<details><summary>Derived gates on 424e929</summary>
- Full build under the lock (`pnpm build --concurrency=2`): VERDICT
command-exit 0, Tasks 117 successful, 117 total. History unshallowed for
the teardown self-test fixture.
- `node scripts/pm/dispatch-gates.mjs --commands` (9 paths vs merge base
`7dae3b3c`): 95 families. Each run once, stdin from /dev/null, exit
captured before any pipe: 95 exit 0, including
`check-plugin-teardown-shape.mjs --self-test` (48 cases), `spec
check:skill-examples` (282 prose examples), `check:dual-build-cjs-loads`
(115 entry points across 70 packages), `check:system-context-census` and
`check:type-check-debt` (0 raw tsc errors above the ledger).
- `dispatch-gates --ran`: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.
- `pnpm lint`: exit 0.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbWZsNmNDugrjfzMoJW)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c76cdeb commit 2ea6042
9 files changed
Lines changed: 405 additions & 33 deletions
File tree
- .changeset
- content/docs/permissions
- packages
- plugins/plugin-auth/src
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | | - | |
| 146 | + | |
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
| |||
575 | 575 | | |
576 | 576 | | |
577 | 577 | | |
578 | | - | |
| 578 | + | |
579 | 579 | | |
580 | 580 | | |
581 | 581 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| |||
1872 | 1873 | | |
1873 | 1874 | | |
1874 | 1875 | | |
| 1876 | + | |
| 1877 | + | |
| 1878 | + | |
| 1879 | + | |
| 1880 | + | |
| 1881 | + | |
1875 | 1882 | | |
1876 | 1883 | | |
1877 | 1884 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| 67 | + | |
67 | 68 | | |
68 | 69 | | |
69 | 70 | | |
| |||
123 | 124 | | |
124 | 125 | | |
125 | 126 | | |
126 | | - | |
127 | | - | |
| 127 | + | |
| 128 | + | |
128 | 129 | | |
129 | 130 | | |
130 | 131 | | |
| 132 | + | |
131 | 133 | | |
132 | 134 | | |
133 | 135 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
| 23 | + | |
| 24 | + | |
28 | 25 | | |
29 | 26 | | |
30 | 27 | | |
| |||
37 | 34 | | |
38 | 35 | | |
39 | 36 | | |
| 37 | + | |
40 | 38 | | |
41 | 39 | | |
42 | 40 | | |
| |||
95 | 93 | | |
96 | 94 | | |
97 | 95 | | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
| 96 | + | |
| 97 | + | |
114 | 98 | | |
115 | 99 | | |
116 | 100 | | |
| |||
133 | 117 | | |
134 | 118 | | |
135 | 119 | | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
| 120 | + | |
144 | 121 | | |
145 | 122 | | |
146 | 123 | | |
| |||
0 commit comments