Skip to content

Commit

Permalink
소셜로그인 provider 관련 코드 수정 (#295)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hank-Choi authored Nov 7, 2024
1 parent 59122ce commit 3e765c8
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 84 deletions.
3 changes: 1 addition & 2 deletions api/src/main/kotlin/filter/ErrorWebFilter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,14 @@ class ErrorWebFilter(
}

private fun makeErrorBody(exception: Snu4tException): ErrorBody {
return ErrorBody(exception.error.errorCode, exception.errorMessage, exception.displayMessage, exception.detail, exception.ext)
return ErrorBody(exception.error.errorCode, exception.errorMessage, exception.displayMessage, exception.ext)
}
}

private data class ErrorBody(
val errcode: Long,
val message: String,
val displayMessage: String,
val detail: Any? = null,
// TODO: 구버전 대응용 ext 필드. 추후 삭제
val ext: Map<String, String> = mapOf(),
)
20 changes: 10 additions & 10 deletions api/src/main/kotlin/handler/UserHandler.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.wafflestudio.snu4t.handler

import com.wafflestudio.snu4t.auth.SocialProvider
import com.wafflestudio.snu4t.auth.AuthProvider
import com.wafflestudio.snu4t.common.dto.OkResponse
import com.wafflestudio.snu4t.common.extension.toZonedDateTime
import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware
import com.wafflestudio.snu4t.users.data.User
import com.wafflestudio.snu4t.users.dto.AuthProvidersCheckDto
import com.wafflestudio.snu4t.users.dto.EmailVerificationResultDto
import com.wafflestudio.snu4t.users.dto.LocalLoginRequest
import com.wafflestudio.snu4t.users.dto.PasswordChangeRequest
import com.wafflestudio.snu4t.users.dto.SendEmailRequest
import com.wafflestudio.snu4t.users.dto.SocialLoginRequest
import com.wafflestudio.snu4t.users.dto.SocialProvidersCheckDto
import com.wafflestudio.snu4t.users.dto.UserDto
import com.wafflestudio.snu4t.users.dto.UserLegacyDto
import com.wafflestudio.snu4t.users.dto.UserPatchRequest
Expand Down Expand Up @@ -106,46 +106,46 @@ class UserHandler(
handle(req) {
val user = req.getContext().user!!
val socialLoginRequest: SocialLoginRequest = req.awaitBody()
userService.attachSocial(user, socialLoginRequest, SocialProvider.FACEBOOK)
userService.attachSocial(user, socialLoginRequest, AuthProvider.FACEBOOK)
}

suspend fun attachGoogle(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
val socialLoginRequest: SocialLoginRequest = req.awaitBody()
userService.attachSocial(user, socialLoginRequest, SocialProvider.GOOGLE)
userService.attachSocial(user, socialLoginRequest, AuthProvider.GOOGLE)
}

suspend fun attachKakao(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
val socialLoginRequest: SocialLoginRequest = req.awaitBody()
userService.attachSocial(user, socialLoginRequest, SocialProvider.KAKAO)
userService.attachSocial(user, socialLoginRequest, AuthProvider.KAKAO)
}

suspend fun detachFacebook(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
userService.detachSocial(user, SocialProvider.FACEBOOK)
userService.detachSocial(user, AuthProvider.FACEBOOK)
}

suspend fun detachGoogle(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
userService.detachSocial(user, SocialProvider.GOOGLE)
userService.detachSocial(user, AuthProvider.GOOGLE)
}

suspend fun detachKakao(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
userService.detachSocial(user, SocialProvider.KAKAO)
userService.detachSocial(user, AuthProvider.KAKAO)
}

suspend fun checkSocialProviders(req: ServerRequest): ServerResponse =
suspend fun checkAuthProviders(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!

SocialProvidersCheckDto(
AuthProvidersCheckDto(
local = user.credential.localId != null,
facebook = user.credential.fbName != null,
google = user.credential.googleSub != null,
Expand Down
3 changes: 2 additions & 1 deletion api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ class MainRouter(
"/users".nest {
GET("/me", userHandler::getUserMe)
PATCH("/me", userHandler::patchUserInfo)
GET("/me/social_providers", userHandler::checkSocialProviders)
GET("/me/social_providers", userHandler::checkAuthProviders)
GET("/me/auth-providers", userHandler::checkAuthProviders)
}
}

Expand Down
8 changes: 4 additions & 4 deletions api/src/main/kotlin/router/docs/UserDocs.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.wafflestudio.snu4t.router.docs

import com.wafflestudio.snu4t.common.dto.OkResponse
import com.wafflestudio.snu4t.users.dto.AuthProvidersCheckDto
import com.wafflestudio.snu4t.users.dto.EmailVerificationResultDto
import com.wafflestudio.snu4t.users.dto.LocalLoginRequest
import com.wafflestudio.snu4t.users.dto.PasswordChangeRequest
import com.wafflestudio.snu4t.users.dto.SendEmailRequest
import com.wafflestudio.snu4t.users.dto.SocialLoginRequest
import com.wafflestudio.snu4t.users.dto.SocialProvidersCheckDto
import com.wafflestudio.snu4t.users.dto.TokenResponse
import com.wafflestudio.snu4t.users.dto.UserDto
import com.wafflestudio.snu4t.users.dto.UserLegacyDto
Expand Down Expand Up @@ -81,16 +81,16 @@ import org.springframework.web.bind.annotation.RequestMethod
),
),
RouterOperation(
path = "/v1/users/me/social_providers",
path = "/v1/users/me/auth-providers",
method = [RequestMethod.GET],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "getSocialProviders",
operationId = "getAuthProviders",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = SocialProvidersCheckDto::class))],
content = [Content(schema = Schema(implementation = AuthProvidersCheckDto::class))],
),
],
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.wafflestudio.snu4t.auth

