Skip to content

Commit

Permalink
Feature/user (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
kih00 authored Jan 24, 2025
1 parent e51173f commit 18566e4
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.wafflestudio.toyproject.memoWithTags.mail.service

import jakarta.mail.MessagingException
import jakarta.mail.internet.MimeMessage
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Profile
import org.springframework.mail.javamail.JavaMailSender
import org.springframework.mail.javamail.MimeMessageHelper
Expand All @@ -11,16 +10,14 @@ import org.springframework.stereotype.Service
@Profile("prod")
@Service
class SmtpMailService(
private val mailSender: JavaMailSender,
@Value("\${spring.mail.username}")
private val fromMail: String
private val mailSender: JavaMailSender
) : MailService {
override fun sendMail(toEmail: String, title: String, content: String) {
val message: MimeMessage = mailSender.createMimeMessage()
val helper = MimeMessageHelper(message, true, "UTF-8")
try {
helper.apply {
setFrom(fromMail)
setFrom("[email protected]")
setTo(toEmail)
setSubject(title)
setText(content, true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.wafflestudio.toyproject.memoWithTags.social.controller

import com.wafflestudio.toyproject.memoWithTags.exception.OAuthRequestException
import com.wafflestudio.toyproject.memoWithTags.social.dto.SocialLoginResponse
import com.wafflestudio.toyproject.memoWithTags.social.service.SocialLoginService
import com.wafflestudio.toyproject.memoWithTags.user.SocialType
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserResponse.LoginResponse
import io.swagger.v3.oas.annotations.Operation
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
Expand All @@ -18,6 +19,8 @@ import org.springframework.web.bind.annotation.RestController
class SocialLoginController(
private val socialLoginService: SocialLoginService
) {
private val logger = LoggerFactory.getLogger(javaClass)

@Operation(summary = "소셜 로그인 요청")
@GetMapping("/auth/code/{provider}")
fun oauthCallback(
Expand All @@ -26,6 +29,8 @@ class SocialLoginController(
): ResponseEntity<Unit> {
if (code == null) throw OAuthRequestException()
val appLink = "memowithtags://oauth/$provider?code=$code"

logger.info("redirect request to $appLink")
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", appLink)
.build()
Expand All @@ -36,14 +41,16 @@ class SocialLoginController(
fun oauthLogin(
@RequestParam(value = "code") code: String,
@PathVariable provider: String
): ResponseEntity<LoginResponse> {
): ResponseEntity<SocialLoginResponse> {
val socialType = SocialType.from(provider)
val loginResult = when (socialType) {
val socialLoginResponse = when (socialType) {
SocialType.KAKAO -> socialLoginService.kakaoLogin(code)
SocialType.NAVER -> socialLoginService.naverLogin(code)
SocialType.GOOGLE -> socialLoginService.googleLogin(code)
else -> throw OAuthRequestException()
}
return ResponseEntity.ok(LoginResponse(loginResult.second, loginResult.third))

logger.info("social login result: (${socialLoginResponse.accessToken}, ${socialLoginResponse.isNewUser})")
return ResponseEntity.ok(socialLoginResponse)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wafflestudio.toyproject.memoWithTags.social.dto

data class SocialLoginResponse(
val accessToken: String,
val refreshToken: String,
val isNewUser: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.SocialLoginResponse
import com.wafflestudio.toyproject.memoWithTags.user.GoogleUtil
import com.wafflestudio.toyproject.memoWithTags.user.JwtUtil
import com.wafflestudio.toyproject.memoWithTags.user.KakaoUtil
Expand All @@ -28,9 +29,10 @@ class SocialLoginService(
/**
* 네이버 프로필 정보를 받아온 후, 로그인 또는 회원가입 후 로그인 로직을 처리하는 함수
*/
fun naverLogin(accessCode: String): Triple<User, String, String> {
fun naverLogin(accessCode: String): SocialLoginResponse {
val oAuthToken: NaverOAuthToken = naverUtil.requestToken(accessCode)
val naverProfile: NaverProfile = naverUtil.requestProfile(oAuthToken)
var isNewUser = false

val naverEmail = naverProfile.email
val userEntity = socialUserService.findUserByEmail(naverEmail)
Expand All @@ -41,24 +43,26 @@ class SocialLoginService(
User.fromEntity(userEntity)
} else if (userEntity == null) {
logger.info("creating naver user $naverEmail")
isNewUser = true
socialUserService.createNaverUser(naverProfile)
} else {
throw EmailAlreadyExistsException()
}

return Triple(
user,
JwtUtil.generateAccessToken(naverEmail),
JwtUtil.generateRefreshToken(naverEmail)
return SocialLoginResponse(
accessToken = JwtUtil.generateAccessToken(naverEmail),
refreshToken = JwtUtil.generateRefreshToken(naverEmail),
isNewUser = isNewUser
)
}

/**
* 카카오 프로필 정보를 받아온 후, 로그인 또는 회원가입 후 로그인 로직을 처리하는 함수
*/
fun kakaoLogin(accessCode: String): Triple<User, String, String> {
fun kakaoLogin(accessCode: String): SocialLoginResponse {
val oAuthToken: KakaoOAuthToken = kakaoUtil.requestToken(accessCode)
val kakaoProfile: KakaoProfile = kakaoUtil.requestProfile(oAuthToken)
var isNewUser = false

val kakaoEmail = kakaoProfile.kakao_account.email
val userEntity = socialUserService.findUserByEmail(kakaoEmail)
Expand All @@ -69,24 +73,26 @@ class SocialLoginService(
User.fromEntity(userEntity)
} else if (userEntity == null) {
logger.info("creating kakao user $kakaoEmail")
isNewUser = true
socialUserService.createKakaoUser(kakaoProfile)
} else {
throw EmailAlreadyExistsException()
}

return Triple(
user,
JwtUtil.generateAccessToken(kakaoEmail),
JwtUtil.generateRefreshToken(kakaoEmail)
return SocialLoginResponse(
accessToken = JwtUtil.generateAccessToken(kakaoEmail),
refreshToken = JwtUtil.generateRefreshToken(kakaoEmail),
isNewUser = isNewUser
)
}

/**
* 구글 프로필 정보를 받아온 후, 로그인 또는 회원가입 후 로그인 로직을 처리하는 함수
*/
fun googleLogin(accessCode: String): Triple<User, String, String> {
fun googleLogin(accessCode: String): SocialLoginResponse {
val oAuthToken: GoogleOAuthToken = googleUtil.requestToken(accessCode)
val googleProfile: GoogleProfile = googleUtil.requestProfile(oAuthToken)
var isNewUser = false

val googleEmail = googleProfile.email
val userEntity = socialUserService.findUserByEmail(googleEmail)
Expand All @@ -97,15 +103,16 @@ class SocialLoginService(
User.fromEntity(userEntity)
} else if (userEntity == null) {
logger.info("creating google user $googleEmail")
isNewUser = true
socialUserService.createGoogleUser(googleProfile)
} else {
throw EmailAlreadyExistsException()
}

return Triple(
user,
JwtUtil.generateAccessToken(googleEmail),
JwtUtil.generateRefreshToken(googleEmail)
return SocialLoginResponse(
accessToken = JwtUtil.generateAccessToken(googleEmail),
refreshToken = JwtUtil.generateRefreshToken(googleEmail),
isNewUser = isNewUser
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.LoginReques
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.RefreshTokenRequest
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.RegisterRequest
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.ResetPasswordRequest
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.UpdateNicknameRequest
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.UpdatePasswordRequest
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserRequest.VerifyEmailRequest
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserResponse.LoginResponse
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserResponse.RefreshTokenResponse
Expand All @@ -16,6 +18,7 @@ import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
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.RequestMapping
import org.springframework.web.bind.annotation.RestController
Expand Down Expand Up @@ -62,6 +65,24 @@ class UserController(
return ResponseEntity.ok().build()
}

@Operation(summary = "비밀번호 수정(로그인 상태에서)")
@PutMapping("/auth/password")
fun updatePassword(
@AuthUser user: User,
@RequestBody request: UpdatePasswordRequest
): ResponseEntity<User> {
return ResponseEntity.ok(userService.updatePassword(user, request.password))
}

@Operation(summary = "닉네임 수정")
@PutMapping("/auth/nickname")
fun updateNickname(
@AuthUser user: User,
@RequestBody request: UpdateNicknameRequest
): ResponseEntity<User> {
return ResponseEntity.ok(userService.updateNickname(user, request.nickname))
}

@Operation(summary = "토큰 재발급")
@PostMapping("/auth/refresh-token")
fun refreshToken(@RequestBody request: RefreshTokenRequest): RefreshTokenResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ sealed class UserRequest {
val password: String
) : UserRequest()

data class UpdatePasswordRequest(
val password: String
) : UserRequest()

data class UpdateNicknameRequest(
val nickname: String
) : UserRequest()

data class RefreshTokenRequest(
val refreshToken: String
) : UserRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,32 @@ class UserService(
return User.fromEntity(userEntity)
}

/**
* 로그인 한 유저의 비밀번호를 변경하는 함수
*/
@Transactional
fun updatePassword(
user: User,
newPassword: String
): User {
val userEntity = userRepository.findByEmail(user.email) ?: throw UserNotFoundException()
userEntity.hashedPassword = BCrypt.hashpw(newPassword, BCrypt.gensalt())
return User.fromEntity(userRepository.save(userEntity))
}

/**
* 로그인 한 유저의 닉네임을 변경하는 함수
*/
@Transactional
fun updateNickname(
user: User,
newNickname: String
): User {
val userEntity = userRepository.findByEmail(user.email) ?: throw UserNotFoundException()
userEntity.nickname = newNickname
return User.fromEntity(userRepository.save(userEntity))
}

/**
* accessToken 만료 시 refreshToken을 통해 유저를 확인하고 새로운 accessToken을 발급하는 함수
*/
Expand Down

0 comments on commit 18566e4

Please sign in to comment.