Data-layer hardening: Ably token scoping, input guards, +500 tests#87
Merged
Conversation
Design-bundle reference snapshots under docs/design/ (HTML/JSX/TSX) are not app source and were breaking `next build` typecheck. Excluding docs/ from tsconfig restores a fully green `npm run check` baseline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full-repo /audit-debt sweep of the pre-redesign codebase. 58 findings (1 Critical, 27 Important, 27 Minor, 3 Nit). Disposition recorded: data-layer + security items fixed in Chunk 0c; the rest filed as GitHub issues #76-#86 for separate scheduling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t allowlists (Chunk 0c) Pre-redesign security hardening surfaced by the audit sweep, behavior- preserving for legitimate clients: - Critical: /api/ably/token had no auth check and minted a wildcard- capability token, letting any caller subscribe to any store's shopping-store:<id> channel (cross-user data leak). Now requires a session and scopes capability to the user's owned + accepted-invitation stores (subscribe+presence only; server still publishes via the API key). - ObjectId.isValid guards on meal-plans/[id] (GET/PUT/DELETE), food-items/[id] (PUT/DELETE), and admin approve/toggle-admin body userId — malformed ids now return 400 instead of 500. - Mass-assignment allowlists: recipes/[id] PUT no longer spreads the raw body into $set (blocks createdBy/createdAt/_id injection; isGlobal stays client-settable as the recipe-sharing mechanism); user/settings POST writes only themeMode + defaultMealPlanOwner via dot-notation, so a crafted body can't forge sharing-invitation grants. Adds regression tests for each, including a new ably/token test file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backfill behavior-preserving tests for the untouched data layer, per the audit sweep — golden-master for pure transforms, MSW path coverage for fetch wrappers, and 401/400/ownership/500 coverage for the sharing/store API routes. Utils (lib): - meal-plan-utils: golden-master for findNextAvailableMealPlanStartDate skip-advance loop + checkMealPlanOverlap edge cases (vi.setSystemTime) - shopping-list-position-utils: new (pure position math + MSW) - shopping-list-utils: cover all remaining fetch wrappers (MSW) - recipe-sharing-utils, meal-plan-sharing-utils, recipe-user-data-utils: new MSW - recipe-utils, pantry-utils: new (array/paginated normalization) - user-utils: getCurrentUserAdminStatus; date-utils: day/next-day arms; auth.ts: redirect/session/jwt callbacks API routes (14 sharing/store routes, previously untested): - stores/[id], stores/[id]/invite, stores/[id]/invitations/[userId], stores/invitations, shopping-lists/[storeId]/positions - user/meal-plan-sharing/* and user/recipe-sharing/* (invitations, invitations/[userId], invite, owners, shared-users) Full suite: 1304 tests across 125 files, green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
review-code (base main) returned READY FOR PR (0 Critical/Important). Applied the informational Minor/Nit fixes that harden Chunk 0's own work: - meal-plans/[id] invalid-id guard returns API_ERRORS.BAD_REQUEST (was a "not found" message with a 400 status) — consistent with food-items/[id] - auth.test: add jwt happy-path + missing-user coverage (was catch-path only) - meal-plan-sharing invitations/[userId]: assert the $pull filter/shape on owner-remove and self-leave (was bare toHaveBeenCalled) - recipe-sharing tests: drop no-op collection name-branching npm run check green: 1306 tests, lint clean, build OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
zwrose
added a commit
that referenced
this pull request
May 29, 2026
* docs: design spec for server-side approval enforcement (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: revise approval-enforcement spec per /review-plan findings (#83) Correct two false premises (existing useApprovalStatus hook; auth.ts does not handle the update trigger), make the auth.ts fix a required change with DB re-read (no self-approval), expand the test plan (middleware harness, ~18-route fixture migration), and pin the middleware gate ordering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: update approval-enforcement spec test plan for #87 merge (#83) Route-test migration scope grew from ~18 to 34 user-data route test files (~270 session-mock literals) after #87; point the jwt update-trigger test at the now-existing src/lib/__tests__/auth.test.ts harness; note ably/token test joins the migration set. Design sections unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: fold pass-2 review refinements into approval-enforcement spec (#83) Exempt /api/avatar in middleware (correct the 'no avatar' claim; verified AuthenticatedLayout->Header->CachedAvatar->/api/avatar), specify the useApprovalStatus hook test's fake-timer requirement + mock harness ref, pin the shared approvedSession() fixture shape/location, and soften the coverage claim in Rollout. Verdict: PLAN READY. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for server-side approval enforcement (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: fold plan-review findings into approval-enforcement plan + spec (#83) Admin bypass: gate and helper now exempt admins (isAdmin/isApproved are independent; unapproved admins must keep admin access) + tests. Clarify the user/settings migration (email-only mocks -> approvedSession({email}); it is a real auth tightening). Fix the worked-example request construction (routes.GET(makeReq)), enumerate explicitly-excluded routes, justify the hook fetch stub under MSW, and note the x-middleware-next assertion check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: handle test-less routes in approval-enforcement plan (#83) Pass-3 review: 3 of 37 migrated routes (recipes/tags, recipes/[id]/user-data, shopping-lists/[storeId]) have no colocated test — Task 6 now instructs scaffolding a minimal route.test.ts (unapproved->403 + approved passthrough) rather than stalling. Verdict: PLAN READY. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: add requireApprovedSession helper (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: add approved/unapproved session fixtures (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: refresh isApproved on jwt update trigger (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval in middleware (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover useApprovalStatus approve/demote transitions (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on meal-plans routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on recipes routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on food-items and pantry routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on stores routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on shopping-lists routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on meal-plan-sharing routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on recipe-sharing routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: enforce approval on ably-token and settings routes (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: narrow settings email + drop unused test imports (#83) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Auto-fix round 1: 1 finding (Test) Add steady-state test cases to use-approval-status.test.tsx asserting that when the polled status matches the current session value, neither update() nor router.push() is called. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pre-redesign data-layer hardening, extracted from the
claude-design-redesignbranch (Chunk 0) so it can land onmainindependently of the visual redesign. Purely additive and behavior-preserving — no UI changes.Why a separate PR
The redesign is a long-lived branch; this hardening is valuable on its own and unblocks parallel work that benefits from the new test coverage and the security fixes. The redesign branch will absorb this via a routine
git merge main.Security fixes (surfaced by an
/audit-debtsweep)/api/ably/tokenhad no auth check and minted a wildcard-capability token, letting any caller subscribe to any store'sshopping-store:<id>channel and read other families' shopping data. Now requires a session and scopes the token capability to the user's owned + accepted-invitation stores (subscribe/presenceonly; the server still publishes via the API key).ObjectId.isValidguards onmeal-plans/[id](GET/PUT/DELETE),food-items/[id](PUT/DELETE), and admin approve/toggle-admin — malformed ids now return 400 instead of 500.recipes/[id]PUT no longer spreads the raw body into$set(blockscreatedBy/createdAt/_idinjection;isGlobalstays client-settable as the recipe-sharing mechanism);user/settingsPOST writes onlythemeMode+defaultMealPlanOwnervia dot-notation, so a crafted body can't forge sharing-invitation grants.Test hardening (the untouched data layer)
meal-plan-utilsoverlap/next-date (incl. the skip-advance loop), viavi.setSystemTime.shopping-list-position-utils,recipe-user-data-utils,recipe-utils,pantry-utils; MSW path coverage for the sharing/shopping-list fetch wrappers;auth.tsredirect/session/jwt callbacks.Full suite: 1306 tests / 125 files green, lint clean, build OK.
Also included
tsconfig.jsonexcludesdocs/from the app typecheck (keeps vendored reference material out ofnext build).docs/debt-audit-2026-05-28.md— the full audit backlog (remaining items filed as [debt] Resolve npm audit advisories (8 high, 10 moderate, 1 low) #76–[debt] A11y: grouped minor cleanups (5) #86).🤖 Generated with Claude Code