diff --git a/api/src/main/java/lab/en2b/quizapi/auth/AuthController.java b/api/src/main/java/lab/en2b/quizapi/auth/AuthController.java index 4627a487..8ea27236 100644 --- a/api/src/main/java/lab/en2b/quizapi/auth/AuthController.java +++ b/api/src/main/java/lab/en2b/quizapi/auth/AuthController.java @@ -4,10 +4,9 @@ import lab.en2b.quizapi.auth.dtos.*; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + @RestController @RequestMapping("/auth") @RequiredArgsConstructor @@ -25,6 +24,12 @@ public ResponseEntity loginUser(@Valid @RequestBody LoginDto log return ResponseEntity.ok(authService.login(loginRequest)); } + @GetMapping("/logout") + public ResponseEntity logoutUser(Authentication authentication){ + authService.logOut(authentication); + return ResponseEntity.noContent().build(); + } + @PostMapping("/refresh-token") public ResponseEntity refreshToken(@Valid @RequestBody RefreshTokenDto refreshTokenRequest){ return ResponseEntity.ok(authService.refreshToken(refreshTokenRequest)); diff --git a/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java b/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java index 33810f9c..e242d993 100644 --- a/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java +++ b/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java @@ -62,4 +62,9 @@ public RefreshTokenResponseDto refreshToken(RefreshTokenDto refreshTokenRequest) "Refresh token is not in database!")); return new RefreshTokenResponseDto(jwtUtils.generateTokenFromEmail(user.getEmail()), user.obtainRefreshIfValid()); } + + public void logOut(Authentication authentication) { + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + userService.deleteRefreshToken(userDetails.getId()); + } } diff --git a/api/src/main/java/lab/en2b/quizapi/auth/config/SecurityConfig.java b/api/src/main/java/lab/en2b/quizapi/auth/config/SecurityConfig.java index 64ee3492..42aaefa6 100644 --- a/api/src/main/java/lab/en2b/quizapi/auth/config/SecurityConfig.java +++ b/api/src/main/java/lab/en2b/quizapi/auth/config/SecurityConfig.java @@ -58,13 +58,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, Authentication .cors(Customizer.withDefaults()) .sessionManagement(configuration -> configuration.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize + .requestMatchers(HttpMethod.GET,"/auth/logout").authenticated() .requestMatchers(HttpMethod.POST,"/auth/**").permitAll() .anyRequest().authenticated()) .csrf(AbstractHttpConfigurer::disable) .authenticationManager(authenticationManager) .addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class) .build(); - //TODO: add exception handling } /** diff --git a/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java b/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java index 419a961f..50ae66d0 100644 --- a/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java +++ b/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java @@ -49,4 +49,11 @@ public String assignNewRefreshToken(Long id) { userRepository.save(user); return user.getRefreshToken(); } + + public void deleteRefreshToken(Long id) { + User user = userRepository.findById(id).orElseThrow(); + user.setRefreshToken(null); + user.setRefreshExpiration(null); + userRepository.save(user); + } } diff --git a/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java b/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java index aec35921..cd785477 100644 --- a/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java +++ b/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java @@ -21,7 +21,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(AuthController.class) @@ -110,6 +112,19 @@ void refreshTokenEmptyTokenShouldReturn400() throws Exception { testRefreshToken(asJsonString( new RefreshTokenDto("")), status().isBadRequest()); } + @Test + void logoutShouldReturn204() throws Exception { + testLogout(status().isNoContent()); + } + + @Test + void logoutNoAuthShouldReturn403() throws Exception { + mockMvc.perform(get("/auth/logout") + .contentType("application/json") + .with(csrf())) + .andExpect(status().isForbidden()); + } + private void testRegister(String content, ResultMatcher code) throws Exception { mockMvc.perform(post("/auth/register") .content(content) @@ -133,4 +148,12 @@ private void testRefreshToken(String content, ResultMatcher code) throws Excepti .with(csrf())) .andExpect(code); } + + private void testLogout(ResultMatcher code) throws Exception { + mockMvc.perform(get("/auth/logout") + .with(user("test").roles("user")) + .contentType("application/json") + .with(csrf())) + .andExpect(code); + } } diff --git a/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java b/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java index 54598188..760fdb72 100644 --- a/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java @@ -22,6 +22,7 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -97,4 +98,15 @@ void testRefreshToken(){ assertEquals(new RefreshTokenResponseDto("jwtToken","token"),actual); } + + @Test + void testLogout(){ + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(UserDetailsImpl.build(defaultUser)); + when(userRepository.findById(any())).thenReturn(Optional.of(defaultUser)); + + authService.logOut(authentication); + assertNull(defaultUser.getRefreshToken()); + assertNull(defaultUser.getRefreshExpiration()); + } }