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 @@ -3,7 +3,7 @@
import io.github.inertia4j.spring.Inertia;
import io.hexlet.cv.dto.registration.RegInputDTO;
import io.hexlet.cv.dto.registration.RegOutputDTO;
import io.hexlet.cv.exception.UserAlreadyExistsException;
import io.hexlet.cv.handler.exception.UserAlreadyExistsException;
import io.hexlet.cv.service.UserService;
import jakarta.validation.Valid;
import java.util.Map;
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/io/hexlet/cv/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.hexlet.cv.controller;

import io.github.inertia4j.spring.Inertia;
import io.hexlet.cv.dto.user.UserPasswordDto;
import io.hexlet.cv.handler.exception.MatchingPasswordsException;
import io.hexlet.cv.handler.exception.WrongPasswordException;
import io.hexlet.cv.service.UserService;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

@Controller
@RequestMapping("/users")
@AllArgsConstructor
public class UserController {

private final Inertia inertia;
private final UserService userService;

@GetMapping(path = "/password")
public ResponseEntity<?> getChangeAccountPassword(@Valid @RequestBody UserPasswordDto userPasswordDto) {
ResponseEntity<?> response = inertia.render(
"/users/password", Map.of("password", userPasswordDto));
return ResponseEntity.status(HttpStatus.OK)
.headers(response.getHeaders())
.body(response.getBody());
}

@PatchMapping(path = "/password")
public ResponseEntity<?> ChangeAccountPassword(@Valid @RequestBody UserPasswordDto userPasswordDto) {
try {
userService.passwordChange(userPasswordDto);
} catch (MatchingPasswordsException | WrongPasswordException e) {
ResponseEntity<?> response = inertia.render("/users/password",
Map.of("password", userPasswordDto, "exception", e.getMessage()));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.headers(response.getHeaders())
.body(response.getBody());
}
ResponseEntity<?> response = inertia.render("/users");
return ResponseEntity.status(HttpStatus.OK)
.headers(response.getHeaders())
.body(response.getBody());
}
}
28 changes: 28 additions & 0 deletions src/main/java/io/hexlet/cv/dto/user/UserPasswordDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.hexlet.cv.dto.user;

import io.hexlet.cv.validator.NotInTop10K;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserPasswordDto {
@NotBlank(message = "Old password required")
private String oldPassword;
@NotBlank(message = "New password required")
@Pattern(
regexp = "^(?=.*[A-Za-z])(?=.*\\d).{8,}$",
message = "Password must be at least 8 characters with at least 1 letter and 1 digit"
)
@NotInTop10K
private String newPassword;
@NotBlank(message = "Repeat new password required")
private String repeatNewPassword;
}
17 changes: 0 additions & 17 deletions src/main/java/io/hexlet/cv/exception/GlobalExceptionHandler.java

This file was deleted.

30 changes: 30 additions & 0 deletions src/main/java/io/hexlet/cv/handler/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.hexlet.cv.handler;

import io.hexlet.cv.handler.exception.MatchingPasswordsException;
import io.hexlet.cv.handler.exception.UserAlreadyExistsException;
import io.hexlet.cv.handler.exception.WrongPasswordException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(MatchingPasswordsException.class)
public ResponseEntity<String> handleMatchingPasswordsException(MatchingPasswordsException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}

@ExceptionHandler(WrongPasswordException.class)
public ResponseEntity<String> handleWrongPasswordException(WrongPasswordException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}

