style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -3,7 +3,7 @@ phase: 03-event-write-back-pwa-install
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["03-01"]
|
||||
depends_on: ['03-01']
|
||||
files_modified:
|
||||
- apps/api/src/routes/events.ts
|
||||
- apps/api/tests/routes/events.test.ts
|
||||
@@ -13,24 +13,24 @@ user_setup: []
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "POST /api/events/create validates input, resolves the writable target calendar, enqueues a pending outbox row, and returns 202"
|
||||
- "PATCH /api/events/:uid/edit and DELETE /api/events/:uid enqueue update/delete outbox rows with the cached etag"
|
||||
- "A member cannot enqueue a write to a calendar they do not own (403) — D-03 / V4 access control"
|
||||
- 'POST /api/events/create validates input, resolves the writable target calendar, enqueues a pending outbox row, and returns 202'
|
||||
- 'PATCH /api/events/:uid/edit and DELETE /api/events/:uid enqueue update/delete outbox rows with the cached etag'
|
||||
- 'A member cannot enqueue a write to a calendar they do not own (403) — D-03 / V4 access control'
|
||||
- "GET /api/events/sync-status?uid= returns the outbox status for that member's UID"
|
||||
- "Edit that changes the target calendar enqueues a linked delete+create pair in one transaction (D-04)"
|
||||
- 'Edit that changes the target calendar enqueues a linked delete+create pair in one transaction (D-04)'
|
||||
- "GET /api/events/writable-calendars returns the member's writable set per D-03 — own personal + shared Family (read-write); never the other member's read-only personal"
|
||||
artifacts:
|
||||
- path: "apps/api/src/routes/events.ts"
|
||||
provides: "create/edit/delete write endpoints + sync-status + writable-calendars, all enqueue-only (broker boundary)"
|
||||
contains: "/writable-calendars"
|
||||
- path: 'apps/api/src/routes/events.ts'
|
||||
provides: 'create/edit/delete write endpoints + sync-status + writable-calendars, all enqueue-only (broker boundary)'
|
||||
contains: '/writable-calendars'
|
||||
key_links:
|
||||
- from: "apps/api/src/routes/events.ts"
|
||||
to: "calendarOutbox"
|
||||
via: "db.insert(calendarOutbox)"
|
||||
pattern: "calendarOutbox"
|
||||
- from: "apps/api/src/routes/events.ts"
|
||||
to: "calendars (ownership check)"
|
||||
via: "WHERE userId = currentUser.id"
|
||||
- from: 'apps/api/src/routes/events.ts'
|
||||
to: 'calendarOutbox'
|
||||
via: 'db.insert(calendarOutbox)'
|
||||
pattern: 'calendarOutbox'
|
||||
- from: 'apps/api/src/routes/events.ts'
|
||||
to: 'calendars (ownership check)'
|
||||
via: 'WHERE userId = currentUser.id'
|
||||
pattern: "calendars\\.userId"
|
||||
---
|
||||
|
||||
@@ -89,6 +89,7 @@ tests from Plan 01.
|
||||
DELETE `/:uid`: ownership check as above; insert a `delete` outbox row with `calendarObjectUrl` + `etag`. Return 202.
|
||||
|
||||
Do NOT build the VEVENT here and do NOT call Fastmail — the worker (Plan 04 wiring) does both. Wrap DB work in try/catch returning 503 per the existing pattern.
|
||||
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/luc/Projects/familysync && pnpm --filter @familysync/api test -- routes/events && pnpm --filter @familysync/api exec tsc --noEmit</automated>
|
||||
@@ -139,6 +140,7 @@ tests from Plan 01.
|
||||
Per RESEARCH.md Open Q3: select the writable set = rows in `calendars WHERE userId = currentUser.id` (the member's own personal calendar(s)) UNION rows WHERE `isShared = 1` (the shared Family calendar, when read-write to the household). Express this as a single Drizzle query with `WHERE eq(calendars.userId, currentUser.id) OR eq(calendars.isShared, true)`. The other member's personal calendar (a row with a different `userId` and `isShared = 0/false`) MUST NOT appear — it is a read-only overlay only (D-03), never a write target.
|
||||
|
||||
Map each row to the response shape `{ calendars: [{ url, displayName, color, isShared }] }` (exactly the `WritableCalendar` shape Plan 05's `fetchWritableCalendars` consumes). Wrap the db work in try/catch returning 503 per the existing GET handler pattern. Do NOT include any Fastmail call (broker boundary).
|
||||
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/luc/Projects/familysync && pnpm --filter @familysync/api test -- routes/events && grep -q "/writable-calendars" apps/api/src/routes/events.ts && pnpm --filter @familysync/api exec tsc --noEmit</automated>
|
||||
@@ -155,23 +157,25 @@ tests from Plan 01.
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| client → write API | Untrusted member input (event fields, target calendar, uid) crosses here |
|
||||
| Boundary | Description |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------- |
|
||||
| client → write API | Untrusted member input (event fields, target calendar, uid) crosses here |
|
||||
| member A → member B data | A member must never write to, treat-as-writable, or read another member's outbox/calendar |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-03-06 | Elevation of Privilege | write to another member's personal calendar | mitigate | Route asserts `calendars.userId === currentUser.id OR isShared=1` before enqueue; else 403 (D-03 / ASVS V4) |
|
||||
| T-03-07 | Information Disclosure | sync-status leaking another member's outbox row | mitigate | sync-status query filtered `WHERE userId = currentUser.id` |
|
||||
| T-03-08 | Tampering | XSS/oversized payload via title/location/description | mitigate | zod length bounds (title 255, location/description 2000); plain-text storage; rendered as JSX children downstream |
|
||||
| T-03-09 | Tampering | SQL injection via uid/calendarUrl | mitigate | Drizzle parameterized queries; no string interpolation |
|
||||
| T-03-10 | Spoofing | client-supplied etag bypassing conflict detection | mitigate | etag read from calendarEvents server-side at enqueue; client never supplies it |
|
||||
| T-03-11 | Elevation of Privilege | writable-calendars surfacing another member's personal calendar as a write target | mitigate | Query restricted to `userId = currentUser.id OR isShared = true`; another member's `isShared=false` personal row is never returned; client treats the response as authoritative and the write endpoints re-enforce D-03 on enqueue |
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
| --------- | ---------------------- | --------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| T-03-06 | Elevation of Privilege | write to another member's personal calendar | mitigate | Route asserts `calendars.userId === currentUser.id OR isShared=1` before enqueue; else 403 (D-03 / ASVS V4) |
|
||||
| T-03-07 | Information Disclosure | sync-status leaking another member's outbox row | mitigate | sync-status query filtered `WHERE userId = currentUser.id` |
|
||||
| T-03-08 | Tampering | XSS/oversized payload via title/location/description | mitigate | zod length bounds (title 255, location/description 2000); plain-text storage; rendered as JSX children downstream |
|
||||
| T-03-09 | Tampering | SQL injection via uid/calendarUrl | mitigate | Drizzle parameterized queries; no string interpolation |
|
||||
| T-03-10 | Spoofing | client-supplied etag bypassing conflict detection | mitigate | etag read from calendarEvents server-side at enqueue; client never supplies it |
|
||||
| T-03-11 | Elevation of Privilege | writable-calendars surfacing another member's personal calendar as a write target | mitigate | Query restricted to `userId = currentUser.id OR isShared = true`; another member's `isShared=false` personal row is never returned; client treats the response as authoritative and the write endpoints re-enforce D-03 on enqueue |
|
||||
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
@@ -181,9 +185,10 @@ tests from Plan 01.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- All five write/status/writable-calendars endpoints enqueue-only and member-scoped.
|
||||
- D-03 ownership enforced on both the write path and the writable-calendars listing; D-04 edit-as-move pair transactional; D-09 polling endpoint live.
|
||||
</success_criteria>
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/03-event-write-back-pwa-install/03-03-SUMMARY.md` when done.
|
||||
|
||||
Reference in New Issue
Block a user