Skip to content

Bug: setDefaultCard transaction allows zero-default state under concurrent requests #566

@Srejoye

Description

@Srejoye

apps/backend/src/services/cardService.tssetDefaultCard() runs:

await app.prisma.$transaction(async (tx) => {
  await tx.card.updateMany({ where: { userId }, data: { isDefault: false } });
  await tx.card.update({ where: { id }, data: { isDefault: true } });
});

This transaction does not specify an isolation level (unlike createCard, which explicitly uses Serializable). Under the Postgres default Read Committed isolation, two concurrent setDefaultCard calls for the same user (e.g. double-tap on "set as default" in the UI, or two browser tabs) can interleave such that:

  • Tx1 clears all isDefault flags for the user.
  • Tx2 clears all isDefault flags for the user.
  • Tx1 sets card A as default.
  • Tx2 sets card B as default.

Final state: both A and B are momentarily isDefault: true only if both updates land — but more commonly the lost-update pattern produces a window where the updateMany from Tx2 (clear all) commits after Tx1's update (set A default), wiping out A's default flag without setting B's, depending on commit ordering, leaving zero cards marked default for that user.

This directly desyncs from createCard's invariant (isDefault: cardCount === 0 — exactly one default on creation) and deleteCard's invariant (always promotes a new default when the default card is deleted). Any UI/profile-rendering code that assumes exactly one isDefault: true card exists per user will silently show no default card or crash on .find(c => c.isDefault) returning undefined.

Affected: apps/backend/src/services/cardService.ts (setDefaultCard), contrast with createCard and deleteCard in the same file which maintain the "exactly one default" invariant carefully.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions