Sharing backend: shares CRUD + ACL enforcement everywhere #16

Closed
opened 2026-07-18 18:19:02 +00:00 by steve · 0 comments
Owner

Part of epic #20 · Design: DESIGN.md

Context

Gardens are owned by their creator and shareable with other users as viewer (read) or editor (edit objects/plantings). The owner alone shares, deletes, and edits garden metadata. requireGardenRole (#7) was built as the single choke point for this — this issue extends it to consult garden_shares and audits every service method against the intended matrix.

Scope

  • internal/store/shares.go + internal/service/shares.go: ListShares (owner only), AddShare (owner only; target user by exact email of an existing account — no invitation emails in v1; sharing with yourself or double-sharing → clean errors), UpdateShareRole, RemoveShare (owner; or a recipient removing themselves).
  • API: GET,POST /api/v1/gardens/:id/shares, PATCH,DELETE /api/v1/gardens/:id/shares/:userId. Responses include user email/display name for the UI.
  • Extend requireGardenRole to consult shares: effective role = owner if gardens.owner_id, else share row's role, else no access (404 per #7's convention).
  • Audit every existing service method (gardens #7, objects #10, plants #12 n/a — user-scoped, plantings #14) against the matrix: viewer → read + /full; editor → viewer + object/planting mutations; owner → editor + garden PATCH/DELETE + shares. Fix any handler-level assumptions (e.g. list-gardens must now include shared gardens, each row carrying my_role).
  • Table-driven ACL test: {owner, editor, viewer, stranger} × {read full, mutate object, mutate planting, edit garden meta, delete garden, manage shares} — the matrix in one test file.

Out of scope

  • Frontend (#17). Email notifications/invite links for non-users (post-v1). Per-object permissions (never — garden-level only).

Key files & patterns

  • Roles table: DESIGN.md § Domain model (garden_shares); choke-point rationale § Backend layout.

Dependencies

Blocked by #7 (also touches #10/#14 surfaces — rebase over whatever exists when picked up).

Acceptance criteria

  • Two-user curl flow: A shares with B as viewer → B lists the garden (my_role: "viewer"), reads /full, gets 403 on mutations; A upgrades B to editor → B moves an object but still can't rename the garden or see shares; A removes the share → B gets 404.
  • The ACL matrix test is green and covers every row above.
  • Sharing with an unknown email → 404 with a clear message (no account enumeration beyond what sharing inherently needs).
Part of epic #20 · Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md) ## Context Gardens are owned by their creator and shareable with other users as **viewer** (read) or **editor** (edit objects/plantings). The owner alone shares, deletes, and edits garden metadata. `requireGardenRole` (#7) was built as the single choke point for this — this issue extends it to consult `garden_shares` and audits every service method against the intended matrix. ## Scope - [ ] `internal/store/shares.go` + `internal/service/shares.go`: ListShares (owner only), AddShare (owner only; target user by **exact email of an existing account** — no invitation emails in v1; sharing with yourself or double-sharing → clean errors), UpdateShareRole, RemoveShare (owner; or a recipient removing themselves). - [ ] API: `GET,POST /api/v1/gardens/:id/shares`, `PATCH,DELETE /api/v1/gardens/:id/shares/:userId`. Responses include user email/display name for the UI. - [ ] Extend `requireGardenRole` to consult shares: effective role = owner if `gardens.owner_id`, else share row's role, else no access (404 per #7's convention). - [ ] **Audit every existing service method** (gardens #7, objects #10, plants #12 n/a — user-scoped, plantings #14) against the matrix: viewer → read + `/full`; editor → viewer + object/planting mutations; owner → editor + garden PATCH/DELETE + shares. Fix any handler-level assumptions (e.g. list-gardens must now include shared gardens, each row carrying `my_role`). - [ ] Table-driven ACL test: {owner, editor, viewer, stranger} × {read full, mutate object, mutate planting, edit garden meta, delete garden, manage shares} — the matrix in one test file. ## Out of scope - Frontend (#17). Email notifications/invite links for non-users (post-v1). Per-object permissions (never — garden-level only). ## Key files & patterns - Roles table: `DESIGN.md` § Domain model (`garden_shares`); choke-point rationale § Backend layout. ## Dependencies Blocked by #7 (also touches #10/#14 surfaces — rebase over whatever exists when picked up). ## Acceptance criteria - Two-user curl flow: A shares with B as viewer → B lists the garden (`my_role: "viewer"`), reads `/full`, gets 403 on mutations; A upgrades B to editor → B moves an object but still can't rename the garden or see shares; A removes the share → B gets 404. - The ACL matrix test is green and covers every row above. - Sharing with an unknown email → 404 with a clear message (no account enumeration beyond what sharing inherently needs).
steve added the backendsharing labels 2026-07-18 18:19:02 +00:00
steve closed this issue 2026-07-19 03:49:55 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#16