dev #2
@@ -30,12 +30,10 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||||
//TODO add try-catch and response code
|
AuthToken login = authService.login(request);
|
||||||
// try {
|
|
||||||
return ResponseEntity.ok(authService.login(request));
|
return ResponseEntity.ok(new LoginResponse(true, login.getUser().getEmail(),
|
||||||
// } catch (RuntimeException e) {
|
login.getToken(),login.getExpiresAt().toString()));
|
||||||
// return ResponseEntity.badRequest();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package ru.soune.no_copy.dto;
|
package ru.soune.no_copy.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
public class LoginRequest {
|
public class LoginRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank(message = "error.not.blank") @Size(min = 8)
|
||||||
private String password;
|
private String password;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package ru.soune.no_copy.dto;
|
package ru.soune.no_copy.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import ru.soune.no_copy.entity.User;
|
import ru.soune.no_copy.entity.User;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
public class LoginResponse {
|
public class LoginResponse {
|
||||||
private boolean success;
|
private boolean success;
|
||||||
private String message;
|
private String email;
|
||||||
private User user;
|
|
||||||
private String token;
|
private String token;
|
||||||
private String expiresAt;
|
private String expiresAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ public record RegisterRequest(
|
|||||||
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String firstName,
|
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String firstName,
|
||||||
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String secondName,
|
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String secondName,
|
||||||
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String lastName,
|
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String lastName,
|
||||||
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128)String email,
|
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128) String email,
|
||||||
@NotBlank(message = "error.not.blank") @Size(min = 8) String password
|
@NotBlank(message = "error.not.blank") @Size(min = 8) String password
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.soune.no_copy.exception;
|
||||||
|
|
||||||
|
public class NotValidationPasswordException extends RuntimeException {
|
||||||
|
public NotValidationPasswordException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.soune.no_copy.exception;
|
||||||
|
|
||||||
|
public class UserNotFoundException extends RuntimeException {
|
||||||
|
public UserNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
|
|||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import ru.soune.no_copy.exception.NotValidationPasswordException;
|
||||||
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
||||||
|
import ru.soune.no_copy.exception.UserNotFoundException;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -39,4 +41,26 @@ public class GlobalExceptionHandler {
|
|||||||
"message" ,ex.getMessage()
|
"message" ,ex.getMessage()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(UserNotFoundException.class)
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
public ResponseEntity<?> handleUserNotFoundException(UserNotFoundException ex) {
|
||||||
|
return ResponseEntity.
|
||||||
|
badRequest()
|
||||||
|
.body(Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message" ,ex.getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NotValidationPasswordException.class)
|
||||||
|
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||||
|
public ResponseEntity<?> handleNotValidationPasswordException(NotValidationPasswordException ex) {
|
||||||
|
return ResponseEntity.
|
||||||
|
badRequest()
|
||||||
|
.body(Map.of(
|
||||||
|
"success", false,
|
||||||
|
"message" ,ex.getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import ru.soune.no_copy.dto.LoginRequest;
|
import ru.soune.no_copy.dto.LoginRequest;
|
||||||
import ru.soune.no_copy.dto.LoginResponse;
|
|
||||||
import ru.soune.no_copy.dto.RegisterRequest;
|
import ru.soune.no_copy.dto.RegisterRequest;
|
||||||
import ru.soune.no_copy.entity.AuthToken;
|
import ru.soune.no_copy.entity.AuthToken;
|
||||||
import ru.soune.no_copy.entity.User;
|
import ru.soune.no_copy.entity.User;
|
||||||
|
import ru.soune.no_copy.exception.NotValidationPasswordException;
|
||||||
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
||||||
|
import ru.soune.no_copy.exception.UserNotFoundException;
|
||||||
import ru.soune.no_copy.repository.AuthTokenRepository;
|
import ru.soune.no_copy.repository.AuthTokenRepository;
|
||||||
import ru.soune.no_copy.repository.UserRepository;
|
import ru.soune.no_copy.repository.UserRepository;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.LocalDateTime;
|
import java.util.Base64;
|
||||||
import java.util.HexFormat;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -32,6 +32,8 @@ public class AuthService {
|
|||||||
|
|
||||||
private final MessageSource messageSource;
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
private final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public AuthToken register(RegisterRequest registerRequest) {
|
public AuthToken register(RegisterRequest registerRequest) {
|
||||||
if (userRepository.existsByEmail(registerRequest.email())) {
|
if (userRepository.existsByEmail(registerRequest.email())) {
|
||||||
@@ -49,48 +51,37 @@ public class AuthService {
|
|||||||
User savedUser = userRepository.save(user);
|
User savedUser = userRepository.save(user);
|
||||||
|
|
||||||
AuthToken authToken = new AuthToken();
|
AuthToken authToken = new AuthToken();
|
||||||
authToken.setToken(generateToken());
|
authToken.setToken(generateAuthToken());
|
||||||
authToken.setUser(savedUser);
|
authToken.setUser(savedUser);
|
||||||
|
|
||||||
return authTokenRepository.save(authToken);
|
return authTokenRepository.save(authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoginResponse login(LoginRequest request) {
|
@Transactional
|
||||||
|
public AuthToken login(LoginRequest request) {
|
||||||
Optional<User> userOpt = userRepository.findByEmail(request.getEmail());
|
Optional<User> userOpt = userRepository.findByEmail(request.getEmail());
|
||||||
|
|
||||||
if (userOpt.isEmpty()) {
|
if (userOpt.isEmpty()) {
|
||||||
//ADD EXCEPTION
|
throw new UserNotFoundException("User with email " + request.getEmail() + " not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
|
|
||||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
||||||
//ADD EXCEPTION
|
throw new NotValidationPasswordException("Invalid password");
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = generateToken();
|
|
||||||
LocalDateTime expiresAt = LocalDateTime.now().plusDays(30);
|
|
||||||
|
|
||||||
AuthToken authToken = new AuthToken();
|
AuthToken authToken = new AuthToken();
|
||||||
authToken.setToken(generateToken());
|
authToken.setToken(generateAuthToken());
|
||||||
authToken.setUser(user);
|
authToken.setUser(user);
|
||||||
|
|
||||||
authTokenRepository.save(authToken);
|
return authTokenRepository.save(authToken);
|
||||||
|
|
||||||
userRepository.save(user);
|
|
||||||
|
|
||||||
LoginResponse response = new LoginResponse();
|
|
||||||
response.setSuccess(true);
|
|
||||||
// response.setMessage(""); add message
|
|
||||||
response.setUser(user);
|
|
||||||
response.setToken(token);
|
|
||||||
response.setExpiresAt(expiresAt.toString());
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
//TODO change logic generate token
|
|
||||||
private String generateToken() {
|
|
||||||
|
private String generateAuthToken() {
|
||||||
byte[] bytes = new byte[32];
|
byte[] bytes = new byte[32];
|
||||||
new SecureRandom().nextBytes(bytes);
|
secureRandom.nextBytes(bytes);
|
||||||
return HexFormat.of().formatHex(bytes);
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,29 @@ package ru.soune.no_copy.service;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import ru.soune.no_copy.dto.LoginRequest;
|
||||||
import ru.soune.no_copy.dto.RegisterRequest;
|
import ru.soune.no_copy.dto.RegisterRequest;
|
||||||
import ru.soune.no_copy.entity.AuthToken;
|
import ru.soune.no_copy.entity.AuthToken;
|
||||||
import ru.soune.no_copy.entity.User;
|
import ru.soune.no_copy.entity.User;
|
||||||
|
import ru.soune.no_copy.exception.NotValidationPasswordException;
|
||||||
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
import ru.soune.no_copy.exception.UserAlreadyExistsException;
|
||||||
|
import ru.soune.no_copy.exception.UserNotFoundException;
|
||||||
import ru.soune.no_copy.repository.AuthTokenRepository;
|
import ru.soune.no_copy.repository.AuthTokenRepository;
|
||||||
import ru.soune.no_copy.repository.UserRepository;
|
import ru.soune.no_copy.repository.UserRepository;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class AuthServiceTest {
|
public class AuthServiceTest {
|
||||||
@@ -45,7 +51,8 @@ public class AuthServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void registerSuccess() {
|
void registerSuccess() {
|
||||||
RegisterRequest request = new RegisterRequest("John","A.","Doe","john@example.com","password123");
|
RegisterRequest request = new RegisterRequest("John","A.","Doe",
|
||||||
|
"john@example.com", "password123");
|
||||||
|
|
||||||
when(userRepository.existsByEmail(request.email())).thenReturn(false);
|
when(userRepository.existsByEmail(request.email())).thenReturn(false);
|
||||||
when(passwordEncoder.encode(request.password())).thenReturn("hashed");
|
when(passwordEncoder.encode(request.password())).thenReturn("hashed");
|
||||||
@@ -65,14 +72,71 @@ public class AuthServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void registerExistingEmailThrows() {
|
void registerExistingEmailThrows() {
|
||||||
RegisterRequest request = new RegisterRequest("John","A.","Doe","john@example.com","123");
|
RegisterRequest request = new RegisterRequest("John","A.","Doe",
|
||||||
|
"john@example.com", "password123");
|
||||||
|
|
||||||
when(userRepository.existsByEmail(request.email())).thenReturn(true);
|
when(userRepository.existsByEmail(request.email()))
|
||||||
when(messageSource.getMessage(anyString(), any(), any(Locale.class))).thenReturn("User exists");
|
.thenReturn(true);
|
||||||
|
when(messageSource.getMessage(anyString(), any(), any(Locale.class)))
|
||||||
|
.thenReturn("User exists");
|
||||||
|
|
||||||
UserAlreadyExistsException ex = assertThrows(UserAlreadyExistsException.class,
|
UserAlreadyExistsException ex = assertThrows(UserAlreadyExistsException.class,
|
||||||
() -> authService.register(request));
|
() -> authService.register(request));
|
||||||
|
|
||||||
assertEquals("User exists", ex.getMessage());
|
assertEquals("User exists", ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void login_success() {
|
||||||
|
LoginRequest request = new LoginRequest("test@mail.com", "password");
|
||||||
|
|
||||||
|
User user = new User();
|
||||||
|
user.setUserId(1L);
|
||||||
|
user.setEmail("test@mail.com");
|
||||||
|
user.setPassword("encoded_pass");
|
||||||
|
|
||||||
|
when(userRepository.findByEmail("test@mail.com"))
|
||||||
|
.thenReturn(Optional.of(user));
|
||||||
|
when(passwordEncoder.matches("password", "encoded_pass"))
|
||||||
|
.thenReturn(true);
|
||||||
|
when(authTokenRepository.save(any(AuthToken.class)))
|
||||||
|
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
AuthToken token = authService.login(request);
|
||||||
|
|
||||||
|
assertNotNull(token);
|
||||||
|
assertNotNull(token.getToken());
|
||||||
|
assertEquals(user, token.getUser());
|
||||||
|
|
||||||
|
ArgumentCaptor<AuthToken> captor = ArgumentCaptor.forClass(AuthToken.class);
|
||||||
|
verify(authTokenRepository).save(captor.capture());
|
||||||
|
|
||||||
|
assertEquals(user, captor.getValue().getUser());
|
||||||
|
assertNotNull(captor.getValue().getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void login_userNotFound() {
|
||||||
|
LoginRequest request = new LoginRequest("missing@mail.com", "pwd");
|
||||||
|
|
||||||
|
when(userRepository.findByEmail("missing@mail.com")).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
assertThrows(UserNotFoundException.class, () -> authService.login(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void login_invalidPassword() {
|
||||||
|
LoginRequest request = new LoginRequest("test@mail.com", "wrong");
|
||||||
|
|
||||||
|
User user = new User();
|
||||||
|
user.setEmail("test@mail.com");
|
||||||
|
user.setPassword("encoded_pass");
|
||||||
|
|
||||||
|
when(userRepository.findByEmail("test@mail.com")).thenReturn(Optional.of(user));
|
||||||
|
when(passwordEncoder.matches("wrong", "encoded_pass")).thenReturn(false);
|
||||||
|
|
||||||
|
assertThrows(NotValidationPasswordException.class, () -> authService.login(request));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user