enum class SocialProvider(val value: String) {
enum class AuthProvider(val value: String) {
LOCAL("local"),
FACEBOOK("facebook"),
APPLE("apple"),
Expand All @@ -11,6 +11,6 @@ enum class SocialProvider(val value: String) {
companion object {
private val mapping = entries.associateBy { e -> e.value }

fun from(value: String): SocialProvider? = mapping[value]
fun from(value: String): AuthProvider? = mapping[value]
}
}
2 changes: 2 additions & 0 deletions core/src/main/kotlin/common/exception/ErrorType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ enum class ErrorType(
EV_DATA_NOT_FOUND(HttpStatus.NOT_FOUND, 40407, "강의평 데이터를 찾을 수 없습니다.", "강의평 데이터를 찾을 수 없습니다."),
TAG_LIST_NOT_FOUND(HttpStatus.NOT_FOUND, 40408, "태그 리스트를 찾을 수 없습니다.", "태그 리스트를 찾을 수 없습니다."),
FRIEND_LINK_NOT_FOUND(HttpStatus.NOT_FOUND, 40409, "친구 링크가 유효하지 않습니다.", "친구 링크가 유효하지 않습니다."),
SOCIAL_PROVIDER_NOT_ATTACHED(HttpStatus.NOT_FOUND, 40410, "소셜 계정이 연동되지 않았습니다.", "소셜 계정이 연동되지 않았습니다."),

DUPLICATE_VACANCY_NOTIFICATION(HttpStatus.CONFLICT, 40900, "빈자리 알림 중복", "이미 등록된 빈자리 알림입니다."),
DUPLICATE_EMAIL(HttpStatus.CONFLICT, 40901, "이미 사용 중인 이메일입니다.", "이미 사용 중인 이메일입니다."),
Expand All @@ -75,6 +76,7 @@ enum class ErrorType(
DUPLICATE_POPUP_KEY(HttpStatus.CONFLICT, 40906, "중복된 팝업 키입니다.", "중복된 팝업 키입니다."),
ALREADY_DOWNLOADED_THEME(HttpStatus.CONFLICT, 40907, "이미 다운로드한 테마입니다.", "이미 다운로드한 테마입니다."),
DUPLICATE_SOCIAL_ACCOUNT(HttpStatus.CONFLICT, 40908, "이미 연결된 소셜 계정입니다.", "이미 연결된 소셜 계정입니다."),
CANNOT_REMOVE_LAST_AUTH_PROVIDER(HttpStatus.CONFLICT, 40909, "최소 한 개의 로그인 수단은 유지해야 합니다.", "최소 한 개의 로그인 수단은 유지해야 합니다."),

DYNAMIC_LINK_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, 50001, "링크 생성 실패", "링크 생성에 실패했습니다. 잠시 후 다시 시도해주세요."),
}
23 changes: 18 additions & 5 deletions core/src/main/kotlin/common/exception/Snu4tException.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.wafflestudio.snu4t.common.exception

import com.wafflestudio.snu4t.auth.SocialProvider
import com.wafflestudio.snu4t.auth.AuthProvider

open class Snu4tException(
val error: ErrorType = ErrorType.DEFAULT_ERROR,
val errorMessage: String = error.errorMessage,
val displayMessage: String = error.displayMessage,
val detail: Any? = null,
// TODO: 구버전 대응용 ext 필드. 추후 삭제
val ext: Map<String, String> = mapOf(),
) : RuntimeException(errorMessage)
Expand Down Expand Up @@ -95,7 +94,6 @@ object WrongSemesterException : Snu4tException(ErrorType.WRONG_SEMESTER)
class LectureTimeOverlapException(confirmMessage: String) : Snu4tException(
error = ErrorType.LECTURE_TIME_OVERLAP,
displayMessage = confirmMessage,
ext = mapOf("confirm_message" to confirmMessage),
)

object CustomLectureResetException : Snu4tException(ErrorType.CANNOT_RESET_CUSTOM_LECTURE)
Expand All @@ -112,6 +110,8 @@ object FriendNotFoundException : Snu4tException(ErrorType.FRIEND_NOT_FOUND)

object FriendLinkNotFoundException : Snu4tException(ErrorType.FRIEND_LINK_NOT_FOUND)

object SocialProviderNotAttachedException : Snu4tException(ErrorType.SOCIAL_PROVIDER_NOT_ATTACHED)

object UserNotFoundByNicknameException : Snu4tException(ErrorType.USER_NOT_FOUND_BY_NICKNAME)

object ThemeNotFoundException : Snu4tException(ErrorType.THEME_NOT_FOUND)
Expand All @@ -122,9 +122,20 @@ object TagListNotFoundException : Snu4tException(ErrorType.TAG_LIST_NOT_FOUND)

object DuplicateVacancyNotificationException : Snu4tException(ErrorType.DUPLICATE_VACANCY_NOTIFICATION)

class DuplicateEmailException(socialProvider: SocialProvider) : Snu4tException(
class DuplicateEmailException(authProviders: List<AuthProvider>) : Snu4tException(
ErrorType.DUPLICATE_EMAIL,
detail = mapOf("socialProvider" to socialProvider),
displayMessage =
run {
val socialProviders = authProviders.filter { it != AuthProvider.LOCAL }
when {
authProviders.contains(AuthProvider.LOCAL) && socialProviders.isEmpty() -> "이미 가입된 이메일입니다. 아이디 찾기를 이용해주세요."
authProviders.contains(AuthProvider.LOCAL) && socialProviders.isNotEmpty() ->
"이미 가입된 이메일입니다. (${socialProviders.joinToString(", ") { it.value }}) 중 하나의 계정으로 로그인하거나 '아이디 찾기'를 이용해주세요."

socialProviders.isNotEmpty() -> "이미 가입된 이메일입니다. 이전에 가입한 ${socialProviders.joinToString(", ") { it.value }} 계정으로 로그인해주세요."
else -> throw IllegalStateException("로그인 방법이 없는 계정")
}
},
)

object DuplicateFriendException : Snu4tException(ErrorType.DUPLICATE_FRIEND)
Expand All @@ -141,4 +152,6 @@ object AlreadyDownloadedThemeException : Snu4tException(ErrorType.ALREADY_DOWNLO

object DuplicateSocialAccountException : Snu4tException(ErrorType.DUPLICATE_SOCIAL_ACCOUNT)

object CannotRemoveLastAuthProviderException : Snu4tException(ErrorType.CANNOT_REMOVE_LAST_AUTH_PROVIDER)

object DynamicLinkGenerationFailedException : Snu4tException(ErrorType.DYNAMIC_LINK_GENERATION_FAILED)
14 changes: 0 additions & 14 deletions core/src/main/kotlin/users/data/Credential.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,17 @@
package com.wafflestudio.snu4t.users.data

import org.springframework.data.mongodb.core.mapping.Field

data class Credential(
@Field(write = Field.Write.ALWAYS)
var localId: String? = null,
@Field(write = Field.Write.ALWAYS)
var localPw: String? = null,
@Field(write = Field.Write.ALWAYS)
var fbId: String? = null,
@Field(write = Field.Write.ALWAYS)
var fbName: String? = null,
@Field(write = Field.Write.ALWAYS)
var appleSub: String? = null,
@Field(write = Field.Write.ALWAYS)
var appleEmail: String? = null,
var appleTransferSub: String? = null,
@Field(write = Field.Write.ALWAYS)
var googleSub: String? = null,
@Field(write = Field.Write.ALWAYS)
var googleEmail: String? = null,
@Field(write = Field.Write.ALWAYS)
var kakaoSub: String? = null,
@Field(write = Field.Write.ALWAYS)
var kakaoEmail: String? = null,
@Field(write = Field.Write.ALWAYS)
var tempDate: String? = null,
@Field(write = Field.Write.ALWAYS)
var tempSeed: String? = null,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.wafflestudio.snu4t.users.dto

data class SocialProvidersCheckDto(
data class AuthProvidersCheckDto(
val local: Boolean,
val facebook: Boolean,
val google: Boolean,
Expand Down
10 changes: 5 additions & 5 deletions core/src/main/kotlin/users/service/AuthService.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.wafflestudio.snu4t.users.service

import com.fasterxml.jackson.databind.ObjectMapper
import com.wafflestudio.snu4t.auth.AuthProvider
import com.wafflestudio.snu4t.auth.OAuth2Client
import com.wafflestudio.snu4t.auth.OAuth2UserResponse
import com.wafflestudio.snu4t.auth.SocialProvider
import com.wafflestudio.snu4t.common.exception.SocialConnectFailException
import com.wafflestudio.snu4t.users.data.Credential
import com.wafflestudio.snu4t.users.data.User
Expand Down Expand Up @@ -41,7 +41,7 @@ interface AuthService {
fun buildKakaoCredential(oAuth2UserResponse: OAuth2UserResponse): Credential

suspend fun socialLoginWithAccessToken(
socialProvider: SocialProvider,
authProvider: AuthProvider,
token: String,
): OAuth2UserResponse
}
Expand All @@ -59,7 +59,7 @@ class AuthServiceImpl(
private val emailRegex = """^[a-zA-Z0-9]([-_.]?[a-zA-Z0-9])*@[a-zA-Z0-9]([-_.]?[a-zA-Z0-9])*.[a-zA-Z]{2,3}$""".toRegex()
}

private val clients = clients.mapKeys { SocialProvider.valueOf(it.key) }
private val clients = clients.mapKeys { AuthProvider.valueOf(it.key) }

override fun isValidLocalId(localId: String) = localId.matches(localIdRegex)

Expand Down Expand Up @@ -116,10 +116,10 @@ class AuthServiceImpl(
}

override suspend fun socialLoginWithAccessToken(
socialProvider: SocialProvider,
authProvider: AuthProvider,
token: String,
): OAuth2UserResponse {
val oAuth2Client = checkNotNull(clients[socialProvider])
val oAuth2Client = checkNotNull(clients[authProvider])

return oAuth2Client.getMe(token) ?: throw SocialConnectFailException
}
Expand Down
Loading

0 comments on commit 3e765c8

Please sign in to comment.