Skip to content

fix: attempts in flight count against the sign-in limit - #942

Merged
blaipr merged 1 commit into
mainfrom
fix/attempts-in-flight-count-against-the-limit
Sep 24, 2026
Merged

blaipr merged 1 commit into
mainfrom
fix/attempts-in-flight-count-against-the-limit

Conversation

@blaipr

@blaipr blaipr commented Sep 24, 2026

Copy link
Copy Markdown
Member

The brute-force limiter (TrackService::checkTracking()) counted a source's recent attempts, the caller then ran the whole attempt, and a failure was recorded only at the end. On a sign-in, that attempt includes a bcrypt verify of about 277ms. Attempts sent together therefore all counted the same rows and all passed, so the ten-attempt limit let through as many guesses per window as there were workers to run them. The guard and the change it guards were separate statements with the slow part between them.

Change

  • checkTracking() records the attempt before counting and leaves its own row out of the count. Of any two concurrent attempts, the one that counts second sees the first.
  • New TrackService::release() withdraws that row once the attempt is decided. A failure has recorded its own row by then, so the counts come out exactly as if the attempts had arrived one at a time. A successful attempt costs nothing, which matters for an office signing in from behind one address.
  • Every door that calls checkTracking() releases in a finally:
    • Login::doLogin()
    • Api::setup()
    • both password-reset save actions
  • A failed release is logged, not raised, so it can't replace the answer the attempt earned.
  • TrackRepository::deleteByIdBatch() is added for the release.

Tests

  • Unit (TrackTest):
    • the row is recorded before counting
    • an attempt's own row doesn't count against it
    • release withdraws exactly what was recorded, and only once
    • a failed release doesn't throw
  • Integration (BruteForceTrackingTest, real DB): two requests from one address, interleaved with nine attempts already standing. The second is refused while the first is in flight, and once both are released a third request is back under the limit.
  • ApiTest and RefusalsTest assert that each door releases on both success and refusal.

Mutation-verified:

  • Removing the reservation fails the three TrackTest ordering/count/release tests and the integration interleaving test.
  • Removing the finally releases fails the door tests.

@blaipr
blaipr merged commit 3906611 into main Sep 24, 2026
8 checks passed
@blaipr
blaipr deleted the fix/attempts-in-flight-count-against-the-limit branch September 24, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant