Skip to content

fix: Account lockout does not reset failed login count after duration expires#10538

Open
dblythy wants to merge 1 commit into
parse-community:alphafrom
dblythy:fix/account-lockout-reset
Open

fix: Account lockout does not reset failed login count after duration expires#10538
dblythy wants to merge 1 commit into
parse-community:alphafrom
dblythy:fix/account-lockout-reset

Conversation

@dblythy

@dblythy dblythy commented Jul 4, 2026

Copy link
Copy Markdown
Member

Closes #9386

Summary by CodeRabbit

  • Bug Fixes

    • Account lockouts now expire cleanly, so failed login counters reset after the lockout period ends.
    • After a lockout window expires, a new failed login won’t immediately re-lock the account.
    • Successful logins continue to work normally once the lockout window has reset.
  • Tests

    • Added coverage for lockout expiry and post-expiry login behavior.

@parse-github-assistant

Copy link
Copy Markdown

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a helper method to reset the failed login count and lockout expiration when a previous lockout has expired, rewrites handleLoginAttempt to async/await incorporating this reset before enforcing lockout thresholds, and adds a regression test validating post-expiry login behavior.

Changes

Account Lockout Expiry Reset

Layer / File(s) Summary
Expired lockout reset and async login flow
src/AccountLockout.js
Adds _resetFailedLoginCountIfExpired() to clear _failed_login_count and remove _account_lockout_expires_at when a stored lockout has expired, ignoring OBJECT_NOT_FOUND and rethrowing other errors; rewrites handleLoginAttempt(loginSuccessful) as async, calling the reset on failed logins before invoking _handleFailedLoginAttempt().
Regression test for post-expiry behavior
spec/AccountLockoutPolicy.spec.js
Adds an async test that locks an account via repeated failures, waits for the lockout window to expire, then verifies a subsequent failure doesn't immediately re-lock and a correct login succeeds.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AccountLockout
  participant UserRecord

  Client->>AccountLockout: handleLoginAttempt(loginSuccessful=false)
  AccountLockout->>UserRecord: _notLocked()
  UserRecord-->>AccountLockout: lockout has expired
  AccountLockout->>AccountLockout: _resetFailedLoginCountIfExpired()
  AccountLockout->>UserRecord: reset failed_login_count, delete lockout_expires_at
  AccountLockout->>AccountLockout: _handleFailedLoginAttempt()
  AccountLockout-->>Client: fresh attempt window
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description only says Closes #9386`` and omits the required Issue, Approach, and Tasks sections. Add the Issue, Approach, and Tasks sections from the template, and briefly describe the fix and completed checklist items.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the required fix: prefix and accurately summarizes the lockout reset bug.
Linked Issues check ✅ Passed The code and test changes implement the lockout-threshold reset after expiration described in #9386.
Out of Scope Changes check ✅ Passed The changes stay focused on the lockout fix and its regression test, with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Security Check ✅ Passed No security regression found; the change only resets stale lockout state and keeps atomic threshold enforcement.
Engage In Review Feedback ✅ Passed No substantive review feedback is visible; only a bot-triggered review request appears, so there was nothing to ignore or resolve.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.66%. Comparing base (cce91e5) to head (4a669a9).
⚠️ Report is 1 commits behind head on alpha.

Files with missing lines Patch % Lines
src/AccountLockout.js 92.30% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            alpha   #10538      +/-   ##
==========================================
- Coverage   92.66%   92.66%   -0.01%     
==========================================
  Files         193      193              
  Lines       16981    16988       +7     
  Branches      248      248              
==========================================
+ Hits        15736    15742       +6     
- Misses       1224     1225       +1     
  Partials       21       21              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dblythy

dblythy commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/AccountLockout.js (1)

59-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate OBJECT_NOT_FOUND swallow-catch.

The catch block here is identical to the one in _setLockoutExpiration (Lines 59-71). Consider extracting a small shared helper to avoid drift between the two copies.

♻️ Suggested helper extraction
+  _ignoreObjectNotFound(err) {
+    if (
+      err &&
+      err.code &&
+      err.message &&
+      err.code === Parse.Error.OBJECT_NOT_FOUND &&
+      err.message === 'Object not found.'
+    ) {
+      return;
+    }
+    throw err;
+  }
+
   _setLockoutExpiration() {
     ...
-    return this._config.database.update('_User', query, updateFields).catch(err => {
-      if (
-        err &&
-        err.code &&
-        err.message &&
-        err.code === Parse.Error.OBJECT_NOT_FOUND &&
-        err.message === 'Object not found.'
-      ) {
-        return;
-      } else {
-        throw err;
-      }
-    });
+    return this._config.database
+      .update('_User', query, updateFields)
+      .catch(this._ignoreObjectNotFound);
   }

Also applies to: 138-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/AccountLockout.js` around lines 59 - 71, The OBJECT_NOT_FOUND
swallow-catch logic is duplicated in AccountLockout, so extract the shared
handling into a small helper and reuse it from both the update path and
_setLockoutExpiration. Move the identical err.code/err.message check into a
uniquely named helper near the existing methods, then have both catch blocks
delegate to it and return/throw based on the helper’s result to keep the
behavior in sync and avoid drift.
spec/AccountLockoutPolicy.spec.js (1)

345-368: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Tight timing margin risks flakiness.

The lockout expires after 3000ms, but the test only waits 3100ms — a 100ms buffer that also has to absorb the latency of the signup + two failed-login DB round trips performed beforehand. Under CI load this margin could be consumed, causing an intermittent false failure.

Consider widening the buffer (e.g., wait 4000-5000ms, or increase duration slightly) for more headroom.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/AccountLockoutPolicy.spec.js` around lines 345 - 368, The lockout reset
test in AccountLockoutPolicy.spec.js is timing-sensitive and can flake because
the wait after triggering lockout is too close to the configured duration.
Update the test named “allows a fresh set of attempts after the lockout duration
expires” to give more headroom by either increasing the `setTimeout` wait or
slightly raising the `accountLockout.duration` value, keeping the existing flow
through `reconfigureServer`, `loginWithWrongCredentialsShouldFail`, and
`Parse.User.logIn` unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@spec/AccountLockoutPolicy.spec.js`:
- Around line 345-368: The lockout reset test in AccountLockoutPolicy.spec.js is
timing-sensitive and can flake because the wait after triggering lockout is too
close to the configured duration. Update the test named “allows a fresh set of
attempts after the lockout duration expires” to give more headroom by either
increasing the `setTimeout` wait or slightly raising the
`accountLockout.duration` value, keeping the existing flow through
`reconfigureServer`, `loginWithWrongCredentialsShouldFail`, and
`Parse.User.logIn` unchanged.

In `@src/AccountLockout.js`:
- Around line 59-71: The OBJECT_NOT_FOUND swallow-catch logic is duplicated in
AccountLockout, so extract the shared handling into a small helper and reuse it
from both the update path and _setLockoutExpiration. Move the identical
err.code/err.message check into a uniquely named helper near the existing
methods, then have both catch blocks delegate to it and return/throw based on
the helper’s result to keep the behavior in sync and avoid drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f91ce09-fb66-4585-9665-4df77aab2097

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9d53a and 4a669a9.

📒 Files selected for processing (2)
  • spec/AccountLockoutPolicy.spec.js
  • src/AccountLockout.js

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.

accountLockout not resetting threshold after configured duration

1 participant