fix(api): bind org GET/sync to the Clerk session org - #422
Conversation
POST /organizations/sync wrote whatever clerk_org_id the client sent, so a signed-in user could overwrite another tenant. GET /organizations/:id returned any org row. Take clerk_org_id from the verified JWT and 404 GET unless the path org is the session org. Closes #421 Co-Authored-By: Duyet Le <me@duyet.net> Co-Authored-By: duyetbot <bot@duyet.net>
Reviewer's GuideThis PR hardens organization APIs against cross-tenant access by binding sync and lookup strictly to the Clerk session org and extends dashboard auth tests to cover unauthenticated and spoofed flows. Sequence diagram for POST /api/v1/organizations/sync session-bound syncsequenceDiagram
actor Dashboard
participant ApiRouter
participant Session
participant Database
Dashboard->>ApiRouter: POST /api/v1/organizations/sync (clerk_org_id?, name)
ApiRouter->>Session: c.get(orgId)
alt [!orgId]
ApiRouter-->>Dashboard: errorResponse UNAUTHORIZED 401
else [orgId present]
ApiRouter->>ApiRouter: parseJsonBody(c)
ApiRouter->>ApiRouter: SyncOrgSchema.parse(body)
ApiRouter->>Database: syncOrganization(db, clerk_org_id, name)
Database-->>ApiRouter: org
ApiRouter-->>Dashboard: JSON org (201 or 200)
end
Sequence diagram for GET /api/v1/organizations/:clerkOrgId session-bound lookupsequenceDiagram
actor Dashboard
participant ApiRouter
participant Session
participant Database
Dashboard->>ApiRouter: GET /api/v1/organizations/:clerkOrgId
ApiRouter->>Session: c.get(orgId)
ApiRouter->>ApiRouter: c.req.param(clerkOrgId)
alt [!sessionOrgId or clerkOrgId !== sessionOrgId]
ApiRouter-->>Dashboard: errorResponse NOT_FOUND 404
else [sessionOrgId matches clerkOrgId]
ApiRouter->>Database: getOrganizationByClerkId(db, clerkOrgId)
alt [!org]
ApiRouter-->>Dashboard: errorResponse NOT_FOUND 404
else [org found]
ApiRouter-->>Dashboard: JSON org 200
end
end
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reached
Next review available in: 110 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
POST /api/v1/organizations/syncnow ignores bodyclerk_org_idand syncs onlyc.get("orgId")from the verified Clerk JWT. Bodynameis applied only to that session org.GET /api/v1/organizations/:idreturns 404 unless the path org is the session org, so another tenant's row is not readable.Fixes #421
Test plan
cd packages/api && bunx vitest run test/clerk-dashboard-auth.test.ts(28 passed)Summary by Sourcery
Enforce session-bound organization access in the dashboard API and harden multi-tenant isolation for Clerk-authenticated requests.
Bug Fixes:
Tests: