Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1248,4 +1248,20 @@ public ResponseEntity<?> checkUserDetails(@PathVariable("userName") String userN
}

}

@Operation(summary = "Manually unlock a user account")
@RequestMapping(value = "/unlockUserAccount", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON, headers = "Authorization")
public String unlockUserAccount(
@RequestParam("userID") Long userID) {
OutputResponse response = new OutputResponse();
try {
logger.info("Unlocking user account for userID: {}", userID);
String resultMessage = iemrAdminUserServiceImpl.manuallyUnlockUserAccount(userID);
response.setResponse(resultMessage);
} catch (Exception e) {
logger.error("Error unlocking user account with ID: " + userID, e);
response.setError(e);
}
return response.toString();
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/iemr/common/data/users/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ public class User implements Serializable {
@Column(name = "failed_attempt")
private Integer failedAttempt;

@Expose
@Column(name = "locked_at")
private Timestamp lockedAt;

@Expose
@Column(name = "dhistoken")
private String dhistoken;
Expand Down Expand Up @@ -540,6 +544,14 @@ public String getDhistoken() {
return dhistoken;
}

public Timestamp getLockedAt() {
return lockedAt;
}

public void setLockedAt(Timestamp lockedAt) {
this.lockedAt = lockedAt;
}

/*
* public User(String userName, String password) { this.userName = userName;
* this.password = password; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,15 +302,52 @@ public List<User> userAuthenticate(String userName, String password) throws Exce
}

private void checkUserLoginFailedAttempt(User user) throws IEMRException {

if (user.getDeleted() != null && user.getDeleted() && user.getLockedAt() != null) {
long lockedTime = user.getLockedAt().getTime();
long currentTime = System.currentTimeMillis();
long diffInMillis = currentTime - lockedTime;
long diffInHours = diffInMillis / (1000 * 60 * 60);

// If 24 hours have passed, auto-unlock the account
if (diffInHours >= 24) {
user.setDeleted(false);
user.setFailedAttempt(0);
user.setLockedAt(null);
iEMRUserRepositoryCustom.save(user);
logger.info("User account auto-unlocked after 24 hours. User ID: {}", user.getUserID());
}
}
}
Comment on lines 304 to 320
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 | πŸ—οΈ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for admin unlock or account unlock endpoints
echo "=== Searching for unlock-related endpoints ==="
rg -n -i 'unlock|unlockaccount' --type=java -C3

echo -e "\n=== Searching for admin user management endpoints ==="
rg -n -i '@PostMapping.*admin.*user|@PutMapping.*admin.*user' --type=java -C3

echo -e "\n=== Checking if deleted field is updated via admin endpoints ==="
rg -n 'setDeleted\(false\)' --type=java -C3

Repository: PSMRI/Common-API

Length of output: 27199


🏁 Script executed:

# Search for admin controller files
echo "=== Looking for admin controller files ==="
fd -type f -name "*Admin*Controller*" src/

echo -e "\n=== Searching for updateUser or similar admin endpoints ==="
rg -n 'public.*User.*update|@PutMapping|@PostMapping' src/main/java/com/iemr/common/controller/users/ -A3 --type=java

echo -e "\n=== Checking for methods that set deleted=false for users (admin context) ==="
rg -n 'public.*setDeleted|unlockAccount|unlockUser' src/main/java/com/iemr/common/service/users/ --type=java -A5

echo -e "\n=== Search for any endpoint mapping patterns for user unlock/update ==="
rg -n '@RequestMapping|@GetMapping|@PostMapping|@PutMapping|@DeleteMapping' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java --type=java | head -30

Repository: PSMRI/Common-API

Length of output: 4397


🏁 Script executed:

# Find the actual admin controller file
echo "=== Finding admin controller file ==="
find src -name "*IEMRAdminController*" -type f

echo -e "\n=== Check the service methods available in IEMRAdminUserServiceImpl ==="
rg -n 'public.*User|public.*save|public.*update' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java --type=java | head -40

echo -e "\n=== Check if there's an admin update user method ==="
cat src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java | head -100

echo -e "\n=== List all public methods in IEMRAdminUserServiceImpl ==="
rg -n '^[[:space:]]*public' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java --type=java

Repository: PSMRI/Common-API

Length of output: 11309


🏁 Script executed:

# Get all endpoints in IEMRAdminController
echo "=== All RequestMapping methods in IEMRAdminController ==="
rg -n '@RequestMapping|@PostMapping|@PutMapping|@GetMapping|@DeleteMapping' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java --type=java -A2 | head -80

echo -e "\n=== Search for any user update/save/modify endpoint in the controller ==="
rg -n 'updateUser|saveUser|modifyUser|unlockAccount|adminUnlock' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java --type=java -C3

echo -e "\n=== Check entire controller file size ==="
wc -l src/main/java/com/iemr/common/controller/users/IEMRAdminController.java

Repository: PSMRI/Common-API

Length of output: 7196


🏁 Script executed:

# Check the rest of the controller file for update endpoints
echo "=== Search in entire controller for any endpoint that might update user ==="
rg -n 'method = RequestMethod.PUT|method = RequestMethod.PATCH|@PutMapping|@PatchMapping' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java --type=java

echo -e "\n=== Check all RequestMapping values (full list) ==="
rg -n '@RequestMapping\(value = "' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java --type=java | tail -20

echo -e "\n=== Check if there's any user admin API/endpoint file ==="
find src -name "*AdminAPI*" -o -name "*UserAdmin*" -o -name "*AdminUser*" | grep -i controller

echo -e "\n=== Final comprehensive search for admin-related endpoints ==="
rg -rn 'class.*Admin.*Controller' src/main/java/com/iemr/common/controller/ --type=java

Repository: PSMRI/Common-API

Length of output: 3272


🏁 Script executed:

# One final comprehensive check for any user update capability
echo "=== Search all Java files for any unlock or admin user update methods ==="
rg -n 'adminUnlock|manualUnlock|unlockUser|unlockAccount' src/main/java --type=java

echo -e "\n=== Check if User model has any unlock-related methods ==="
rg -n 'public.*unlock|setDeleted.*admin|unlock.*method' src/main/java/com/iemr/common/data/users/User.java --type=java

echo -e "\n=== Verify the User.java has `@Expose` annotation on lockedAt field ==="
rg -n '@Expose.*lockedAt|lockedAt.*@Expose|private.*lockedAt' src/main/java/com/iemr/common/data/users/User.java --type=java -B2 -A2

Repository: PSMRI/Common-API

Length of output: 362


Add endpoint for manual admin account unlock.

The PR exposes account lock status via the @Expose annotation on the lockedAt field in User.java, fulfilling one requirement. However, no admin endpoint exists for manually unlocking accountsβ€”only automatic unlock after 24 hours is implemented in checkUserLoginFailedAttempt().

Add a service method and controller endpoint to allow admins to manually unlock accounts:

Example implementation
// In IEMRAdminUserServiceImpl
public void adminUnlockAccount(Long userId) throws IEMRException {
    User user = iEMRUserRepositoryCustom.findUserByUserID(userId);
    if (user != null) {
        user.setDeleted(false);
        user.setFailedAttempt(0);
        user.setLockedAt(null);
        iEMRUserRepositoryCustom.save(user);
        logger.info("Admin unlocked user account. User ID: {}", userId);
    }
}

// In IEMRAdminController
`@PostMapping`("/unlockUserAccount")
public String unlockUserAccount(`@RequestBody` Map<String, Long> request) {
    Long userId = request.get("userID");
    iemrAdminUserServiceImpl.adminUnlockAccount(userId);
    // Return response
}
πŸ€– 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/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java`
around lines 304 - 320, Add a manual admin unlock: implement a new service
method adminUnlockAccount(Long userId) in IEMRAdminUserServiceImpl that looks up
the user via iEMRUserRepositoryCustom.findUserByUserID(userId), returns/throws
if not found, sets user.setDeleted(false), user.setFailedAttempt(0),
user.setLockedAt(null), saves with iEMRUserRepositoryCustom.save(user) and logs
via logger.info("Admin unlocked user account. User ID: {}", userId);
declare/throw IEMRException as needed. Expose a controller endpoint in
IEMRAdminController (e.g., `@PostMapping`("/unlockUserAccount")) which accepts a
request containing userID, validates input, calls
iemrAdminUserServiceImpl.adminUnlockAccount(userId), handles exceptions and
returns an appropriate success/failure response. Ensure null checks, proper
exception handling, and permission (admin) guard where applicable.


private void updateUserLoginFailedAttempt(User user) throws IEMRException {
int failedAttempt = 0;
if (failedLoginAttempt != null)
failedAttempt = Integer.parseInt(failedLoginAttempt);
else
failedAttempt = 5;

user.setFailedAttempt(user.getFailedAttempt() != null ? user.getFailedAttempt() + 1 : 1);

if (user.getFailedAttempt() >= failedAttempt) {
user.setDeleted(true);
user.setLockedAt(new java.sql.Timestamp(System.currentTimeMillis()));
logger.warn("User Account has been locked after reaching the limit of {} failed login attempts. User ID: {}, Lock time: {}",
failedAttempt, user.getUserID(), user.getLockedAt());
} else {
logger.warn("Failed login attempt {} of {} for user ID: {}",
user.getFailedAttempt(), failedAttempt, user.getUserID());
}

iEMRUserRepositoryCustom.save(user);
}

private void resetUserLoginFailedAttempt(User user) throws IEMRException {

if (user.getFailedAttempt() != null && user.getFailedAttempt() > 0) {
user.setFailedAttempt(0);
user.setLockedAt(null);
iEMRUserRepositoryCustom.save(user);
logger.info("User failed login attempts reset. User ID: {}", user.getUserID());
}
}

/**
Expand All @@ -323,17 +360,20 @@ public User superUserAuthenticate(String userName, String password) throws Excep
if (users.size() != 1) {
throw new IEMRException("Invalid username or password");
}
int failedAttempt = 0;
if (failedLoginAttempt != null)
failedAttempt = Integer.parseInt(failedLoginAttempt);
else
failedAttempt = 5;

User user = users.get(0);

// Check if user account should be auto-unlocked after 24 hours
checkUserLoginFailedAttempt(user);

try {
int validatePassword;
validatePassword = securePassword.validatePassword(password, user.getPassword());
if (validatePassword == 1) {
checkUserAccountStatus(user);
// Reset failed login attempts on successful password validation
resetUserLoginFailedAttempt(user);

int iterations = 1001;
char[] chars = password.toCharArray();
byte[] salt = getSalt();
Expand All @@ -348,37 +388,19 @@ public User superUserAuthenticate(String userName, String password) throws Excep

} else if (validatePassword == 2) {
checkUserAccountStatus(user);
// Reset failed login attempts on successful password validation
resetUserLoginFailedAttempt(user);
iEMRUserRepositoryCustom.save(user);

} else if (validatePassword == 0) {
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");
} else if (user.getFailedAttempt() + 1 >= failedAttempt) {
user.setFailedAttempt(user.getFailedAttempt() + 1);
user.setDeleted(true);
user = iEMRUserRepositoryCustom.save(user);
logger.warn("User Account has been locked after reaching the limit of {} failed login attempts.",
ConfigProperties.getInteger("failedLoginAttempt"));

throw new IEMRException(
"Invalid username or password. Please contact administrator.");
} else {
user.setFailedAttempt(user.getFailedAttempt() + 1);
user = iEMRUserRepositoryCustom.save(user);
logger.warn("Failed login attempt {} of {} for a user account.",
user.getFailedAttempt(), ConfigProperties.getInteger("failedLoginAttempt"));
throw new IEMRException(
"Invalid username or password. Please contact administrator.");
}
// Update failed login attempts and lock account if threshold reached
updateUserLoginFailedAttempt(user);
logger.warn("User Password Wrong. Failed attempts: {}", user.getFailedAttempt());
throw new IEMRException("Invalid username or password");
} else {
checkUserAccountStatus(user);
if (user.getFailedAttempt() != 0) {
user.setFailedAttempt(0);
user = iEMRUserRepositoryCustom.save(user);
}
// Reset failed login attempts on successful login
resetUserLoginFailedAttempt(user);
}
} catch (Exception e) {
throw new IEMRException(e.getMessage());
Expand Down Expand Up @@ -1230,4 +1252,36 @@ public List<User> findUserIdByUserName(String userName) {

return iEMRUserRepositoryCustom.findUserName(userName);
}

/**
* Manually unlock a user account by resetting failed login attempts and lock timestamp
* @param userID - The ID of the user to unlock
* @return - Success message
* @throws IEMRException - If user is not found or operation fails
*/
@Override
public String manuallyUnlockUserAccount(Long userID) throws IEMRException {
try {
User user = iEMRUserRepositoryCustom.findByUserID(userID);

if (user == null) {
throw new IEMRException("User not found with ID: " + userID);
}

// Reset the account lock
user.setDeleted(false);
user.setFailedAttempt(0);
user.setLockedAt(null);

iEMRUserRepositoryCustom.save(user);
logger.info("User account manually unlocked. User ID: {}, User Name: {}", userID, user.getUserName());

return "User account with ID " + userID + " has been successfully unlocked.";
} catch (IEMRException e) {
throw e;
} catch (Exception e) {
logger.error("Error unlocking user account with ID: " + userID, e);
throw new IEMRException("Error unlocking user account: " + e.getMessage(), e);
}
}
}