feat(online-inbox): carry ownerId on sync to prepare for multi-user
Plumb a per-resource owner (Zitadel sub) through the sync contract without enforcing isolation client-side — the server stays the authority. - Dtos: add optional ownerId to RemoteList/RemoteTask/MirrorTask - JwtClaims: decode the sub claim from the access token (never throws) - OnlineSyncService: stamp ownerId on pushed lists + mirror; defensively skip pulled tasks owned by a different user (unowned tasks still sync, so single-user behavior is unchanged) - docs: contract documents ownerId + multi-user readiness Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,17 @@ Sync directions (each one-way per entity → no conflict resolution needed):
|
||||
- **Idle tasks**: desktop mirrors its Idle backlog up; the web can create new ones, which the
|
||||
desktop pulls down and then owns.
|
||||
|
||||
Single user. Both the desktop and the web client authenticate as the **same Zitadel user**.
|
||||
Single user today. Both the desktop and the web client authenticate as the **same Zitadel
|
||||
user**.
|
||||
|
||||
**Multi-user readiness (`ownerId`).** Each resource is owned by a Zitadel subject (`sub`).
|
||||
`RemoteList`, `RemoteTask`, and `MirrorTask` carry an optional `ownerId` field. The desktop
|
||||
stamps its own `sub` (decoded from the access token) onto everything it pushes, and
|
||||
defensively ignores any pulled task whose `ownerId` is set to a *different* user; an absent
|
||||
`ownerId` is treated as unowned/legacy and still syncs. This keeps the contract ready for
|
||||
multiple users **without enforcing isolation client-side** — the server remains the
|
||||
authority that scopes every request by the token's `sub`. When the server goes multi-user it
|
||||
should partition all rows by owner and ignore (or validate) the client-supplied `ownerId`.
|
||||
|
||||
**Access control (as of 2026-06-10).** Access is granted by assigning the **"user" project
|
||||
role** in the Zitadel project "ClaudeDo" (id `376787351902355727`, issuer
|
||||
@@ -94,13 +104,17 @@ surfaces "missing 'user' role in Zitadel" and pauses sync until the user signs i
|
||||
|
||||
| Method & path | Caller | Body | Response |
|
||||
|---|---|---|---|
|
||||
| `PUT /lists` | desktop | `[{ "id", "name" }]` — the FULL catalog | `200` |
|
||||
| `GET /lists` | web | — | `200 [{ "id", "name" }]` |
|
||||
| `PUT /lists` | desktop | `[{ "id", "name", "ownerId"? }]` — the FULL catalog | `200` |
|
||||
| `GET /lists` | web | — | `200 [{ "id", "name", "ownerId"? }]` |
|
||||
| `GET /lists/{id}/tasks` | web | — | `200` tasks in that list (`404` if list unknown) |
|
||||
| `POST /tasks` | web | `{ "title", "description"?, "listId" }` | `201` created task incl. `id` |
|
||||
| `GET /tasks?imported=false` | desktop | — | `200 [{ "id","listId","title","description","createdAt" }]` |
|
||||
| `GET /tasks?imported=false` | desktop | — | `200 [{ "id","listId","title","description","createdAt","ownerId"? }]` |
|
||||
| `POST /tasks/{id}/imported` | desktop | — | `200` (`404` if unknown) |
|
||||
| `PUT /tasks/mirror` | desktop | `[{ "id","listId","title","description" }]` — full Idle set | `200` |
|
||||
| `PUT /tasks/mirror` | desktop | `[{ "id","listId","title","description","ownerId"? }]` — full Idle set | `200` |
|
||||
|
||||
`ownerId` (optional, see §1) is the Zitadel `sub` of the owner. The desktop sends it on push
|
||||
and ignores pulled tasks owned by a different user; the server should derive/validate it from
|
||||
the token rather than trust the client value.
|
||||
|
||||
Semantics:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user