mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
Fixes four recurring production Sentry issues (triaged from the last 24h of `vercel-production`): ## X OAuth: 403 client-not-enrolled → 400 (STACK-BACKEND-1JE) When a customer's X developer App isn't attached to an X API Project, X deterministically rejects `GET /2/users/me` with 403 `client-not-enrolled` even though the token exchange succeeds. This previously surfaced as a `HexclaveAssertionError` (opaque 500 + Sentry alert) on every sign-in attempt for the misconfigured project. It's now a 400 `StatusError` telling the developer to attach their App to a Project, mirroring the existing `invalid_client` handling in `providers/base.tsx`. Other non-OK statuses still fail loud. ## Stripe: ignore `refund.updated` (STACK-BACKEND-1D9) Stripe emits `refund.updated` asynchronously when the card network fills in `destination_details` on refunds we created — and already ledgered — synchronously in the internal refund route, so it carries no actionable information. Added it to `ignoredEvents` (same approach as #1667/#1461). `refund.failed` deliberately stays fail-loud, since a refund failing after it's ledgered is actionable. ## external-db-sync: fix enqueue deadlocks (STACK-BACKEND-16P) `enqueueExternalDbSyncBatch` batch-inserts deduplication keys with `ON CONFLICT DO NOTHING`; two concurrent batches inserting overlapping keys in different orders could deadlock (Postgres 40P01, surfaced as an empty-titled `PrismaClientKnownRequestError`). Worse than the noise: the flag-clearing UPDATE and the enqueue run in separate transactions, so a deadlocked enqueue silently lost that batch's sync trigger until the next change for the tenancy. Fixed by deduplicating and inserting keys in canonical (sorted) order, so the circular wait between two batches can't form. Deliberately minimal: deadlocks against other `OutgoingRequest` writers (e.g. `recoverStaleOutgoingRequests`) are theoretically still possible but not evidenced in the Sentry events; if 40P01 reappears, a retry can be added then. ## Password reset: 404 instead of 500 for deleted users (STACK-BACKEND-1JD) Password reset codes embed the `user_id` at issuance time, so a code can be redeemed after the user was deleted (observed in prod from a scripted client replaying a reset code). The `usersCrudHandlers.adminUpdate` call didn't declare `UserNotFound` via `allowedErrorTypes`, so the CRUD handler wrapped it in `CrudHandlerInvocationError` and the endpoint returned an internal 500. Now declares it and lets the client-safe `USER_NOT_FOUND` KnownError propagate as a 404, matching the ~10 sibling call sites that already do this. ## Testing - New e2e test: resetting the password of a deleted user returns 404 `USER_NOT_FOUND` (`auth/password/reset.test.ts`) — verified passing locally against the full stack - Backend + e2e typecheck and lint pass - No new tests for the other three: the ignore-list entry is covered by `satisfies Stripe.Event.Type[]` and the existing unknown-type webhook tests; the X fix would need the X API base URL to be injectable plus a mock user-info endpoint (possible follow-up); the deadlock is a Postgres timing race that can't be deterministically reproduced through the e2e API surface <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved password reset handling when the account no longer exists. * Added clearer guidance for X API configuration errors. * Prevented unsupported Stripe refund update events from triggering processing. * Ensured external database synchronization requests are deduplicated and consistently ordered. * **Tests** * Added coverage for password reset attempts involving deleted accounts. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|---|---|---|
| .. | ||
| backend | ||
| general | ||
| helpers | ||
| js | ||
| globals.d.ts | ||
| helpers.ts | ||
| load-env.ts | ||
| setup.ts | ||
| snapshot-serializer.ts | ||