diff --git a/src/main/java/org/hydev/clock_api/controller/UserController.java b/src/main/java/org/hydev/clock_api/controller/UserController.java index ce1b6cb..795aa51 100644 --- a/src/main/java/org/hydev/clock_api/controller/UserController.java +++ b/src/main/java/org/hydev/clock_api/controller/UserController.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController; import javax.validation.constraints.Pattern; import java.util.List; +import java.util.function.Function; @Validated @RestController @@ -32,7 +33,7 @@ public class UserController { // TODO: This method should be synchronized to avoid race condition. // Also, this method should not be private, or else cannot use userRepository. - // TODO: 2021/1/22 Need a better design! + // TODO: 2021/1/22 Need a better design! // Controller Return error code list as List, or return uuid as String. @SuppressWarnings("rawtypes") public synchronized ResponseEntity register( @@ -65,16 +66,30 @@ public class UserController { return DigestUtils.md5DigestAsHex(beforeMd5.getBytes()).toLowerCase(); } - @PostMapping("/delete") - public synchronized ResponseEntity delete(@RequestHeader String username, @RequestHeader String password) { + // Check username & password. + // user not exists -> http 404, password not match -> http 401; all match -> do and return do's result String. + private ResponseEntity checkPasswordAndDo(String username, String password, + Function operation) { User user = userRepository.queryByUsername(username); if (user == null) return ResponseEntity.notFound().build(); - if (user.getPasswordMd5().equals(userToSaltedMd5(username, password))) { - userRepository.delete(user); - return ResponseEntity.ok(""); - } + if (!user.getPasswordMd5().equals(userToSaltedMd5(username, password))) + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(""); + String result = operation.apply(user); + return ResponseEntity.ok(result); + } + + @PostMapping("/delete") + public synchronized ResponseEntity delete(@RequestHeader String username, @RequestHeader String password) { + return checkPasswordAndDo(username, password, user -> { + userRepository.delete(user); + return ""; + }); + } + + @PostMapping("login") + public synchronized ResponseEntity login(@RequestHeader String username, @RequestHeader String password) { + return checkPasswordAndDo(username, password, user -> userRepository.queryByUsername(username).getUuid()); } } diff --git a/src/main/java/org/hydev/clock_api/repository/UserRepository.java b/src/main/java/org/hydev/clock_api/repository/UserRepository.java index 0f1ba82..ce1ebc4 100644 --- a/src/main/java/org/hydev/clock_api/repository/UserRepository.java +++ b/src/main/java/org/hydev/clock_api/repository/UserRepository.java @@ -8,5 +8,6 @@ import org.springframework.stereotype.Repository; public interface UserRepository extends JpaRepository { // https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods boolean existsByUsername(String username); + User queryByUsername(String username); } diff --git a/src/test/java/org/hydev/clock_api/UserRegisterDeleteNodeTest.kt b/src/test/java/org/hydev/clock_api/UserRegisterDeleteNodeTest.kt index ae846f2..932e497 100644 --- a/src/test/java/org/hydev/clock_api/UserRegisterDeleteNodeTest.kt +++ b/src/test/java/org/hydev/clock_api/UserRegisterDeleteNodeTest.kt @@ -1,6 +1,7 @@ package org.hydev.clock_api import org.hamcrest.Matchers +import org.hamcrest.Matchers.matchesPattern import org.hydev.clock_api.entity.User import org.hydev.clock_api.error.ErrorCode.* import org.hydev.clock_api.repository.UserRepository @@ -44,6 +45,7 @@ class UserRegisterDeleteNodeTest { private const val TEST_NODE = "/user" private const val REGISTER_NODE = "${TEST_NODE}/register" private const val DELETE_NODE = "${TEST_NODE}/delete" + private const val LOGIN_NODE = "${TEST_NODE}/login" private const val H_USERNAME = "username" private const val V_USERNAME = "vanilla" @@ -194,4 +196,21 @@ class UserRegisterDeleteNodeTest { assertEquals(false, userRepository.existsByUsername(V_USERNAME)) } } + + @Test + fun testUserLogin() { + mockMvc.perform(post(REGISTER_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD)) + // https://stackoverflow.com/questions/49722217/how-do-i-validate-a-json-field-is-formatted-correctly-using-mockmvcresultmatcher + .andExpect(status().isOk) + .andExpect(content().string(matchesPattern(R_UUID))) + .andDo { + // Missing field, username not exists, password not match cases already test in testDeleteUser(). + // Test successful login cases, status 200 and current user's uuid. + mockMvc.perform(post(LOGIN_NODE).header(H_USERNAME, V_USERNAME).header(H_PASSWORD, V_PASSWORD)) + .andExpect(status().isOk) + .andExpect(content().string(it.response.contentAsString)) + + userRepository.deleteById(it.response.contentAsString) + } + } }