@ExceptionHandler(UserAlreadyExistsException.class)
public ResponseEntity<String> handleUserAlreadyExists(UserAlreadyExistsException ex) {
// Возвращаем статус 409 и только текст ошибки в теле
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.hexlet.cv.exception;
package io.hexlet.cv.handler;

import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.hexlet.cv.handler.exception;

public class MatchingPasswordsException extends RuntimeException {
public MatchingPasswordsException(String massage) {
super(massage);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.hexlet.cv.exception;
package io.hexlet.cv.handler.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.hexlet.cv.handler.exception;

public class WrongPasswordException extends RuntimeException {
public WrongPasswordException(String message) {
super(message);
}
}
27 changes: 26 additions & 1 deletion src/main/java/io/hexlet/cv/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

import io.hexlet.cv.dto.registration.RegInputDTO;
import io.hexlet.cv.dto.registration.RegOutputDTO;
import io.hexlet.cv.exception.UserAlreadyExistsException;
import io.hexlet.cv.handler.exception.UserAlreadyExistsException;
import io.hexlet.cv.handler.exception.WrongPasswordException;
import io.hexlet.cv.mapper.RegistrationMapper;
import io.hexlet.cv.model.User;
import io.hexlet.cv.model.enums.RoleType;
import io.hexlet.cv.repository.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import io.hexlet.cv.dto.user.UserPasswordDto;
import io.hexlet.cv.handler.exception.MatchingPasswordsException;
import org.springframework.stereotype.Service;

@Service
Expand Down Expand Up @@ -41,4 +47,23 @@ public RegOutputDTO registration(RegInputDTO inputDTO) {

return retDTO;
}

public void passwordChange(UserPasswordDto userPasswordDto) {
User user = userRepository.findByEmail(getCurrentUsername()).get();
if (!encoder.matches(userPasswordDto.getOldPassword(), user.getEncryptedPassword())) {
throw new WrongPasswordException("incorrect password entered");
} else if (!userPasswordDto.getNewPassword().equals(
userPasswordDto.getRepeatNewPassword())) {
throw new MatchingPasswordsException("passwords must match");
} else {
user.setEncryptedPassword(encoder.encode(userPasswordDto.getNewPassword()));
userRepository.save(user);
System.out.println("password change success");
}
}

public String getCurrentUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth.getName();
}
}
5 changes: 5 additions & 0 deletions src/main/java/io/hexlet/cv/utils/PasswordValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.hexlet.cv.utils;


public class PasswordValidator {
}
109 changes: 109 additions & 0 deletions src/test/java/io/hexlet/cv/controller/TestUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package io.hexlet.cv.controller;

import io.hexlet.cv.dto.user.UserPasswordDto;
import io.hexlet.cv.handler.exception.MatchingPasswordsException;
import io.hexlet.cv.handler.exception.WrongPasswordException;
import io.hexlet.cv.mapper.RegistrationMapper;
import io.hexlet.cv.model.User;
import io.hexlet.cv.repository.UserRepository;
import io.hexlet.cv.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class TestUser {

@Mock
private UserRepository userRepository;

@Mock
private BCryptPasswordEncoder encoder;

@InjectMocks
private UserService userService;

private User user;

@BeforeEach
public void setup() {
user = new User();
user.setEmail("[email protected]");
user.setEncryptedPassword("Encrypted_old_password");
user.setFirstName("firstName");
user.setLastName("lastName");

Authentication authentication = mock(Authentication.class);
when(authentication.getName()).thenReturn("[email protected]");
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
}

@Test
public void testSuccessPasswordChange() {
UserPasswordDto userPasswordDto = new UserPasswordDto(
"Old_password", "New_password", "New_password");

when(userRepository.findByEmail("[email protected]")).thenReturn(Optional.of(user));
when(encoder.matches("Old_password", "Encrypted_old_password"))
.thenReturn(true);
when(encoder.encode("New_password")).thenReturn("Encrypted_new_password");

userService.passwordChange(userPasswordDto);

assertEquals("Encrypted_new_password", user.getEncryptedPassword());
verify(userRepository).save(user);
}

@Test
public void testWrongOldPassword() {
UserPasswordDto userPasswordDto = new UserPasswordDto(
"Wrong_old_password", "New_password", "New_password");

when(userRepository.findByEmail("[email protected]")).thenReturn(Optional.of(user));
when(encoder.matches("Wrong_old_password", "Encrypted_old_password"))
.thenReturn(false);

assertThrows(WrongPasswordException.class, () -> {
userService.passwordChange(userPasswordDto);
});

verify(userRepository, never()).save(any());
}

@Test
public void testWrongRepeatNewPassword() {
UserPasswordDto userPasswordDto = new UserPasswordDto(
"Old_password", "New_password", "New_wrong_password");

when(userRepository.findByEmail("[email protected]")).thenReturn(Optional.of(user));
when(encoder.matches("Old_password", "Encrypted_old_password"))
.thenReturn(true);

assertThrows(MatchingPasswordsException.class, () -> {
userService.passwordChange(userPasswordDto);
});

verify(userRepository, never()).save(any());
}
}