fix(api): bound webauthn_challenges growth by superseding all prior rows

The supersede DELETE in CreatePasskeyChallenge filtered consumed_at IS NULL, so it only reaped the prior LIVE challenge; the row that each finish stamps consumed_at on was left behind. A begin->finish loop therefore accumulated one dead row per cycle, unbounded. Drop the consumed_at clause so a fresh begin reaps ALL prior rows for (user, purpose), bounding the table at one row per (user, purpose) with zero net growth per cycle. Deleting an already-consumed row is safe: it has been redeemed and nothing reads it. The fake mirrors the widened supersede.
This commit is contained in:
flyemoji committed 2026-07-02 06:55:21 +09:00
1 parent cdbb5abc35
commit 99532759b2
2 files changed
+14 -10

No files matched your search

+6 -5
View File
@@ -285,12 +285,13 @@ func (f *fakeRepo) VerifyEmailOTP(_ context.Context, userID, purpose, codeHash s
}
// CreatePasskeyChallenge / ConsumePasskeyChallengeByUser mirror PGRepo's contract so
// the hermetic tests exercise the same semantics: a fresh begin supersedes the prior
// live challenge for (user, purpose), and the consume path redeems the newest live one
// (expiry checked before consuming), single-use.
// the hermetic tests exercise the same semantics: a fresh begin supersedes ALL prior
// rows for (user, purpose) — live, consumed, or expired — so the table holds at most one
// row per (user, purpose), and the consume path redeems the newest live one (expiry
// checked before consuming), single-use.
func (f *fakeRepo) CreatePasskeyChallenge(_ context.Context, id, userID, purpose string, sessionData []byte, expiresAt time.Time) error {
for k, c := range f.passkeyChallenges { // supersede prior live (DELETE ... consumed_at IS NULL)
if c.userID == userID && c.purpose == purpose && !c.consumed {
for k, c := range f.passkeyChallenges { // supersede all prior (DELETE ... user_id=$1 AND purpose=$2)
if c.userID == userID && c.purpose == purpose {
delete(f.passkeyChallenges, k)
}
}