Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds client-scoped access checks to multiple payments endpoints by passing full request context into route handlers and invoking ensureClientCanAccessCustomer (now recognizing a "custom" type that is denied). Also updates client/server request routing, server error-wrapping, and many e2e tests to use Auth.fastSignUp and tokenized requests. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Route as Payments Route Handler
participant Guard as ensureClientCanAccessCustomer
participant Tenancy as Tenancy/DB
Client->>Route: HTTP GET/POST (auth.type = "client")
Note over Route,Guard: Route receives fullReq (includes fullReq.auth?.user)
Route->>Guard: ensureClientCanAccessCustomer({ customerType, customerId, user, tenancy, forbiddenMessage })
Guard->>Tenancy: verify ownership or short-circuit for "custom"
Tenancy-->>Guard: validation result or error
Guard-->>Route: OK or throw Forbidden
alt Guard OK
Route->>Tenancy: continue processing (fetch/create)
Tenancy-->>Route: data
Route-->>Client: 200 response
else Guard throws
Route-->>Client: 403 Forbidden
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
Greptile OverviewGreptile SummaryThis PR fixes a security issue by adding access control checks to payment route handlers. The changes ensure that client requests can only access their own user or team products/items. Key Changes:
Security Impact: Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant RouteHandler
participant ensureClientCanAccessCustomer
participant DB
Client->>RouteHandler: GET /payments/items/[type]/[id]/[item_id]
RouteHandler->>RouteHandler: Check auth.type === "client"
RouteHandler->>ensureClientCanAccessCustomer: Validate access
alt customerType === "custom"
ensureClientCanAccessCustomer->>Client: 403 Forbidden
else customerType === "user"
ensureClientCanAccessCustomer->>ensureClientCanAccessCustomer: Check customerId === currentUser.id
alt IDs don't match
ensureClientCanAccessCustomer->>Client: 403 Forbidden
else IDs match
ensureClientCanAccessCustomer->>RouteHandler: Access granted
end
else customerType === "team"
ensureClientCanAccessCustomer->>DB: Check team_admin permission
alt No permission
ensureClientCanAccessCustomer->>Client: 403 Forbidden
else Has permission
ensureClientCanAccessCustomer->>RouteHandler: Access granted
end
end
RouteHandler->>DB: Fetch item/product data
DB->>RouteHandler: Return data
RouteHandler->>Client: 200 OK with data
|
…ack-auth into fix-product-access
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (15)
apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts (5)
146-202: MissinguserAuthon client requests may cause test failures.This test uses
Auth.fastSignUp()but does not passuserAuthon the client DELETE (line 176) and GET (line 188) requests. Given the PR's goal is to add client-access guards that validate whether the client can access the customer, these requests may now require authentication tokens to succeed.🐛 Proposed fix
+ const { userId, accessToken, refreshToken } = await Auth.fastSignUp(); - const { userId } = await Auth.fastSignUp(); await niceBackendFetch(`/api/v1/payments/products/user/${userId}`, { method: "POST", accessType: "server", body: { product_id: "pro-plan", }, }); const cancelResponse = await niceBackendFetch(`/api/v1/payments/products/user/${userId}/pro-plan`, { method: "DELETE", accessType: "client", + userAuth: { accessToken, refreshToken }, }); expect(cancelResponse).toMatchInlineSnapshot(` NiceResponse { "status": 200, "body": { "success": true }, "headers": Headers { <some fields may have been hidden> }, } `); const listResponse = await niceBackendFetch(`/api/v1/payments/products/user/${userId}`, { accessType: "client", + userAuth: { accessToken, refreshToken }, });
225-248: Test for unauthorized access may need authentication tokens.This test verifies that a client cannot cancel someone else's subscription. However, the second user's
accessTokenandrefreshTokenare not passed in the cancel request (line 237). If the backend now requires authentication to determine "who is making the request", the test may not correctly simulate an authenticated but unauthorized user.🐛 Proposed fix
- const { userId: userId2 } = await Auth.fastSignUp(); + const { userId: userId2, accessToken, refreshToken } = await Auth.fastSignUp(); expect(userId2).not.toEqual(userId1); const cancelResponse = await niceBackendFetch(`/api/v1/payments/products/user/${userId1}/pro-plan`, { method: "DELETE", accessType: "client", + userAuth: { accessToken, refreshToken }, });
314-356: MissinguserAuthon client requests for cancellation and listing.Similar to other tests, the client DELETE (line 330) and GET (line 342) requests don't include
userAuth. The test expects these to succeed, but if client-access guards now require authentication, these requests will fail.
378-398: Unused destructured variables and missinguserAuth.The
accessTokenandrefreshTokenare not used. Additionally, the client DELETE request (line 387) lacksuserAuth.
421-443: Unused destructured variables and missinguserAuth.Line 421 destructures variables that aren't used. The client DELETE (line 432) likely needs
userAuthfor the new access guards.apps/e2e/tests/backend/endpoints/api/v1/payments/items.test.ts (7)
52-67: MissinguserAuthon client request.The test uses
Auth.fastSignUp()but doesn't passuserAuthto the client request. If the backend now enforces client access guards, this request may fail without authentication.🐛 Proposed fix
- const { userId } = await Auth.fastSignUp(); + const { userId, accessToken, refreshToken } = await Auth.fastSignUp(); const response = await niceBackendFetch(`/api/latest/payments/items/user/${userId}/test-item`, { accessType: "client", + userAuth: { accessToken, refreshToken }, });
82-100: MissinguserAuthon client request.Same issue - client request without authentication tokens.
115-138: MissinguserAuthon client request.Client request lacks authentication, which may be required by the new access guards.
181-195: MissinguserAuthon client GET request.Line 190 makes a client request without
userAuth.🐛 Proposed fix
- const { userId } = await Auth.fastSignUp(); + const { userId, accessToken, refreshToken } = await Auth.fastSignUp(); const post1 = await niceBackendFetch(`/api/latest/payments/items/user/${userId}/test-item/update-quantity?allow_negative=false`, { method: "POST", accessType: "admin", body: { delta: 2 }, }); expect(post1.status).toBe(200); const get1 = await niceBackendFetch(`/api/latest/payments/items/user/${userId}/test-item`, { accessType: "client", + userAuth: { accessToken, refreshToken }, });
210-224: MissinguserAuthon client GET request.Line 219's client request lacks authentication.
239-255: MissinguserAuthon client GET request.Line 250's client request lacks authentication.
407-438: MissinguserAuthon client GET request.Line 424's client request lacks authentication.
apps/e2e/tests/backend/endpoints/api/v1/stripe-webhooks.test.ts (3)
142-149: MissinguserAuthon client request.The client request at line 145 lacks authentication tokens. If the backend now requires client authentication for the items endpoint, this test will fail.
🐛 Proposed fix
- const { userId } = await Auth.fastSignUp(); + const { userId, accessToken, refreshToken } = await Auth.fastSignUp(); // Before webhook: quantity should be 0 const getBefore = await niceBackendFetch(`/api/latest/payments/items/user/${userId}/${itemId}`, { accessType: "client", + userAuth: { accessToken, refreshToken }, });And similarly for line 208:
const getAfter = await niceBackendFetch(`/api/latest/payments/items/user/${userId}/${itemId}`, { accessType: "client", + userAuth: { accessToken, refreshToken }, });
522-528: MissinguserAuthon client requests.Multiple client requests in this test (lines 524, 591, 601) lack
userAuth.
635-641: MissinguserAuthon client requests.Client requests at lines 637, 704, 744 lack authentication tokens.
🧹 Nitpick comments (7)
packages/stack-shared/src/interface/client-interface.ts (1)
1801-1833: The(this as any).sendServerRequest as neverpattern is a type-safety concern.This pattern accesses a method (
sendServerRequest) that only exists on the child class (StackServerInterface) from the parent class (StackClientInterface). While this matches existing patterns in the file (e.g.,listProjectApiKeys), it has risks:
- Runtime error if
getItemis called withrequestType !== "client"on aStackClientInterfaceinstance (not aStackServerInterface)- The
as nevercast suppresses all type checking on the return valueConsider adding a runtime guard or documenting this constraint clearly:
+ // Note: requestType !== "client" requires this to be a StackServerInterface instance const sendRequest = (requestType === "client" ? this.sendClientRequest : (this as any).sendServerRequest as never).bind(this);Alternatively, consider refactoring to make
sendServerRequestavailable on the base class (returning an error for non-server instances) or using a discriminated check.apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts (6)
31-38: Unused destructured variables.The
accessTokenandrefreshTokenare destructured but not used in this test. The test expects a 401 response for client requests trying to grant a product, so authentication isn't needed to trigger the access type rejection.♻️ Suggested fix
- const { userId, accessToken, refreshToken } = await Auth.fastSignUp(); + const { userId } = await Auth.fastSignUp();
271-291: Unused destructured variables.The
accessTokenandrefreshTokenfrom the result ofAuth.fastSignUp()on line 271 are not used in subsequent requests.
568-576: Unused destructured variables.
accessTokenandrefreshTokenare destructured but onlyuserIdis used in subsequent requests.
765-780: Unused destructured variables.
accessTokenandrefreshTokenare destructured but not used since the subsequent request usesaccessType: "server".
804-821: Unused destructured variables.
accessTokenandrefreshTokenare destructured but the server request doesn't need them.
844-872: Unused destructured variables.Same pattern -
accessTokenandrefreshTokenare destructured but only server-access requests follow.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
1762-1774: Apply consistent session derivation pattern tolistInvoicesmethod.The
listProductsmethod correctly derives the session from the current user's_internalSessionwhen available, butlistInvoices(line 1776) still usesawait this._getSession()directly. Since both are payment-related endpoints requiring proper access guards, they should follow the same pattern.Update
listInvoicesto use:const currentUser = await this.getUser(); const session = currentUser?._internalSession ?? await this._getSession();
🧹 Nitpick comments (7)
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts (1)
2-2: Remove unusedUserimport.The
Userimport is no longer used in this file after migrating toAuth.fastSignUp().Suggested fix
-import { Auth, Payments, Project, User, niceBackendFetch } from "../../../../../backend-helpers"; +import { Auth, Payments, Project, niceBackendFetch } from "../../../../../backend-helpers";apps/e2e/tests/backend/endpoints/api/v1/payments/before-catalog-to-product-line-rename/outdated--validate-code.test.ts (1)
6-6: UnusedUserimport.
Useris imported but no longer used in this file after migrating toAuth.fastSignUp().Suggested fix
-import { Auth, Payments, Project, User, niceBackendFetch } from "../../../../../backend-helpers"; +import { Auth, Payments, Project, niceBackendFetch } from "../../../../../backend-helpers";apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (2)
3-3: UnusedUserimport.
Useris imported but no longer used in this file after migrating toAuth.fastSignUp().Suggested fix
-import { Auth, Payments, Project, User, niceBackendFetch } from "../../../../backend-helpers"; +import { Auth, Payments, Project, niceBackendFetch } from "../../../../backend-helpers";
156-167: Consider passinguserAuthexplicitly for consistency.Other tests in this file explicitly pass
userAuthtoniceBackendFetchfor client requests, but this test relies on the implicit context fromAuth.fastSignUp(). While both approaches work (sinceniceBackendFetchfalls back tobackendContext.value.userAuth), explicit token propagation is clearer and more consistent with the authenticated flow pattern used elsewhere in this file.apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts (1)
2-2: UnusedUserimport.
Useris imported but no longer used in this file after migrating toAuth.fastSignUp().Suggested fix
-import { Auth, Payments, Project, User, niceBackendFetch } from "../../../../backend-helpers"; +import { Auth, Payments, Project, niceBackendFetch } from "../../../../backend-helpers";apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (2)
3-3: UnusedUserimport.
Useris imported but no longer used in this file after migrating toAuth.fastSignUp().Suggested fix
-import { Auth, Payments, Project, User, niceBackendFetch } from "../../../../../backend-helpers"; +import { Auth, Payments, Project, niceBackendFetch } from "../../../../../backend-helpers";
158-169: Consider passinguserAuthexplicitly for consistency.Similar to the main
purchase-session.test.ts, this test relies on implicit context rather than explicit token propagation. Consider aligning with the pattern used in tests at lines 456-497 and 580-622 for consistency.
Summary by CodeRabbit
Security
SDK / Client
Server
Tests
✏️ Tip: You can customize this high-level summary in your review settings.