Skip to content

Commit

Permalink
Merge pull request ajaynegi45#82 from Guhapriya01/feature/secure-memb…
Browse files Browse the repository at this point in the history
…er-and-password-update

Enhance Member Management with Secure Updates and Password Management
  • Loading branch information
ajaynegi45 authored Oct 23, 2024
2 parents 3cd9bfe + 60781ee commit 7b8d882
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.libraryman_api.fine.FineRepository;
import com.libraryman_api.member.MemberService;
import com.libraryman_api.member.Members;
import com.libraryman_api.member.MembersDto;
import com.libraryman_api.member.dto.MembersDto;
import com.libraryman_api.notification.NotificationService;

import org.springframework.data.domain.Page;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.libraryman_api.book.BookDto;
import com.libraryman_api.fine.Fines;
import com.libraryman_api.member.MembersDto;
import com.libraryman_api.member.dto.MembersDto;

import java.util.Date;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,64 @@
import java.util.Date;

/**
* Global exception handler for the LibraryMan API.
* This class provides centralized exception handling across all controllers in the application.
* It handles specific exceptions and returns appropriate HTTP responses.
* Global exception handler for the LibraryMan API. This class provides
* centralized exception handling across all controllers in the application. It
* handles specific exceptions and returns appropriate HTTP responses.
*/
@ControllerAdvice
public class GlobalExceptionHandler {

/**
* Handles {@link ResourceNotFoundException} exceptions.
* This method is triggered when a {@code ResourceNotFoundException} is thrown in the application.
* It constructs an {@link ErrorDetails} object containing the exception details and returns
* a {@link ResponseEntity} with an HTTP status of {@code 404 Not Found}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an HTTP status of {@code 404 Not Found}.
*/
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}

/**
* Handles {@link InvalidSortFieldException} exceptions.
* This method is triggered when an {@code InvalidSortFieldException} is thrown in the application.
* It constructs an {@link ErrorDetails} object containing the exception details and returns
* a {@link ResponseEntity} with an HTTP status of {@code 400 Bad Request}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an HTTP status of {@code 400 Bad Request}.
*/
@ExceptionHandler(InvalidSortFieldException.class)
public ResponseEntity<?> invalidSortFieldException(InvalidSortFieldException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
/**
* Handles {@link ResourceNotFoundException} exceptions. This method is
* triggered when a {@code ResourceNotFoundException} is thrown in the
* application. It constructs an {@link ErrorDetails} object containing the
* exception details and returns a {@link ResponseEntity} with an HTTP status of
* {@code 404 Not Found}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an
* HTTP status of {@code 404 Not Found}.
*/
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}

/**
* Handles {@link InvalidSortFieldException} exceptions. This method is
* triggered when an {@code InvalidSortFieldException} is thrown in the
* application. It constructs an {@link ErrorDetails} object containing the
* exception details and returns a {@link ResponseEntity} with an HTTP status of
* {@code 400 Bad Request}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an
* HTTP status of {@code 400 Bad Request}.
*/
@ExceptionHandler(InvalidSortFieldException.class)
public ResponseEntity<?> invalidSortFieldException(InvalidSortFieldException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}

