diff --git a/src/main/kotlin/andreas311/miso/domain/auth/presentation/AuthController.kt b/src/main/kotlin/andreas311/miso/domain/auth/presentation/AuthController.kt index ec1cb81d..669d3cfc 100644 --- a/src/main/kotlin/andreas311/miso/domain/auth/presentation/AuthController.kt +++ b/src/main/kotlin/andreas311/miso/domain/auth/presentation/AuthController.kt @@ -2,22 +2,27 @@ package andreas311.miso.domain.auth.presentation import andreas311.miso.domain.auth.presentation.data.request.SignInRequestDto import andreas311.miso.domain.auth.presentation.data.request.SignUpRequestDto +import andreas311.miso.domain.auth.presentation.data.response.NewRefreshTokenResponseDto import andreas311.miso.domain.auth.presentation.data.response.SignInResponseDto import andreas311.miso.domain.auth.service.SignInService import andreas311.miso.domain.auth.service.SignUpService +import andreas311.miso.domain.auth.service.TokenReissueService import andreas311.miso.domain.auth.util.AuthConverter import andreas311.miso.global.annotation.RequestController import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader import javax.validation.Valid @RequestController("/auth") class AuthController( private val signUpService: SignUpService, private val signInService: SignInService, - private val authConverter: AuthConverter + private val authConverter: AuthConverter, + private val tokenReissueService: TokenReissueService ) { @PostMapping @@ -29,4 +34,9 @@ class AuthController( fun signIn(@Valid @RequestBody signInRequestDto: SignInRequestDto): ResponseEntity = authConverter.toDto(signInRequestDto) .let { ResponseEntity.ok(signInService.execute(it)) } + + @PatchMapping + fun getNewRefreshToken(@RequestHeader("Refresh-Token") refreshToken: String): ResponseEntity = + tokenReissueService.execute(refreshToken) + .let { ResponseEntity.status(HttpStatus.OK).body(it) } } \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/auth/presentation/data/response/NewRefreshTokenResponseDto.kt b/src/main/kotlin/andreas311/miso/domain/auth/presentation/data/response/NewRefreshTokenResponseDto.kt new file mode 100644 index 00000000..a5dd63d7 --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/auth/presentation/data/response/NewRefreshTokenResponseDto.kt @@ -0,0 +1,13 @@ +package andreas311.miso.domain.auth.presentation.data.response + +import com.fasterxml.jackson.annotation.JsonFormat +import java.time.ZonedDateTime + +data class NewRefreshTokenResponseDto( + val accessToken: String, + val refreshToken: String, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + val accessExp: ZonedDateTime, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + val refreshExp: ZonedDateTime +) diff --git a/src/main/kotlin/andreas311/miso/domain/auth/service/TokenReissueService.kt b/src/main/kotlin/andreas311/miso/domain/auth/service/TokenReissueService.kt new file mode 100644 index 00000000..7932222e --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/auth/service/TokenReissueService.kt @@ -0,0 +1,8 @@ +package andreas311.miso.domain.auth.service + +import andreas311.miso.domain.auth.presentation.data.response.NewRefreshTokenResponseDto + +interface TokenReissueService { + + fun execute(refreshToken: String): NewRefreshTokenResponseDto +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/auth/service/impl/TokenReissueServiceImpl.kt b/src/main/kotlin/andreas311/miso/domain/auth/service/impl/TokenReissueServiceImpl.kt new file mode 100644 index 00000000..c26a5d09 --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/auth/service/impl/TokenReissueServiceImpl.kt @@ -0,0 +1,54 @@ +package andreas311.miso.domain.auth.service.impl + +import andreas311.miso.domain.auth.presentation.data.response.NewRefreshTokenResponseDto +import andreas311.miso.domain.auth.repository.RefreshTokenRepository +import andreas311.miso.domain.auth.service.TokenReissueService +import andreas311.miso.domain.auth.util.AuthConverter +import andreas311.miso.domain.user.enums.Role +import andreas311.miso.domain.user.exception.UserNotFoundException +import andreas311.miso.domain.user.repository.UserRepository +import andreas311.miso.global.annotation.RollbackService +import andreas311.miso.global.security.exception.TokenExpiredException +import andreas311.miso.global.security.exception.TokenInvalidException +import andreas311.miso.global.security.jwt.TokenProvider +import java.time.ZonedDateTime + +@RollbackService +class TokenReissueServiceImpl( + private val authConverter: AuthConverter, + private val tokenProvider: TokenProvider, + private val userRepository: UserRepository, + private val refreshTokenRepository: RefreshTokenRepository, +) : TokenReissueService { + + override fun execute(refreshToken: String): NewRefreshTokenResponseDto { + val refresh = tokenProvider.parseToken(refreshToken) + ?: throw TokenInvalidException() + + val email: String = tokenProvider.exactEmailFromRefreshToken(refresh) + + val existingRefreshToken = refreshTokenRepository.findByToken(refresh) + ?: throw TokenExpiredException() + + val newAccessToken = tokenProvider.generateAccessToken(email) + val newRefreshToken = tokenProvider.generateRefreshToken(email) + val accessExp: ZonedDateTime = tokenProvider.accessExpiredTime + val refreshExp: ZonedDateTime = tokenProvider.refreshExpiredTime + + val newRefreshTokenEntity = authConverter.toEntity( + userId = existingRefreshToken.userId, + refreshToken = newRefreshToken + ) + + refreshTokenRepository.save(newRefreshTokenEntity) + val user = userRepository.findByEmail(email) + ?: throw UserNotFoundException() + + return NewRefreshTokenResponseDto( + accessToken = newAccessToken, + refreshToken = newRefreshToken, + accessExp = accessExp, + refreshExp = refreshExp + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/global/security/SecurityConfig.kt b/src/main/kotlin/andreas311/miso/global/security/SecurityConfig.kt index 0cb0efa2..9d9415e9 100644 --- a/src/main/kotlin/andreas311/miso/global/security/SecurityConfig.kt +++ b/src/main/kotlin/andreas311/miso/global/security/SecurityConfig.kt @@ -40,6 +40,7 @@ class SecurityConfig( .antMatchers(HttpMethod.POST, "/auth").permitAll() .antMatchers(HttpMethod.POST, "/auth/signIn").permitAll() + .antMatchers(HttpMethod.PATCH, "/auth").permitAll() .antMatchers(HttpMethod.POST, "/email").permitAll()