Skip to content

implement time-bound account lock with auto-unlock after 24h#394

Open
neha222222 wants to merge 2 commits intoPSMRI:mainfrom
neha222222:feature/time-bound-account-lock
Open

implement time-bound account lock with auto-unlock after 24h#394
neha222222 wants to merge 2 commits intoPSMRI:mainfrom
neha222222:feature/time-bound-account-lock

Conversation

@neha222222
Copy link
Copy Markdown

@neha222222 neha222222 commented Apr 12, 2026

  • added lock_timestamp column to m_user table
  • on 5th failed login, account gets locked with timestamp instead of permanent block
  • on next login attempt after 24h, account auto-unlocks (resets deleted flag, failed attempts, and lock timestamp)
  • updated error message: "Your account has been locked. You can try tomorrow or connect to the administrator."
  • failed attempt counter resets on successful login

fixes PSMRI/AMRIT#118

📋 Description

JIRA ID:

Please provide a summary of the change and the motivation behind it. Include relevant context and details.


✅ Type of Change

  • 🐞 Bug fix (non-breaking change which resolves an issue)
  • New feature (non-breaking change which adds functionality)
  • 🔥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 🛠 Refactor (change that is neither a fix nor a new feature)
  • ⚙️ Config change (configuration file or build script updates)
  • 📚 Documentation (updates to docs or readme)
  • 🧪 Tests (adding new or updating existing tests)
  • 🎨 UI/UX (changes that affect the user interface)
  • 🚀 Performance (improves performance)
  • 🧹 Chore (miscellaneous changes that don't modify src or test files)

ℹ️ Additional Information

Please describe how the changes were tested, and include any relevant screenshots, logs, or other information that provides additional context.

Summary by CodeRabbit

  • New Features

    • Accounts locked due to repeated failed logins now record a lock time and will auto-unlock after the lock period (24 hours).
  • Bug Fixes

    • Improved authentication flow to preserve and respect existing lock timestamps.
    • Clarified error messages for locked or invalid login attempts and ensured lock timestamps are set/cleared correctly.

- added lock_timestamp column to m_user table
- on 5th failed login, account gets locked with timestamp
  instead of permanent block
- on next login attempt after 24h, account auto-unlocks
  (resets deleted flag, failed attempts, and lock timestamp)
- updated error message: "Your account has been locked. You can
  try tomorrow or connect to the administrator."
- failed attempt counter resets on successful login

fixes PSMRI/AMRIT#118
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

A time-bound account locking mechanism was added: a persisted lockTimestamp on User, authentication now records/clears that timestamp and auto-unlocks accounts after the lock period, and a DB migration adds the lock_timestamp column.

Changes

Cohort / File(s) Summary
Data Model
src/main/java/com/iemr/common/data/users/User.java
Added private Timestamp lockTimestamp mapped to lock_timestamp column and annotated with @Expose.
Authentication Service
src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java
Auth flows updated: on failed attempts set lockTimestamp when threshold reached; on successful auth reset lockTimestamp; checkUserAccountStatus auto-unlocks when current time exceeds lock timestamp; adjusted error messages and user lookup for super-user.
Database Schema / Migration
src/main/resources/db/migration/V1__add_lock_timestamp_to_m_user.sql
Adds nullable lock_timestamp DATETIME column to m_user table (schema migration).

Sequence Diagram

sequenceDiagram
    participant User
    participant AuthService as Auth Service
    participant UserDB as User DB
    participant Clock as System Clock

    User->>AuthService: Login (username + password)
    AuthService->>UserDB: Fetch user
    UserDB-->>AuthService: User record (may include lockTimestamp)
    alt user locked (lockTimestamp present)
        AuthService->>Clock: Get current time
        Clock-->>AuthService: Current timestamp
        alt current >= lockTimestamp + 24h
            AuthService->>AuthService: Auto-unlock (clear deleted/failedAttempt/lockTimestamp)
            AuthService->>UserDB: Save unlocked user
            UserDB-->>AuthService: Saved
            AuthService->>AuthService: Continue authentication
        else
            AuthService-->>User: "Account locked...try tomorrow"
        end
    end
    alt password invalid
        AuthService->>AuthService: Increment failedAttempt
        alt failedAttempt >= threshold
            AuthService->>Clock: Get current time
            Clock-->>AuthService: Current timestamp
            AuthService->>AuthService: Set lockTimestamp
            AuthService->>UserDB: Save locked user
            UserDB-->>AuthService: Saved
            AuthService-->>User: "Account locked...try tomorrow"
        else
            AuthService->>UserDB: Save updated failedAttempt
            UserDB-->>AuthService: Saved
            AuthService-->>User: "Invalid username or password"
        end
    else password valid
        AuthService->>AuthService: Reset failedAttempt and clear lockTimestamp
        AuthService->>UserDB: Save user
        UserDB-->>AuthService: Saved
        AuthService-->>User: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐇 I set a timestamp in the night,

five wrong hops and doors go tight.
Wait till the sun has run its round,
I nudge the latch and turn it sound.
Tomorrow's carrot — login's light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing time-bound account locking with automatic 24-hour unlock, which is the primary objective of the pull request.
Linked Issues check ✅ Passed The PR implements core backend requirements: time-bound account locking after 5 failed attempts, auto-unlock via timestamp comparison, updated error message, and failed-attempt reset on success. Admin Panel exposure and admin unlock functionality are not included in this backend-only PR.
Out of Scope Changes check ✅ Passed All changes align with linked issue #118 requirements: lock_timestamp field addition, authentication flow updates, and database migration. The superuser lookup method change is a supporting adjustment for the lock mechanism.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java (2)

287-297: ⚠️ Potential issue | 🔴 Critical

Don’t rewrite lockTimestamp for users who are already deleted.

userAuthenticate() loads deleted rows via findByUserNameNew(), but this branch runs before any deleted/lock guard. If a locked user submits a wrong password, Line 296 overwrites the original lock time and effectively extends the lock window on every attempt. The same path can also stamp a lockTimestamp onto a manually deactivated account, making it auto-unlockable later.

🔒 Minimal guard to stop mutating already-deleted accounts
 			} else if (validatePassword == 0) {
+				if (Boolean.TRUE.equals(user.getDeleted())) {
+					checkUserAccountStatus(user);
+				}
 				if (user.getFailedAttempt() + 1 < failedAttempt) {
 					user.setFailedAttempt(user.getFailedAttempt() + 1);
 					user = iEMRUserRepositoryCustom.save(user);
 					logger.warn("User Password Wrong");
 					throw new IEMRException("Invalid username or password");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`
around lines 287 - 297, The code in userAuthenticate() increments failed
attempts and unconditionally sets user.setDeleted(true) and
user.setLockTimestamp(...) when failedAttempt threshold is reached, which
overwrites existing lockTimestamp or stamps deactivated accounts; guard this by
checking user.isDeleted() (or user.getDeleted()) and only setLockTimestamp and
setDeleted when the user is not already deleted/locked. Specifically, in the
branch handling validatePassword == 0 where you call user.setFailedAttempt(...),
add a conditional around the block that sets user.setDeleted(true) and
user.setLockTimestamp(...) (and saves) so those calls only run if
user.getDeleted() == false (and/or existing lockTimestamp is null), leaving
existing deleted/locked users' timestamps untouched; keep updating failedAttempt
for audit but avoid mutating lockTimestamp/deleted for already-deleted users.

342-390: ⚠️ Potential issue | 🟠 Major

Locked superusers can’t auto-unlock with this repository call.

superUserAuthenticate() still uses findByUserName(), and that query filters on deleted=false. A temporarily locked superuser is therefore never loaded, checkUserAccountStatus() never runs, and the flow falls out as “Invalid username or password” instead of auto-unlocking after the lock window. Cross-file evidence: src/main/java/com/iemr/common/repository/users/IEMRUserRepositoryCustom.java:39-46.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`
around lines 342 - 390, superUserAuthenticate loads users via
iEMRUserRepositoryCustom.findByUserName which filters out deleted=true, so
temporarily locked (deleted=true) superusers are never fetched and cannot be
auto-unlocked by checkUserAccountStatus; update the data access to fetch the
user regardless of deleted flag (either add/use a new repository method like
findByUserNameIncludeDeleted or change the query behind findByUserName to not
filter deleted for admin flow), then call checkUserAccountStatus(user) as before
so locked accounts within the unlock window are un-locked and processed
normally; ensure any save() calls still set deleted/lockTimestamp appropriately
and that other code paths that rely on filtered queries are not broken.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`:
- Around line 227-241: The current check in IEMRAdminUserServiceImpl (using
user.getLockTimestamp(), lockTime, now, twentyFourHoursMs) enforces a rolling
24-hour lock; replace it with a calendar-day-based check: convert
user.getLockTimestamp() to a LocalDate (using the application's ZoneId) and
compare it to LocalDate.now(); if the lock date is before today then auto-unlock
(reset deleted, failedAttempt, lockTimestamp and call
iEMRUserRepositoryCustom.save and log), otherwise throw the IEMRException. This
ensures any lock that occurred on a prior calendar day is unlocked after
midnight rather than waiting a full 24 hours.

---

Outside diff comments:
In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`:
- Around line 287-297: The code in userAuthenticate() increments failed attempts
and unconditionally sets user.setDeleted(true) and user.setLockTimestamp(...)
when failedAttempt threshold is reached, which overwrites existing lockTimestamp
or stamps deactivated accounts; guard this by checking user.isDeleted() (or
user.getDeleted()) and only setLockTimestamp and setDeleted when the user is not
already deleted/locked. Specifically, in the branch handling validatePassword ==
0 where you call user.setFailedAttempt(...), add a conditional around the block
that sets user.setDeleted(true) and user.setLockTimestamp(...) (and saves) so
those calls only run if user.getDeleted() == false (and/or existing
lockTimestamp is null), leaving existing deleted/locked users' timestamps
untouched; keep updating failedAttempt for audit but avoid mutating
lockTimestamp/deleted for already-deleted users.
- Around line 342-390: superUserAuthenticate loads users via
iEMRUserRepositoryCustom.findByUserName which filters out deleted=true, so
temporarily locked (deleted=true) superusers are never fetched and cannot be
auto-unlocked by checkUserAccountStatus; update the data access to fetch the
user regardless of deleted flag (either add/use a new repository method like
findByUserNameIncludeDeleted or change the query behind findByUserName to not
filter deleted for admin flow), then call checkUserAccountStatus(user) as before
so locked accounts within the unlock window are un-locked and processed
normally; ensure any save() calls still set deleted/lockTimestamp appropriately
and that other code paths that rely on filtered queries are not broken.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b771db1e-54c8-450e-8b8b-cb66fdb62e6e

📥 Commits

Reviewing files that changed from the base of the PR and between 231773e and 33ad971.

📒 Files selected for processing (3)
  • src/main/java/com/iemr/common/data/users/User.java
  • src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java
  • src/main/resources/db/migration/V1__add_lock_timestamp_to_m_user.sql

…er query

- use LocalDate comparison instead of rolling 24h so accounts
  unlock at midnight as the error message promises
- guard against overwriting lockTimestamp when an already-locked
  user enters wrong password again
- switch superUserAuthenticate to findByUserNameNew so temporarily
  locked superusers can still be loaded and auto-unlocked
- clear lockTimestamp on successful superuser login too
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
35.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`:
- Around line 319-320: In userAuthenticate() and superUserAuthenticate(), the
successful-password branches (where SecurePassword.validatePassword(...) == 1,
== 2, or == 3) must clear the account failure state; currently failedAttempt and
lockTimestamp are only reset in the final else. Update each success branch to
set user.setFailedAttempt(0) and user.setLockTimestamp(null) and persist the
change via iEMRUserRepositoryCustom.save(user) (same persistence call used
elsewhere) so a previously-failed user who now authenticates successfully does
not retain stale failure counts that could re-lock them on a subsequent failure.
- Around line 230-238: The auto-unlock block in IEMRAdminUserServiceImpl
currently clears deleted/failedAttempt/lockTimestamp then returns, which
bypasses the subsequent inactive-account status check; remove the premature
return after the iEMRUserRepositoryCustom.save(user) and logger.info(...) so the
method continues into the existing status check logic (so deactivated accounts
with statusID > 2 are handled properly) while keeping the auto-unlock field
resets and save call intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b1effd2f-1a3c-413d-99da-9e6f598367e1

📥 Commits

Reviewing files that changed from the base of the PR and between 33ad971 and 799c214.

📒 Files selected for processing (1)
  • src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java

Comment on lines +230 to +238
if (today.isAfter(lockDate)) {
// auto-unlock: it's a new day after the lock date
user.setDeleted(false);
user.setFailedAttempt(0);
user.setLockTimestamp(null);
iEMRUserRepositoryCustom.save(user);
logger.info("Account auto-unlocked for user {} (locked on {}, unlocked on {})",
user.getUserName(), lockDate, today);
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't auto-unlock past the inactive-status guard.

If an account is deactivated (statusID > 2) while it still has a historical lockTimestamp, this branch clears deleted and returns before Line 246 runs. The rest of the login flow then proceeds with an inactive account. Let the method continue into the status check after clearing the temporary lock, instead of returning here.

Suggested fix
 		if (user.getDeleted()) {
 			if (user.getLockTimestamp() != null) {
 				java.time.LocalDate lockDate = user.getLockTimestamp().toLocalDateTime().toLocalDate();
 				java.time.LocalDate today = java.time.LocalDate.now();

 				if (today.isAfter(lockDate)) {
 					user.setDeleted(false);
 					user.setFailedAttempt(0);
 					user.setLockTimestamp(null);
 					iEMRUserRepositoryCustom.save(user);
 					logger.info("Account auto-unlocked for user {} (locked on {}, unlocked on {})",
 							user.getUserName(), lockDate, today);
-					return;
 				} else {
 					throw new IEMRException(
 							"Your account has been locked. You can try tomorrow or connect to the administrator.");
 				}
 			}
-			throw new IEMRException(
-					"Your account is locked or de-activated. Please contact administrator");
-		} else if (user.getStatusID() > 2) {
+			if (user.getDeleted()) {
+				throw new IEMRException(
+						"Your account is locked or de-activated. Please contact administrator");
+			}
+		}
+		if (user.getStatusID() > 2) {
 			throw new IEMRException("Your account is not active. Please contact administrator");
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`
around lines 230 - 238, The auto-unlock block in IEMRAdminUserServiceImpl
currently clears deleted/failedAttempt/lockTimestamp then returns, which
bypasses the subsequent inactive-account status check; remove the premature
return after the iEMRUserRepositoryCustom.save(user) and logger.info(...) so the
method continues into the existing status check logic (so deactivated accounts
with statusID > 2 are handled properly) while keeping the auto-unlock field
resets and save call intact.

Comment on lines +319 to 320
user.setLockTimestamp(null);
user = iEMRUserRepositoryCustom.save(user);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SecurePassword.validatePassword contract =="
fd 'SecurePassword.java' src --exec sed -n '1,260p' {}

echo
echo "== validatePassword call sites in IEMRAdminUserServiceImpl =="
rg -n -C3 '\bvalidatePassword\s*\(' --type=java src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java

Repository: PSMRI/Common-API

Length of output: 6138


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full code around first reset (lines 250-330) =="
sed -n '250,330p' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java

echo
echo "== Full code around second reset (lines 340-420) =="
sed -n '340,420p' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java

Repository: PSMRI/Common-API

Length of output: 6292


Reset the failure state in every successful-auth branch.

failedAttempt and lockTimestamp are only cleared in the final else block of each method. Since SecurePassword.validatePassword(...) returns 1, 2, or 3 for successful password matches—and the code handles each distinctly—users who log in successfully via any of these paths retain stale failure counts. A user with prior failures can authenticate successfully but still be re-locked on the next incorrect password attempt.

Move the reset into all three success branches (validatePassword == 1, 2, 3) in both userAuthenticate() and superUserAuthenticate().

This applies to both methods around the locations indicated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`
around lines 319 - 320, In userAuthenticate() and superUserAuthenticate(), the
successful-password branches (where SecurePassword.validatePassword(...) == 1,
== 2, or == 3) must clear the account failure state; currently failedAttempt and
lockTimestamp are only reset in the final else. Update each success branch to
set user.setFailedAttempt(0) and user.setLockTimestamp(null) and persist the
change via iEMRUserRepositoryCustom.save(user) (same persistence call used
elsewhere) so a previously-failed user who now authenticates successfully does
not retain stale failure counts that could re-lock them on a subsequent failure.

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.

[C4GT Community]: Account Lockout After Multiple Failed Login Attempts – Auto Unlock Required After 24 Hours

1 participant