/**
* Handles {@link InvalidPasswordException} exceptions. This method is triggered
* when an {@code InvalidPasswordException} is thrown in the application. It
* constructs an {@link ErrorDetails} object containing the exception details
* and returns a {@link ResponseEntity} with an HTTP status of
* {@code 400 Bad Request}.
*
* @param ex the exception that was thrown.
* @param request the current web request in which the exception was thrown.
* @return a {@link ResponseEntity} containing the {@link ErrorDetails} and an
* HTTP status of {@code 400 Bad Request}.
*/
@ExceptionHandler(InvalidPasswordException.class)
public ResponseEntity<?> invalidPasswordException(InvalidPasswordException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.libraryman_api.exception;

import java.io.Serial;

/**
* Custom exception class to handle scenarios where an invalid password is provided
* in the Library Management System. This exception is thrown when a password update
* operation fails due to invalid password criteria.
*/
public class InvalidPasswordException extends RuntimeException {

/**
* The {@code serialVersionUID} is a unique identifier for each version of a serializable class.
* It is used during the deserialization process to verify that the sender and receiver of a
* serialized object have loaded classes for that object that are compatible with each other.
*
* The {@code serialVersionUID} field is important for ensuring that a serialized class
* (especially when transmitted over a network or saved to disk) can be successfully deserialized,
* even if the class definition changes in later versions. If the {@code serialVersionUID} does not
* match during deserialization, an {@code InvalidClassException} is thrown.
*
* This field is optional, but it is good practice to explicitly declare it to prevent
* automatic generation, which could lead to compatibility issues when the class structure changes.
*
* The {@code @Serial} annotation is used here to indicate that this field is related to
* serialization. This annotation is available starting from Java 14 and helps improve clarity
* regarding the purpose of this field.
*/
@Serial
private static final long serialVersionUID = 1L;

/**
* Constructs a new {@code InvalidPasswordException} with the specified detail message.
*
* @param message the detail message explaining the reason for the exception
*/
public InvalidPasswordException(String message) {
super(message);
}

/**
* Constructs a new {@code InvalidPasswordException} with the specified detail message and cause.
*
* @param message the detail message explaining the reason for the exception
* @param cause the cause of the exception (which is saved for later retrieval by the {@link #getCause()} method)
*/
public InvalidPasswordException(String message, Throwable cause) {
super(message, cause);
}
}
36 changes: 32 additions & 4 deletions src/main/java/com/libraryman_api/member/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package com.libraryman_api.member;

import com.libraryman_api.exception.ResourceNotFoundException;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.libraryman_api.exception.ResourceNotFoundException;
import com.libraryman_api.member.dto.MembersDto;
import com.libraryman_api.member.dto.UpdateMembersDto;
import com.libraryman_api.member.dto.UpdatePasswordDto;

/**
* REST controller for managing library members.
Expand Down Expand Up @@ -83,7 +93,8 @@ public ResponseEntity<MembersDto> getMemberById(@PathVariable int id) {
* @return the updated {@link Members} object
*/
@PutMapping("/{id}")
public MembersDto updateMember(@PathVariable int id, @RequestBody MembersDto membersDtoDetails) {
@PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN') or (hasRole('USER') and #id == authentication.principal.memberId)")
public MembersDto updateMember(@PathVariable int id, @RequestBody UpdateMembersDto membersDtoDetails) {
return memberService.updateMember(id, membersDtoDetails);
}

Expand All @@ -98,4 +109,21 @@ public MembersDto updateMember(@PathVariable int id, @RequestBody MembersDto mem
public void deleteMember(@PathVariable int id) {
memberService.deleteMember(id);
}

/**
* Updates the password for a library member.
* If the member is not found or the update fails, an appropriate exception will be thrown.
*
* @param id the ID of the member whose password is to be updated
* @param updatePasswordDto the {@link UpdatePasswordDto} object containing the password details
* @return a {@link ResponseEntity} containing a success message indicating the password was updated successfully
*/
@PutMapping("/{id}/password")
@PreAuthorize("#id == authentication.principal.memberId")
public ResponseEntity<?> updatePassword(@PathVariable int id,
@RequestBody UpdatePasswordDto updatePasswordDto) {
memberService.updatePassword(id, updatePasswordDto);
return ResponseEntity.ok("Password updated successfully.");
}

}
47 changes: 42 additions & 5 deletions src/main/java/com/libraryman_api/member/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.stereotype.Service;

import com.libraryman_api.exception.InvalidPasswordException;
import com.libraryman_api.exception.InvalidSortFieldException;
import com.libraryman_api.exception.ResourceNotFoundException;
import com.libraryman_api.member.dto.MembersDto;
import com.libraryman_api.member.dto.UpdateMembersDto;
import com.libraryman_api.member.dto.UpdatePasswordDto;
import com.libraryman_api.notification.NotificationService;
import com.libraryman_api.security.config.PasswordEncoder;



Expand All @@ -35,16 +40,18 @@ public class MemberService {

private final MemberRepository memberRepository;
private final NotificationService notificationService;
private final PasswordEncoder passwordEncoder;

/**
* Constructs a new {@code MemberService} with the specified repositories and services.
*
* @param memberRepository the repository for managing member records
* @param notificationService the service for sending notifications related to member activities
*/
public MemberService(MemberRepository memberRepository, NotificationService notificationService) {
public MemberService(MemberRepository memberRepository, NotificationService notificationService, PasswordEncoder passwordEncoder) {
this.memberRepository = memberRepository;
this.notificationService = notificationService;
this.passwordEncoder = passwordEncoder;
}

/**
Expand Down Expand Up @@ -109,15 +116,12 @@ public MembersDto addMember(MembersDto membersDto) {
*/

@CacheEvict(value = "members", key = "#memberId")
public MembersDto updateMember(int memberId, MembersDto membersDtoDetails) {
public MembersDto updateMember(int memberId, UpdateMembersDto membersDtoDetails) {
Members member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResourceNotFoundException("Member not found"));
member.setName(membersDtoDetails.getName());
member.setUsername(membersDtoDetails.getUsername());
member.setEmail(membersDtoDetails.getEmail());
member.setPassword(membersDtoDetails.getPassword());
member.setRole(membersDtoDetails.getRole());
member.setMembershipDate(membersDtoDetails.getMembershipDate());
member = memberRepository.save(member);
if(member!=null)
notificationService.accountDetailsUpdateNotification(member);
Expand Down Expand Up @@ -146,6 +150,39 @@ public void deleteMember(int memberId) {
memberRepository.delete(member);
}

/**
* Updates the password for a library member.
*
* <p>This method verifies the current password provided by the member, checks if the
* new password is different, and then updates the member's password in the database.
* If the current password is incorrect or the new password is the same as the current
* password, an {@link InvalidPasswordException} is thrown.</p>
*
* @param memberId the ID of the member whose password is to be updated
* @param updatePasswordDto the {@link UpdatePasswordDto} object containing the password details
* @throws ResourceNotFoundException if the member with the specified ID is not found
* @throws InvalidPasswordException if the current password is incorrect or the new password is the same as the current password
*/
public void updatePassword(int memberId, UpdatePasswordDto updatePasswordDto) {
Members member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResourceNotFoundException("Member not found"));

// Check the current password
String currentAuthPassword = member.getPassword();

if (!passwordEncoder.bCryptPasswordEncoder().matches(updatePasswordDto.getCurrentPassword(), currentAuthPassword)) {
throw new InvalidPasswordException("Current password is incorrect");
}

// Check if new password is different from old password
if (updatePasswordDto.getCurrentPassword().equals(updatePasswordDto.getNewPassword())) {
throw new InvalidPasswordException("New password must be different from the old password");
}

member.setPassword(passwordEncoder.bCryptPasswordEncoder().encode(updatePasswordDto.getNewPassword()));
memberRepository.save(member);
}

/**
* Converts a MembersDto object to a Members entity.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.libraryman_api.member;
package com.libraryman_api.member.dto;

import java.util.Date;

import com.libraryman_api.member.Role;

public class MembersDto {

private int memberId;
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/com/libraryman_api/member/dto/UpdateMembersDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.libraryman_api.member.dto;

public class UpdateMembersDto {

private String name;

private String username;

private String email;

public UpdateMembersDto(String name, String username, String email) {
this.name = name;
this.username = username;
this.email = email;
}

public UpdateMembersDto() {
}

public String getName() {
return name;
}

public String getUsername() {
return username;
}

public void setName(String name) {
this.name = name;
}

public void setUsername(String username) {
this.username = username;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

@Override
public String toString() {
return "UpdateMembersDto{" +
"name='" + name + '\'' +
", username='" + username + '\'' +
", email='" + email + '\'' +
'}';
}
}
Loading

0 comments on commit 7b8d882

Please sign in to comment.