diff --git a/src/main/java/com/bit/lot/flower/auth/common/dto/AccessTokenExpiredResponse.java b/src/main/java/com/bit/lot/flower/auth/common/dto/AccessTokenExpiredResponse.java new file mode 100644 index 00000000..f6a75b06 --- /dev/null +++ b/src/main/java/com/bit/lot/flower/auth/common/dto/AccessTokenExpiredResponse.java @@ -0,0 +1,17 @@ +package com.bit.lot.flower.auth.common.dto; + +import com.bit.lot.flower.auth.common.valueobject.BaseId; +import com.bit.lot.flower.auth.common.valueobject.Role; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class AccessTokenExpiredResponse { + private T id; + private Role role; +} diff --git a/src/main/java/com/bit/lot/flower/auth/common/dto/RenewAccessTokenDto.java b/src/main/java/com/bit/lot/flower/auth/common/dto/RenewAccessTokenDto.java index 1613c9a5..b6966a12 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/dto/RenewAccessTokenDto.java +++ b/src/main/java/com/bit/lot/flower/auth/common/dto/RenewAccessTokenDto.java @@ -11,7 +11,7 @@ @NoArgsConstructor @Getter public class RenewAccessTokenDto { - private T authId; + private T id; private Role role; private String expiredAccessToken; diff --git a/src/main/java/com/bit/lot/flower/auth/common/http/controller/AuthPolicyController.java b/src/main/java/com/bit/lot/flower/auth/common/http/controller/AuthPolicyController.java index b00adfd6..955fcc83 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/http/controller/AuthPolicyController.java +++ b/src/main/java/com/bit/lot/flower/auth/common/http/controller/AuthPolicyController.java @@ -18,11 +18,11 @@ public class AuthPolicyController { private final RenewRefreshTokenStrategy renewRefreshTokenStrategy; - @PostMapping("/api/auth/refresh-token") + @PostMapping("/refresh-token") public ResponseEntity renewRefreshToken( @RequestBody RenewAccessTokenDto renewAccessTokenDto, HttpServletRequest request, HttpServletResponse response) { - String newAccessToken = renewRefreshTokenStrategy.renew(renewAccessTokenDto.getAuthId(), + String newAccessToken = renewRefreshTokenStrategy.renew(renewAccessTokenDto.getId(), renewAccessTokenDto.getRole(),renewAccessTokenDto.getExpiredAccessToken(), request, response); response.addHeader(SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_HEADER_NAME, diff --git a/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/ExceptionHandlerFilter.java b/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/ExceptionHandlerFilter.java index 4dd9976c..e2417738 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/ExceptionHandlerFilter.java @@ -1,5 +1,6 @@ package com.bit.lot.flower.auth.common.http.interceptor.filter; +import com.bit.lot.flower.auth.common.dto.AccessTokenExpiredResponse; import com.bit.lot.flower.auth.common.dto.RenewAccessTokenDto; import com.bit.lot.flower.auth.common.exception.ErrorDTO; import com.bit.lot.flower.auth.common.util.JsonBinderUtil; @@ -33,9 +34,9 @@ public void doFilterInternal(HttpServletRequest request, HttpServletResponse res } } - private RenewAccessTokenDto createDtoByToken (ExpiredJwtException e){ - return RenewAccessTokenDto.builder() - .authId(new AuthId(Long.valueOf(e.getClaims().getSubject()))) + private AccessTokenExpiredResponse createDtoByToken (ExpiredJwtException e){ + return AccessTokenExpiredResponse.builder() + .id(new AuthId(Long.valueOf(e.getClaims().getSubject()))) .role(Role.valueOf(e.getClaims().get( SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, String.class))).build(); } diff --git a/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/JwtAuthenticationFilter.java b/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/JwtAuthenticationFilter.java index 8c4050cd..d9176d0c 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/bit/lot/flower/auth/common/http/interceptor/filter/JwtAuthenticationFilter.java @@ -36,9 +36,10 @@ public void doFilterInternal(HttpServletRequest request, HttpServletResponse res throw new AuthenticationException("해당 토큰은 이미 로그아웃 처리된 토큰이라 사용할 수 없는 토큰입니다."); } try { - JwtUtil.isTokenValid(token); + JwtUtil.isAccessTokenValid(token); } catch (ExpiredJwtException e) { - throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "만료된 토큰입니다. Refresh토큰을 확인하세요"); + response.setStatus(401); + throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "Expired"); } filterChain.doFilter(request, response); } diff --git a/src/main/java/com/bit/lot/flower/auth/common/security/IssueRefreshRefreshTokenInCookie.java b/src/main/java/com/bit/lot/flower/auth/common/security/IssueRefreshRefreshTokenInCookie.java index a576be87..4e54f1d3 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/security/IssueRefreshRefreshTokenInCookie.java +++ b/src/main/java/com/bit/lot/flower/auth/common/security/IssueRefreshRefreshTokenInCookie.java @@ -27,7 +27,8 @@ public void createRefreshToken(String userId, HttpServletResponse response) { String refreshToken = JwtUtil.generateRefreshToken(String.valueOf(userId)); redisRefreshTokenUtil.saveRefreshToken(userId, refreshToken, Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)); - response.setHeader(refreshCookieName,CookieUtil.createRefreshNoCORSCookie(refreshCookieName, refreshToken, + response.setHeader(refreshCookieName, + CookieUtil.createRefreshNoCORSCookie(refreshCookieName, refreshToken, Duration.ofDays(1), domain).toString()); } diff --git a/src/main/java/com/bit/lot/flower/auth/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java b/src/main/java/com/bit/lot/flower/auth/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java index 5f6ae0d6..4946574f 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java +++ b/src/main/java/com/bit/lot/flower/auth/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java @@ -8,6 +8,8 @@ import com.bit.lot.flower.auth.common.valueobject.BaseId; import com.bit.lot.flower.auth.common.valueobject.Role; import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,11 +34,16 @@ public String renew(ID id, Role role, String expiredAccessToken, HttpServletRequ checkTokenIsBlacklist(expiredAccessToken); - String refreshCookieValue = CookieUtil.getCookieValue(request, refreshCookieName); - if (!redisRefreshTokenUtil.getRefreshToken(id.getValue().toString()).equals(refreshCookieValue)) { - throw new IllegalArgumentException("유효한 접근이 아닙니다. Refresh토큰을 확인해주세요"); + + try { + JwtUtil.isRefreshTokenValid(id.toString()); + } catch (ExpiredJwtException e) { + throw new JwtException("refresh 토큰이 만료되었습니다. 다시 로그인 해주세요."); } - return tokenHandler.createToken(id.getValue().toString(), createClaimsRoleMap(role), response); + + checkRefreshTokenIsExistedBothRedisAndCookie(id,request); + + return createNewTokenWithInvalidatingTheOldToken(id, role, expiredAccessToken, response); } private void checkTokenIsBlacklist(String accessToken) { @@ -45,6 +52,23 @@ private void checkTokenIsBlacklist(String accessToken) { } } + private void checkRefreshTokenIsExistedBothRedisAndCookie(ID id, HttpServletRequest request) { + String refreshCookieValue = CookieUtil.getCookieValue(request, refreshCookieName); + + if (!redisRefreshTokenUtil.getRefreshToken(id.getValue().toString()) + .equals(refreshCookieValue)) { + throw new IllegalArgumentException("유효한 접근이 아닙니다. Refresh토큰을 확인해주세요"); + } + } + + private String createNewTokenWithInvalidatingTheOldToken(ID id, Role role, + String expiredAccessToken, + HttpServletResponse response) { + tokenHandler.invalidateToken(id.getValue().toString(), expiredAccessToken, response); + return tokenHandler.createToken(id.getValue().toString(), createClaimsRoleMap(role), response); + } + + private Map createClaimsRoleMap(Role role) { return JwtUtil.addClaims( SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, role); diff --git a/src/main/java/com/bit/lot/flower/auth/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/com/bit/lot/flower/auth/common/util/ExtractAuthorizationTokenUtil.java index 9eaecffb..ee102781 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/com/bit/lot/flower/auth/common/util/ExtractAuthorizationTokenUtil.java @@ -24,12 +24,12 @@ public static String extractToken(HttpServletRequest request) { public static String extractUserId(HttpServletRequest request){ String token = extractToken(request); - return JwtUtil.extractSubject(token); + return JwtUtil.extractAccessTokenSubject(token); } public static String extractRole(HttpServletRequest request) { String token = ExtractAuthorizationTokenUtil.extractToken(request); - Claims claims = JwtUtil.extractClaims(token); + Claims claims = JwtUtil.extractAccessTokenClaims(token); return (String) claims.get(SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME); } diff --git a/src/main/java/com/bit/lot/flower/auth/common/util/JwtUtil.java b/src/main/java/com/bit/lot/flower/auth/common/util/JwtUtil.java index 02d9dbcc..670db2c5 100644 --- a/src/main/java/com/bit/lot/flower/auth/common/util/JwtUtil.java +++ b/src/main/java/com/bit/lot/flower/auth/common/util/JwtUtil.java @@ -6,114 +6,124 @@ import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.UnsupportedJwtException; -import java.security.NoSuchAlgorithmException; +import io.jsonwebtoken.security.Keys; import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.Map; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class JwtUtil { - private JwtUtil(){ - - } - private static SecretKey accessSecret; - private static SecretKey refreshSecret; + public static String accessKey; + public static String refreshKey; + private JwtUtil() { + } public static String generateAccessTokenWithClaims(String subject, Map claimsList) { Date now = new Date(); - - initAccessKey(); return Jwts.builder() .setSubject(subject) .setIssuedAt(now) .setExpiration(Date.from(Instant.now().plusSeconds( Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) - .signWith(accessSecret) + .signWith(Keys.hmacShaKeyFor(accessKey.getBytes()), SignatureAlgorithm.HS256) .addClaims(claimsList) .compact(); } - public static Map addClaims(String id, Object value) { - Map claims = new HashMap<>(); - claims.put(id, value); - return claims; - } - public static String generateAccessToken(String subject) { Date now = new Date(); - - initAccessKey(); return Jwts.builder() .setSubject(subject) .setIssuedAt(now) .setExpiration(Date.from(Instant.now().plusSeconds( Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) - .signWith(accessSecret) + .signWith(Keys.hmacShaKeyFor(accessKey.getBytes()), SignatureAlgorithm.HS256) .compact(); } public static String generateRefreshToken(String subject) { Date now = new Date(); - initRefreshKey(); return Jwts.builder() .setSubject(subject) .setIssuedAt(now) .setExpiration(Date.from(Instant.now().plusSeconds( Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)))) - .signWith(refreshSecret) + .signWith(Keys.hmacShaKeyFor(refreshKey.getBytes()), SignatureAlgorithm.HS256) .compact(); } - public static String extractSubject(String token) { - return extractClaims(token).getSubject(); + public static Map addClaims(String id, Object value) { + Map claims = new HashMap<>(); + claims.put(id, value); + return claims; + } + + public static String extractAccessTokenSubject(String token) { + return extractAccessTokenClaims(token).getSubject(); } - public static Claims extractClaims(String token) { - return Jwts.parserBuilder().setSigningKey(accessSecret).build().parseClaimsJws(token) + public static Claims extractAccessTokenClaims(String accessToken) { + return Jwts.parserBuilder() + .setSigningKey(accessKey). + build() + .parseClaimsJws(accessToken) .getBody(); } - public static boolean isTokenValid(String token) { + public static Claims extractRefreshToken(String refreshToken) { + return Jwts.parserBuilder() + .setSigningKey(refreshKey). + build() + .parseClaimsJws(refreshToken) + .getBody(); + } + + public static boolean isAccessTokenValid(String accessToken) { try { - extractClaims(token); + extractAccessTokenClaims(accessToken); return true; } catch (ExpiredJwtException e) { - throw new ExpiredJwtException(e.getHeader(),e.getClaims(),e.getMessage()) { + throw new ExpiredJwtException(e.getHeader(), e.getClaims(), e.getMessage()) { }; } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) { throw new IllegalArgumentException("올바르지 않은 접근입니다."); } } - private static void initRefreshKey() { - try { - refreshSecret = KeyGenerator.getInstance("HmacSHA256").generateKey(); - - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("plz init the secret key"); - } + public static boolean isRefreshTokenValid(String refreshToken) { + try { + extractRefreshToken(refreshToken); + return true; + } catch (ExpiredJwtException e) { + throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "refresh-token이 만료되었습니다.") { + }; + } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) { + throw new IllegalArgumentException("올바르지 않은 접근입니다."); } + } + + @Value("${encrypt.key.access}") + private void setAccessKey(String accessKey) { + JwtUtil.accessKey = accessKey; + } + @Value("${encrypt.key.refresh}") + private void setRefreshKey(String refreshKey) { + JwtUtil.refreshKey = refreshKey; + } - private static void initAccessKey() { - try { - accessSecret = KeyGenerator.getInstance("HmacSHA256").generateKey(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("plz init the secret key"); - } +} - } - } diff --git a/src/main/java/com/bit/lot/flower/auth/email/http/controller/EmailCodeRestController.java b/src/main/java/com/bit/lot/flower/auth/email/http/controller/EmailCodeRestController.java index f1f229b0..00c43c28 100644 --- a/src/main/java/com/bit/lot/flower/auth/email/http/controller/EmailCodeRestController.java +++ b/src/main/java/com/bit/lot/flower/auth/email/http/controller/EmailCodeRestController.java @@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/com/bit/lot/flower/auth/system/admin/config/SystemAdminSecurityConfig.java b/src/main/java/com/bit/lot/flower/auth/system/admin/config/SystemAdminSecurityConfig.java index f0124a59..a76ef3fc 100644 --- a/src/main/java/com/bit/lot/flower/auth/system/admin/config/SystemAdminSecurityConfig.java +++ b/src/main/java/com/bit/lot/flower/auth/system/admin/config/SystemAdminSecurityConfig.java @@ -60,7 +60,7 @@ AuthenticationManager systemAuthenticationManager() { public UsernamePasswordAuthenticationFilter systemAdminAuthenticationFilter() { SystemAdminAuthenticationFilter systemAdminAuthenticationFilter = new SystemAdminAuthenticationFilter(authenticationSuccessHandler, systemAuthenticationManager()); - systemAdminAuthenticationFilter.setFilterProcessesUrl("/**/admin/login"); + systemAdminAuthenticationFilter.setFilterProcessesUrl("/**/system/admin/login"); return systemAdminAuthenticationFilter; } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index ac2a95a6..e9fe4910 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -105,3 +105,7 @@ user: secret: user-secret-user-secret-user-secret-user-secret-user-secret +encrypt: + key: + access: access-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-key + refresh: refresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyfresh-key \ No newline at end of file diff --git a/src/test/java/com/bit/lot/flower/auth/common/interceptor/refresh/GetUserDataWhenTokenIsExpiredTest.java b/src/test/java/com/bit/lot/flower/auth/common/interceptor/refresh/GetUserDataWhenTokenIsExpiredTest.java deleted file mode 100644 index e7c79931..00000000 --- a/src/test/java/com/bit/lot/flower/auth/common/interceptor/refresh/GetUserDataWhenTokenIsExpiredTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.bit.lot.flower.auth.common.interceptor.refresh; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.verify; - -import com.bit.lot.flower.auth.common.http.interceptor.filter.ExceptionHandlerFilter; -import io.jsonwebtoken.ExpiredJwtException; -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - - -@ExtendWith(MockitoExtension.class) -class GetUserDataWhenTokenIsExpiredTest { - - @Mock - ExceptionHandlerFilter exceptionHandlerFilter; - @Mock - HttpServletRequest request; - @Mock - HttpServletResponse response; - @Mock - FilterChain filterChain; - - @Test - @DisplayName("JWT 토큰이 Expired 되었을 때 Response에 RenewAccessTokenDto 확인") - void GetUserTokenInfo_WhenTokenIsExpired_ResponseWithRenewAccessTokenDto() - throws ServletException, IOException { - doThrow(ExpiredJwtException.class).when(exceptionHandlerFilter) - .doFilterInternal(any(HttpServletRequest.class), any(HttpServletResponse.class), - any(FilterChain.class)); - assertThrowsExactly(ExpiredJwtException.class, () -> { - exceptionHandlerFilter.doFilterInternal(request, response, filterChain); - }); - - } -} \ No newline at end of file diff --git a/src/test/java/com/bit/lot/flower/auth/common/interceptor/refresh/TokenExpiredTest.java b/src/test/java/com/bit/lot/flower/auth/common/interceptor/refresh/TokenExpiredTest.java deleted file mode 100644 index b8b20249..00000000 --- a/src/test/java/com/bit/lot/flower/auth/common/interceptor/refresh/TokenExpiredTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.bit.lot.flower.auth.common.interceptor.refresh; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.bit.lot.flower.auth.common.http.interceptor.filter.JwtAuthenticationFilter; -import com.bit.lot.flower.auth.common.util.JwtUtil; -import com.bit.lot.flower.auth.common.util.RedisBlackListTokenUtil; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; -import io.jsonwebtoken.ExpiredJwtException; -import javax.crypto.SecretKey; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.util.ReflectionTestUtils; - -@ExtendWith(MockitoExtension.class) -class TokenExpiredTest { - - @InjectMocks - JwtAuthenticationFilter jwtAuthenticationFilter; - @Mock - RedisBlackListTokenUtil redisBlackListTokenUtil; - - MockHttpServletRequest request; - MockHttpServletResponse response; - MockFilterChain filterChain; - - @BeforeEach - void init() { - ReflectionTestUtils.setField(JwtUtil.class, "accessSecret", mock(SecretKey.class)); - Mockito.mockStatic(JwtUtil.class); - Mockito.mockStatic(SecurityPolicyStaticValue.class); - request = new MockHttpServletRequest(); - request.addHeader(SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_HEADER_NAME, - SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX + "unValid"); - response = new MockHttpServletResponse(); - } - - @Test - @DisplayName("JWT 토큰이 Expired 되었을 때 에러 Throw") - void expirationTest_WhenJwtTokenIsExpired_ThrowExpiredJwtException() { - - when(redisBlackListTokenUtil.isTokenBlacklisted(anyString())).thenReturn(false); - when(JwtUtil.isTokenValid(anyString())).thenThrow(ExpiredJwtException.class); - - assertThrows(ExpiredJwtException.class, - () -> jwtAuthenticationFilter.doFilterInternal(request, response, filterChain)); - - } - -} diff --git a/src/test/java/com/bit/lot/flower/auth/email/service/SendEmailServiceTest.java b/src/test/java/com/bit/lot/flower/auth/email/service/SendEmailServiceTest.java deleted file mode 100644 index 7cc621ba..00000000 --- a/src/test/java/com/bit/lot/flower/auth/email/service/SendEmailServiceTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.bit.lot.flower.auth.email.service; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import javax.transaction.Transactional; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -@TestPropertySource(locations="classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@ExtendWith(SpringExtension.class) -@SpringBootTest - class SendEmailServiceTest { - - private final String to = "rnwldnd7248@gmail.com"; - private final String title = "Test email send"; - private final String text = "test text"; - @Autowired - MailService mailService; - - @DisplayName("이메일 보내기 테스트") - @Test - void sendEmail_WhenToEmailAndTitleAndTextAreProvided_NoException() { - assertDoesNotThrow(()->{ - mailService.sendEmail(to,title,text); - }); - } - - -} diff --git a/src/test/java/com/bit/lot/flower/auth/email/service/VerifyEmailServiceTest.java b/src/test/java/com/bit/lot/flower/auth/email/service/VerifyEmailServiceTest.java deleted file mode 100644 index 9719d9a4..00000000 --- a/src/test/java/com/bit/lot/flower/auth/email/service/VerifyEmailServiceTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.bit.lot.flower.auth.email.service; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; - -import com.bit.lot.flower.auth.email.entity.EmailCode; -import com.bit.lot.flower.auth.email.exception.EmailCodeException; -import com.bit.lot.flower.auth.email.repository.EmailCodeJpaRepository; -import java.util.List; -import javax.transaction.Transactional; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -@TestPropertySource(locations="classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@ExtendWith(SpringExtension.class) -@SpringBootTest - class VerifyEmailServiceTest { - - private final String testEmail = "rnwldnd7248@gmail.com"; - @Autowired - EmailCodeService emailCodeService; - @Autowired - EmailCodeJpaRepository repository; - - private void sendFirstEmail() { - emailCodeService.create(testEmail); - } - - private void sendSecondEmail() { - emailCodeService.create(testEmail); - } - - - @DisplayName("가장 최근에 보내진 이메일 랜덤 Value로 인증") - @Test - void VerifyEmail_WhenEmailIsLastSent_NotThrowEmailCodeException() { - sendFirstEmail(); - EmailCode getRecentSentEmailCode = repository.findAll().get(0); - assertDoesNotThrow(() -> { - emailCodeService.verify(testEmail, getRecentSentEmailCode.getEmailCode()); - }); - - } - - @DisplayName("가장 최근에 보내지지 않은 이메일 랜덤 Value로 인증시 EmailCodeException Throw") - @Test - void VerifyEmail_WhenEmailIsNotLastSent_ThrowEmailCodeException() { - - sendFirstEmail(); - sendSecondEmail(); - List emailList = repository.findAll(); - assertThrowsExactly(EmailCodeException.class, () -> { - emailCodeService.verify(testEmail, findNotRecentEmailCode(emailList).getEmailCode()); - }); - } - - private EmailCode findNotRecentEmailCode(List emailCodeList) { - EmailCode notRecentEmailCode = emailCodeList.get(0); - if (notRecentEmailCode.getCreatedAt().compareTo(emailCodeList.get(1).getCreatedAt())>0){ - notRecentEmailCode = emailCodeList.get(1); - - } - return notRecentEmailCode; - } - -} - - diff --git a/src/test/java/com/bit/lot/flower/auth/social/service/UserWithdrawalServiceTest.java b/src/test/java/com/bit/lot/flower/auth/social/service/UserWithdrawalServiceTest.java deleted file mode 100644 index 30bc916b..00000000 --- a/src/test/java/com/bit/lot/flower/auth/social/service/UserWithdrawalServiceTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.bit.lot.flower.auth.social.service; - - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.bit.lot.flower.auth.social.entity.SocialAuth; -import com.bit.lot.flower.auth.social.exception.SocialAuthException; -import com.bit.lot.flower.auth.social.repository.SocialAuthJpaRepository; -import com.bit.lot.flower.auth.common.valueobject.AuthId; -import javax.transaction.Transactional; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - - -@TestPropertySource(locations="classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@ExtendWith(SpringExtension.class) -@SpringBootTest - class UserWithdrawalServiceTest { - - @Autowired - SocialAuthJpaRepository repository; - @Autowired - SocialAuthService socialAuthService; - - - private AuthId getAuthIdFromLong(Long value){ - return AuthId.builder().value(value).build(); - } - - private SocialAuth initAlreadyWithdrawalSocialUser() { - SocialAuth socialAuthAlreadyOut = SocialAuth.builder().oauthId(1L).isRecentlyOut(true) - .lastLogoutTime(null).build(); - return repository.save(socialAuthAlreadyOut); - } - - private SocialAuth initNormalUser() { - SocialAuth normalSocialAuth = SocialAuth.builder().oauthId(1L).isRecentlyOut(false) - .lastLogoutTime(null).build(); - return repository.save(normalSocialAuth); - } - - - @Transactional - @DisplayName("유저 회원탈퇴 요청, 유저가 없을시 SocialAuthException Throw ") - @Test - void UserWithdrawal_WhenUserIsNotExist_ThrowSocialAuthException() { - AuthId notExistId = getAuthIdFromLong(2L); - assertThrows(SocialAuthException.class, () -> { - socialAuthService.userWithdrawalUserSelf(notExistId); - }); - - - } - - @Transactional - @DisplayName("유저 회원탈퇴 요청, 유저가 이미 회원탈퇴일 경우 SocialAuthException Throw ") - @Test - void UserWithdrawal_WhenUserIsExistButAlreadyOut_ThrowSocialAuthException() { - SocialAuth alreadyOutSocialUser = initAlreadyWithdrawalSocialUser(); - AuthId alreadyOutSocialUserId = getAuthIdFromLong(alreadyOutSocialUser.getOauthId()); - assertThrows(SocialAuthException.class, () -> { - socialAuthService.userWithdrawalUserSelf(alreadyOutSocialUserId); - }); } - - @Transactional - @DisplayName("유저 회원탈퇴 요청, 유저가 이미 회원 탈퇴가 되어있지 않고 유저가 존재할 때, 유저의 상태 isRecentlyOut으로 변경") - @Test - void UserWithdrawal_WhenUserIsExistAndNotRecentlyOut_ChangeUserStatusToIsRecentlyOut() { - SocialAuth normalUserNotWithdrawal = initNormalUser(); - AuthId normalUserNotWithdrawalId = getAuthIdFromLong(normalUserNotWithdrawal.getOauthId()); - socialAuthService.userWithdrawalUserSelf(normalUserNotWithdrawalId); - assertTrue(repository.findById(1L).get().isRecentlyOut()); - } - - -} diff --git a/src/test/java/com/bit/lot/flower/auth/store/service/EmailDuplicationCheckerServiceTest.java b/src/test/java/com/bit/lot/flower/auth/store/service/EmailDuplicationCheckerServiceTest.java deleted file mode 100644 index e82fdb28..00000000 --- a/src/test/java/com/bit/lot/flower/auth/store/service/EmailDuplicationCheckerServiceTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.bit.lot.flower.auth.store.service; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.bit.lot.flower.auth.store.entity.StoreManagerAuth; -import com.bit.lot.flower.auth.store.exception.StoreManagerAuthException; -import com.bit.lot.flower.auth.store.repository.StoreManagerAuthRepository; -import com.bit.lot.flower.auth.store.valueobject.StoreManagerStatus; -import javax.transaction.Transactional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -@TestPropertySource(locations="classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@ExtendWith(SpringExtension.class) -@SpringBootTest -class EmailDuplicationCheckerServiceTest { - - private final String alreadyExistedEmail = "alreadyExist@Email.com"; - private final String notDuplicatedEmail = "notDuplicated@Email.com"; - @Autowired - EmailDuplicationCheckerService emailDuplicationCheckerService; - @Autowired - StoreManagerAuthRepository repository; - - - @DisplayName("중복 체크를 위해서 하나의 계정을 주입하는 BeforeEach") - @BeforeEach - void saveStoreManagerForTestObject() { - StoreManagerAuth storeManagerAuth = StoreManagerAuth.builder() - .status(StoreManagerStatus.ROLE_STORE_MANAGER_PERMITTED) - .email(alreadyExistedEmail).password("randomPassword").lastLogoutTime(null).build(); - repository.save(storeManagerAuth); - - } - - @DisplayName("이메일 중복일시 StoreManagerAuthException Throw 테스트") - @Transactional - @Test - void EmailDuplicationCheck_WhenThereIsAlreadyExistEmail_ThrowStoreManagerException() { - assertThrows(StoreManagerAuthException.class, () -> { - emailDuplicationCheckerService.isDuplicated(alreadyExistedEmail); - }); - - } - - @DisplayName("이메일 중복이 아닐시 error를 던지지 않는 테스트") - @Transactional - @Test - void EmailDuplicationCheck_WhenThereIsNotExistEmail_NotThrowException() { - assertDoesNotThrow(() -> { - emailDuplicationCheckerService.isDuplicated(notDuplicatedEmail); - }); - } - -} - - diff --git a/src/test/java/com/bit/lot/flower/auth/store/service/StoreManagerSignUpValidationTest.java b/src/test/java/com/bit/lot/flower/auth/store/service/StoreManagerSignUpValidationTest.java deleted file mode 100644 index a634aae9..00000000 --- a/src/test/java/com/bit/lot/flower/auth/store/service/StoreManagerSignUpValidationTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.bit.lot.flower.auth.store.service; - - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.bit.lot.flower.auth.store.dto.StoreMangerSignUpCommand; -import java.util.Set; -import javax.transaction.Transactional; -import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -@TestPropertySource(locations="classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@Import(LocalValidatorFactoryBean.class) -class StoreManagerSignUpValidationTest { - - private final String UNVALID_IMAGE_URL = "unValidImageUrl"; - private final String VALID_IMAGE_URL = "http://ww.validImageUrl.com"; - private final String UNVALID_PASSWORD_INCLUDE_SPECIAL_LETTER = "abcdef!"; - private final String VALID_PASSWORD = "123456ab"; - private final String VALID_EMAIL = "valid@gmail.com"; - private final String UNVALID_EMAIL = "unvalidEmail"; - private final String UNVALID_NAME_LENGTH_OVER_6 = "unvalidOver6"; - private final String VALID_NAME_LENGTH_LESS_6 = "valid"; - private Validator validator; - - @BeforeEach - public void setUpClass() { - ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); - validator = factory.getValidator(); - } - - - public Set> validateDto(StoreMangerSignUpCommand dto) { - return validator.validate(dto); - } - - - @DisplayName("이메일 regex 체크 실패시 validation 체크") - @Test - void ValidationCheck_WhenEmailIsNotValid_setSize1() { - assertEquals(validateDto( - StoreMangerSignUpCommand.builder().email(UNVALID_EMAIL).password(VALID_PASSWORD) - .name(VALID_NAME_LENGTH_LESS_6) - .businessNumberImage(VALID_IMAGE_URL) - .isEmailVerified(true).build()).size(), 1); - } - - @DisplayName("패스워드 regex 체크 실패시(영문,숫자가 아닌 것을 입력") - @Test - void ValidationCheck_WhenPasswordIsNotSatisfiedByCriteria_setSize1() { - assertEquals(validateDto( - StoreMangerSignUpCommand.builder().isEmailVerified(true).businessNumberImage(VALID_IMAGE_URL) - .name(VALID_NAME_LENGTH_LESS_6).password(UNVALID_PASSWORD_INCLUDE_SPECIAL_LETTER).email( - VALID_EMAIL) - .build()).size(), 1); - } - - - @DisplayName("비즈니스 넘버 이미지 URL regex 체크") - @Test - void ValidationCheck_WhenImageIsNotURL_setSize1() { - assertEquals(validateDto( - StoreMangerSignUpCommand.builder().isEmailVerified(true).businessNumberImage(UNVALID_IMAGE_URL) - .name(VALID_NAME_LENGTH_LESS_6).password(VALID_PASSWORD).email(VALID_EMAIL) - .build()).size(), 1); - } - - @DisplayName("이름 길이 6글자 넘을 때 validation error 체크") - @Test - void ValidationCheck_WhenNameLengthOver6_setSize1() { - assertEquals(validateDto( - StoreMangerSignUpCommand.builder().isEmailVerified(true).businessNumberImage(VALID_IMAGE_URL) - .name(UNVALID_NAME_LENGTH_OVER_6).password(VALID_PASSWORD).email(VALID_EMAIL) - .build()).size(), 1); - - } - - @DisplayName("모든 Regex 만족하는 경우 에러 Set 사이즈 0") - @Test - void ValidationCheck_WhenAllRegexIsSatisfied_setSize0() { - assertEquals(validateDto( - StoreMangerSignUpCommand.builder().isEmailVerified(true).businessNumberImage(VALID_IMAGE_URL) - .name(VALID_NAME_LENGTH_LESS_6).password(VALID_PASSWORD).email(VALID_EMAIL) - .build()).size(), 0); - } -} - - - diff --git a/src/test/java/com/bit/lot/flower/auth/store/service/StoreManagerSingUpTest.java b/src/test/java/com/bit/lot/flower/auth/store/service/StoreManagerSingUpTest.java deleted file mode 100644 index 35f64455..00000000 --- a/src/test/java/com/bit/lot/flower/auth/store/service/StoreManagerSingUpTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.bit.lot.flower.auth.store.service; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.bit.lot.flower.auth.store.dto.StoreMangerSignUpCommand; -import com.bit.lot.flower.auth.store.entity.StoreManagerAuth; -import com.bit.lot.flower.auth.store.exception.StoreManagerAuthException; -import com.bit.lot.flower.auth.store.repository.StoreManagerAuthRepository; -import javax.transaction.Transactional; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -@TestPropertySource(locations = "classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@ExtendWith(SpringExtension.class) -@SpringBootTest -class StoreManagerSingUpTest { - - private final String VALID_IMAGE_URL = "http://ww.validImageUrl.com"; - private final String VALID_PASSWORD = "123456ab"; - private final String VALID_EMAIL = "valid@gmail.com"; - private final String VALID_NAME_LENGTH_LESS_6 = "valid"; - - @Mock - StoreManagerAuthRepository repository; - - @Mock - BCryptPasswordEncoder encoder; - - @InjectMocks - OnlyVerifiedStoreManagerEmailCanSignUpService storeManagerSingUpService; - - private StoreMangerSignUpCommand emailVerifiedDto() { - return StoreMangerSignUpCommand.builder().email(VALID_EMAIL).password(VALID_PASSWORD) - .name(VALID_NAME_LENGTH_LESS_6) - .businessNumberImage(VALID_IMAGE_URL) - .isEmailVerified(true).build(); - } - - - private StoreMangerSignUpCommand emailNotVerifiedDto() { - return StoreMangerSignUpCommand.builder().email(VALID_EMAIL).password(VALID_PASSWORD) - .name(VALID_NAME_LENGTH_LESS_6) - .businessNumberImage(VALID_IMAGE_URL) - .isEmailVerified(false).build(); - } - - - @Transactional - @DisplayName("이메일 인증을 하지 않은 경우 ThrowStoreManagerException") - @Test - void SignUp_WhenAllValidationCheckIsSatisfiedButNotEmailIsVerified_ThrowStoreManagerException() { - - StoreMangerSignUpCommand emailVerifiedDto = emailNotVerifiedDto(); - assertThrows(StoreManagerAuthException.class, () -> { - storeManagerSingUpService.signup(emailVerifiedDto); - }); - - } - - @DisplayName("이메일 인증을 한 경우 NotThrowStoreManagerAuthException, entity 저장") - @Transactional - @Test - void SignUp_WhenAllValidationCheckIsSatisfiedAndEmailIsVerified_NotThrowStoreManagerAuthExceptionAndEntityIsSaved() { - when(encoder.encode(anyString())).thenReturn("encodedPassword"); - when(repository.save(any(StoreManagerAuth.class))).thenReturn(mock(StoreManagerAuth.class)); - - StoreMangerSignUpCommand emailVerifiedDto = emailVerifiedDto(); - assertDoesNotThrow(() -> { - storeManagerSingUpService.signup(emailVerifiedDto); - }); - verify(repository).save(any(StoreManagerAuth.class)); - - - } -} diff --git a/src/test/java/com/bit/lot/flower/auth/system/service/UpdateStoreManagerStatusTest.java b/src/test/java/com/bit/lot/flower/auth/system/service/UpdateStoreManagerStatusTest.java deleted file mode 100644 index 63e5e38a..00000000 --- a/src/test/java/com/bit/lot/flower/auth/system/service/UpdateStoreManagerStatusTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.bit.lot.flower.auth.system.service; - -import com.bit.lot.flower.auth.common.valueobject.AuthId; -import com.bit.lot.flower.auth.store.entity.StoreManagerAuth; -import com.bit.lot.flower.auth.store.exception.StoreManagerAuthException; -import com.bit.lot.flower.auth.store.repository.StoreManagerAuthRepository; -import com.bit.lot.flower.auth.store.valueobject.StoreManagerStatus; -import com.bit.lot.flower.auth.system.admin.dto.UpdateStoreManagerStatusDto; -import com.bit.lot.flower.auth.system.admin.service.UpdateStoreMangerStatusService; -import java.util.Optional; -import javax.transaction.Transactional; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.web.context.WebApplicationContext; - -@TestPropertySource(locations="classpath:application-test.yml") -@ActiveProfiles("test") -@Transactional -@ExtendWith(SpringExtension.class) -@SpringBootTest -class UpdateStoreManagerStatusTest { - - - @Autowired - private StoreManagerAuthRepository repository; - @Autowired - private UpdateStoreMangerStatusService updateStoreMangerStatusService; - - @Autowired - private WebApplicationContext webApplicationContext; - - - private AuthId idBuilder(Long id) { - return AuthId.builder().value(id).build(); - } - - - - private StoreManagerAuth savePendingStoreManager() { - StoreManagerAuth auth = repository.save( - StoreManagerAuth.builder().lastLogoutTime(null).email("random@gmail.com") - .password("randomPassword").status(StoreManagerStatus.ROLE_STORE_MANAGER_PENDING) - .build()); - return auth; - } - - @Test - void UpdateStoreManagerStatus_WhenStoreManagerIsPending_ChangeStoreManagerStatusToROLE_STORE_MANAGER_PERMITTED() { - StoreManagerAuth storeManagerAuth = savePendingStoreManager(); - repository.save(storeManagerAuth); - Optional found = repository.findById(storeManagerAuth.getId()); - Assertions.assertNotNull(found.get()); - updateStoreMangerStatusService.update(idBuilder(storeManagerAuth.getId()), - StoreManagerStatus.ROLE_STORE_MANAGER_PERMITTED); - Assertions.assertEquals(StoreManagerStatus.ROLE_STORE_MANAGER_PERMITTED, - found.get().getStatus()); - } - - @Transactional - @Test - void UpdateStoreManagerStatus_WhenStoreMangerIsNotExisted_ThrowStoreManagerException() { - StoreManagerAuth auth = savePendingStoreManager(); - StoreManagerAuthException exception = Assertions.assertThrowsExactly( - StoreManagerAuthException.class, - () -> updateStoreMangerStatusService.update(idBuilder(2L), - StoreManagerStatus.ROLE_STORE_MANAGER_PERMITTED) - ); - } - -} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index ee9660b5..5e937a6c 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -111,3 +111,8 @@ client: user: info: secret: user-secret-user-secret-user-secret-user-secret-user-secret + +encrypt: + key: + access: access-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-key + refresh: refresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyfresh-key \ No newline at end of file