test #383
@@ -41,6 +41,8 @@ dependencies {
|
|||||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
|
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
|
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
|
||||||
|
|
||||||
|
implementation("com.nimbusds:nimbus-jose-jwt:9.37.3")
|
||||||
|
|
||||||
// querydsl (추가 설정)
|
// querydsl (추가 설정)
|
||||||
implementation("com.querydsl:querydsl-jpa:$querydslVersion")
|
implementation("com.querydsl:querydsl-jpa:$querydslVersion")
|
||||||
kapt("com.querydsl:querydsl-apt:$querydslVersion:jpa")
|
kapt("com.querydsl:querydsl-apt:$querydslVersion:jpa")
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class SecurityConfig(
|
|||||||
.antMatchers("/member/login").permitAll()
|
.antMatchers("/member/login").permitAll()
|
||||||
.antMatchers("/member/login/google").permitAll()
|
.antMatchers("/member/login/google").permitAll()
|
||||||
.antMatchers("/member/login/kakao").permitAll()
|
.antMatchers("/member/login/kakao").permitAll()
|
||||||
|
.antMatchers("/member/login/apple").permitAll()
|
||||||
.antMatchers("/creator-admin/member/login").permitAll()
|
.antMatchers("/creator-admin/member/login").permitAll()
|
||||||
.antMatchers("/member/forgot-password").permitAll()
|
.antMatchers("/member/forgot-password").permitAll()
|
||||||
.antMatchers("/stplat/terms_of_service").permitAll()
|
.antMatchers("/stplat/terms_of_service").permitAll()
|
||||||
|
|||||||
@@ -1280,6 +1280,11 @@ class SodaMessageSource {
|
|||||||
Lang.EN to "Kakao login failed. Please try again.",
|
Lang.EN to "Kakao login failed. Please try again.",
|
||||||
Lang.JA to "Kakaoでログインできませんでした。もう一度お試しください。"
|
Lang.JA to "Kakaoでログインできませんでした。もう一度お試しください。"
|
||||||
),
|
),
|
||||||
|
"member.social.apple_login_failed" to mapOf(
|
||||||
|
Lang.KO to "애플 로그인을 하지 못했습니다. 다시 시도해 주세요",
|
||||||
|
Lang.EN to "Apple sign-in failed. Please try again.",
|
||||||
|
Lang.JA to "Appleでログインできませんでした。もう一度お試しください。"
|
||||||
|
),
|
||||||
"member.social.email_consent_required" to mapOf(
|
"member.social.email_consent_required" to mapOf(
|
||||||
Lang.KO to "이메일 제공에 동의하셔야 서비스 이용이 가능합니다.",
|
Lang.KO to "이메일 제공에 동의하셔야 서비스 이용이 가능합니다.",
|
||||||
Lang.EN to "You must agree to provide your email to use the service.",
|
Lang.EN to "You must agree to provide your email to use the service.",
|
||||||
|
|||||||
@@ -343,7 +343,8 @@ class MemberController(
|
|||||||
@RequestHeader("Authorization") authHeader: String,
|
@RequestHeader("Authorization") authHeader: String,
|
||||||
@RequestBody request: SocialLoginRequest
|
@RequestBody request: SocialLoginRequest
|
||||||
): ApiResponse<LoginResponse> {
|
): ApiResponse<LoginResponse> {
|
||||||
return processSocialLogin(MemberProvider.GOOGLE, authHeader, request)
|
val token = extractBearerToken(authHeader, MemberProvider.GOOGLE)
|
||||||
|
return processSocialLogin(MemberProvider.GOOGLE, token, request, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login/kakao")
|
@PostMapping("/login/kakao")
|
||||||
@@ -351,27 +352,37 @@ class MemberController(
|
|||||||
@RequestHeader("Authorization") authHeader: String,
|
@RequestHeader("Authorization") authHeader: String,
|
||||||
@RequestBody request: SocialLoginRequest
|
@RequestBody request: SocialLoginRequest
|
||||||
): ApiResponse<LoginResponse> {
|
): ApiResponse<LoginResponse> {
|
||||||
return processSocialLogin(MemberProvider.KAKAO, authHeader, request)
|
val token = extractBearerToken(authHeader, MemberProvider.KAKAO)
|
||||||
|
return processSocialLogin(MemberProvider.KAKAO, token, request, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login/apple")
|
||||||
|
fun loginApple(
|
||||||
|
@RequestBody request: SocialLoginRequest
|
||||||
|
): ApiResponse<LoginResponse> {
|
||||||
|
val errorKey = socialLoginErrorKey(MemberProvider.APPLE)
|
||||||
|
val token = request.identityToken?.takeIf { it.isNotBlank() }
|
||||||
|
?: throw SodaException(messageKey = errorKey)
|
||||||
|
val nonce = request.nonce?.takeIf { it.isNotBlank() }
|
||||||
|
?: throw SodaException(messageKey = errorKey)
|
||||||
|
|
||||||
|
return processSocialLogin(MemberProvider.APPLE, token, request, nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSocialLogin(
|
private fun processSocialLogin(
|
||||||
provider: MemberProvider,
|
provider: MemberProvider,
|
||||||
authHeader: String,
|
token: String,
|
||||||
request: SocialLoginRequest
|
request: SocialLoginRequest,
|
||||||
|
nonce: String?
|
||||||
): ApiResponse<LoginResponse> {
|
): ApiResponse<LoginResponse> {
|
||||||
val errorKey = when (provider) {
|
|
||||||
MemberProvider.GOOGLE -> "member.social.google_login_failed"
|
|
||||||
MemberProvider.KAKAO -> "member.social.kakao_login_failed"
|
|
||||||
else -> "common.error.bad_request"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authHeader.startsWith("Bearer ")) {
|
|
||||||
throw SodaException(messageKey = errorKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
val token = authHeader.substring(7)
|
|
||||||
val authService = socialAuthServiceResolver.resolve(provider)
|
val authService = socialAuthServiceResolver.resolve(provider)
|
||||||
val response = authService.authenticate(token, request.container, request.marketingPid, request.pushToken)
|
val response = authService.authenticate(
|
||||||
|
token = token,
|
||||||
|
container = request.container,
|
||||||
|
marketingPid = request.marketingPid,
|
||||||
|
pushToken = request.pushToken,
|
||||||
|
nonce = nonce
|
||||||
|
)
|
||||||
|
|
||||||
if (!response.marketingPid.isNullOrBlank()) {
|
if (!response.marketingPid.isNullOrBlank()) {
|
||||||
trackingService.saveTrackingHistory(
|
trackingService.saveTrackingHistory(
|
||||||
@@ -392,4 +403,21 @@ class MemberController(
|
|||||||
val message = messageSource.getMessage("member.signup.success", langContext.lang)
|
val message = messageSource.getMessage("member.signup.success", langContext.lang)
|
||||||
return ApiResponse.ok(message = message, data = response.loginResponse)
|
return ApiResponse.ok(message = message, data = response.loginResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractBearerToken(authHeader: String, provider: MemberProvider): String {
|
||||||
|
val errorKey = socialLoginErrorKey(provider)
|
||||||
|
if (!authHeader.startsWith("Bearer ")) {
|
||||||
|
throw SodaException(messageKey = errorKey)
|
||||||
|
}
|
||||||
|
return authHeader.substring(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun socialLoginErrorKey(provider: MemberProvider): String {
|
||||||
|
return when (provider) {
|
||||||
|
MemberProvider.GOOGLE -> "member.social.google_login_failed"
|
||||||
|
MemberProvider.KAKAO -> "member.social.kakao_login_failed"
|
||||||
|
MemberProvider.APPLE -> "member.social.apple_login_failed"
|
||||||
|
else -> "common.error.bad_request"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ interface MemberRepository : JpaRepository<Member, Long>, MemberQueryRepository
|
|||||||
fun findByNickname(nickname: String): Member?
|
fun findByNickname(nickname: String): Member?
|
||||||
fun findByGoogleId(googleId: String): Member?
|
fun findByGoogleId(googleId: String): Member?
|
||||||
fun findByKakaoId(kakaoId: Long): Member?
|
fun findByKakaoId(kakaoId: Long): Member?
|
||||||
|
fun findByAppleId(appleId: String): Member?
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MemberQueryRepository {
|
interface MemberQueryRepository {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
|
|||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
||||||
import kr.co.vividnext.sodalive.member.social.MemberResolveResult
|
import kr.co.vividnext.sodalive.member.social.MemberResolveResult
|
||||||
|
import kr.co.vividnext.sodalive.member.social.apple.AppleUserInfo
|
||||||
import kr.co.vividnext.sodalive.member.social.google.GoogleUserInfo
|
import kr.co.vividnext.sodalive.member.social.google.GoogleUserInfo
|
||||||
import kr.co.vividnext.sodalive.member.social.kakao.KakaoUserInfo
|
import kr.co.vividnext.sodalive.member.social.kakao.KakaoUserInfo
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
||||||
@@ -932,6 +933,63 @@ class MemberService(
|
|||||||
return MemberResolveResult(member = member, isNew = true)
|
return MemberResolveResult(member = member, isNew = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun findOrRegister(
|
||||||
|
appleUserInfo: AppleUserInfo,
|
||||||
|
container: String,
|
||||||
|
marketingPid: String?,
|
||||||
|
pushToken: String?
|
||||||
|
): MemberResolveResult {
|
||||||
|
val findMember = repository.findByAppleId(appleUserInfo.sub)
|
||||||
|
if (findMember != null) {
|
||||||
|
if (findMember.isActive) {
|
||||||
|
return MemberResolveResult(member = findMember, isNew = false)
|
||||||
|
} else {
|
||||||
|
throw SodaException(messageKey = "member.validation.inactive_account")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
||||||
|
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
|
||||||
|
|
||||||
|
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
|
||||||
|
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
|
||||||
|
|
||||||
|
val email = appleUserInfo.email
|
||||||
|
checkEmail(email)
|
||||||
|
|
||||||
|
val nickname = nicknameGenerateService.generateUniqueNickname()
|
||||||
|
val member = Member(
|
||||||
|
appleId = appleUserInfo.sub,
|
||||||
|
email = email,
|
||||||
|
password = "",
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "profile/default-profile.png",
|
||||||
|
gender = Gender.NONE,
|
||||||
|
provider = MemberProvider.APPLE,
|
||||||
|
container = container,
|
||||||
|
countryCode = countryContext.countryCode
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!marketingPid.isNullOrBlank()) {
|
||||||
|
member.activePid = marketingPid
|
||||||
|
member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.save(member)
|
||||||
|
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
|
||||||
|
|
||||||
|
if (pushToken != null) {
|
||||||
|
pushTokenService.registerToken(
|
||||||
|
memberId = member.id!!,
|
||||||
|
token = pushToken,
|
||||||
|
deviceType = container
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MemberResolveResult(member = member, isNew = true)
|
||||||
|
}
|
||||||
|
|
||||||
private fun findMemberByUsername(username: String): Member? {
|
private fun findMemberByUsername(username: String): Member? {
|
||||||
return if (username.startsWith("member:")) {
|
return if (username.startsWith("member:")) {
|
||||||
val id = username.substringAfter("member:").toLongOrNull()
|
val id = username.substringAfter("member:").toLongOrNull()
|
||||||
|
|||||||
@@ -10,5 +10,7 @@ data class LoginRequest(
|
|||||||
data class SocialLoginRequest(
|
data class SocialLoginRequest(
|
||||||
val container: String,
|
val container: String,
|
||||||
val pushToken: String? = null,
|
val pushToken: String? = null,
|
||||||
val marketingPid: String? = null
|
val marketingPid: String? = null,
|
||||||
|
val identityToken: String? = null,
|
||||||
|
val nonce: String? = null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface SocialAuthService {
|
|||||||
token: String,
|
token: String,
|
||||||
container: String,
|
container: String,
|
||||||
marketingPid: String?,
|
marketingPid: String?,
|
||||||
pushToken: String?
|
pushToken: String?,
|
||||||
|
nonce: String?
|
||||||
): SocialLoginResponse
|
): SocialLoginResponse
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package kr.co.vividnext.sodalive.member.social.apple
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
|
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||||
|
import kr.co.vividnext.sodalive.member.social.SocialAuthService
|
||||||
|
import kr.co.vividnext.sodalive.member.social.SocialLoginResponse
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AppleAuthService(
|
||||||
|
private val appleIdentityTokenVerifier: AppleIdentityTokenVerifier,
|
||||||
|
private val memberService: MemberService,
|
||||||
|
private val tokenProvider: TokenProvider,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) : SocialAuthService {
|
||||||
|
override fun getProvider(): MemberProvider = MemberProvider.APPLE
|
||||||
|
|
||||||
|
override fun authenticate(
|
||||||
|
token: String,
|
||||||
|
container: String,
|
||||||
|
marketingPid: String?,
|
||||||
|
pushToken: String?,
|
||||||
|
nonce: String?
|
||||||
|
): SocialLoginResponse {
|
||||||
|
val rawNonce = nonce?.takeIf { it.isNotBlank() }
|
||||||
|
?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
|
||||||
|
val appleUserInfo = appleIdentityTokenVerifier.verify(token, rawNonce)
|
||||||
|
val memberResolveResult = memberService.findOrRegister(appleUserInfo, container, marketingPid, pushToken)
|
||||||
|
val member = memberResolveResult.member
|
||||||
|
val principal = MemberAdapter(member)
|
||||||
|
val authToken = AppleAuthenticationToken(token, principal.authorities)
|
||||||
|
authToken.setPrincipal(principal)
|
||||||
|
SecurityContextHolder.getContext().authentication = authToken
|
||||||
|
|
||||||
|
val jwt = tokenProvider.createToken(
|
||||||
|
authentication = authToken,
|
||||||
|
memberId = member.id!!
|
||||||
|
)
|
||||||
|
|
||||||
|
val loginResponse = LoginResponse(
|
||||||
|
userId = member.id!!,
|
||||||
|
token = jwt,
|
||||||
|
nickname = member.nickname,
|
||||||
|
email = member.email ?: "",
|
||||||
|
profileImage = if (member.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${member.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return SocialLoginResponse(
|
||||||
|
memberId = member.id!!,
|
||||||
|
marketingPid = marketingPid,
|
||||||
|
loginResponse = loginResponse,
|
||||||
|
isNew = memberResolveResult.isNew
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package kr.co.vividnext.sodalive.member.social.apple
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||||
|
import org.springframework.security.core.GrantedAuthority
|
||||||
|
|
||||||
|
class AppleAuthenticationToken(
|
||||||
|
private val idToken: String,
|
||||||
|
authorities: Collection<GrantedAuthority>? = null
|
||||||
|
) : AbstractAuthenticationToken(authorities) {
|
||||||
|
private var principal: Any? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
isAuthenticated = authorities != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCredentials(): Any = idToken
|
||||||
|
|
||||||
|
override fun getPrincipal(): Any? = principal
|
||||||
|
|
||||||
|
fun setPrincipal(principal: Any) {
|
||||||
|
this.principal = principal
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package kr.co.vividnext.sodalive.member.social.apple
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSource
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSourceBuilder
|
||||||
|
import com.nimbusds.jose.proc.JWSVerificationKeySelector
|
||||||
|
import com.nimbusds.jose.proc.SecurityContext
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet
|
||||||
|
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor
|
||||||
|
import com.nimbusds.jwt.proc.DefaultJWTProcessor
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.net.URL
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AppleIdentityTokenVerifier(
|
||||||
|
@Value("\${apple.bundle-id}")
|
||||||
|
private val bundleId: String
|
||||||
|
) {
|
||||||
|
private val jwkUrl = URL("https://appleid.apple.com/auth/keys")
|
||||||
|
private val jwkSource: JWKSource<SecurityContext> = JWKSourceBuilder.create<SecurityContext>(jwkUrl)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val jwtProcessor: ConfigurableJWTProcessor<SecurityContext> =
|
||||||
|
DefaultJWTProcessor<SecurityContext>().apply {
|
||||||
|
jwsKeySelector = JWSVerificationKeySelector(JWSAlgorithm.RS256, jwkSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verify(identityToken: String, rawNonce: String): AppleUserInfo {
|
||||||
|
if (bundleId.isBlank()) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawNonce.isBlank()) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
val claims = try {
|
||||||
|
jwtProcessor.process(identityToken, null)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
validateClaims(claims, rawNonce)
|
||||||
|
|
||||||
|
return AppleUserInfo(
|
||||||
|
sub = claims.subject ?: throw SodaException(messageKey = "member.social.apple_login_failed"),
|
||||||
|
email = claims.getStringClaim("email")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateClaims(claims: JWTClaimsSet, rawNonce: String) {
|
||||||
|
if (claims.issuer != ISSUER) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!claims.audience.contains(bundleId)) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = Date()
|
||||||
|
val expirationTime = claims.expirationTime ?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
if (expirationTime.before(now)) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
val issuedAt = claims.issueTime ?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
if (issuedAt.after(now)) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
val nonce = claims.getStringClaim("nonce") ?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
val expectedNonce = hashNonce(rawNonce)
|
||||||
|
if (nonce != expectedNonce) {
|
||||||
|
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hashNonce(rawNonce: String): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val hashed = digest.digest(rawNonce.toByteArray(StandardCharsets.UTF_8))
|
||||||
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(hashed)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ISSUER = "https://appleid.apple.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package kr.co.vividnext.sodalive.member.social.apple
|
||||||
|
|
||||||
|
data class AppleUserInfo(
|
||||||
|
val sub: String,
|
||||||
|
val email: String?
|
||||||
|
)
|
||||||
@@ -27,7 +27,8 @@ class GoogleAuthService(
|
|||||||
token: String,
|
token: String,
|
||||||
container: String,
|
container: String,
|
||||||
marketingPid: String?,
|
marketingPid: String?,
|
||||||
pushToken: String?
|
pushToken: String?,
|
||||||
|
nonce: String?
|
||||||
): SocialLoginResponse {
|
): SocialLoginResponse {
|
||||||
val googleUserInfo = googleService.getUserInfo(token)
|
val googleUserInfo = googleService.getUserInfo(token)
|
||||||
?: throw SodaException(messageKey = "member.social.google_login_failed")
|
?: throw SodaException(messageKey = "member.social.google_login_failed")
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ class KakaoAuthService(
|
|||||||
token: String,
|
token: String,
|
||||||
container: String,
|
container: String,
|
||||||
marketingPid: String?,
|
marketingPid: String?,
|
||||||
pushToken: String?
|
pushToken: String?,
|
||||||
|
nonce: String?
|
||||||
): SocialLoginResponse {
|
): SocialLoginResponse {
|
||||||
val kakaoUserInfo = kakaoService.getUserInfo(token)
|
val kakaoUserInfo = kakaoService.getUserInfo(token)
|
||||||
?: throw SodaException(messageKey = "member.social.kakao_login_failed")
|
?: throw SodaException(messageKey = "member.social.kakao_login_failed")
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ bootpay:
|
|||||||
apple:
|
apple:
|
||||||
iapVerifyUrl: https://buy.itunes.apple.com/verifyReceipt
|
iapVerifyUrl: https://buy.itunes.apple.com/verifyReceipt
|
||||||
iapVerifySandboxUrl: https://sandbox.itunes.apple.com/verifyReceipt
|
iapVerifySandboxUrl: https://sandbox.itunes.apple.com/verifyReceipt
|
||||||
|
bundleId: ${APPLE_BUNDLE_ID}
|
||||||
|
|
||||||
agora:
|
agora:
|
||||||
appId: ${AGORA_APP_ID}
|
appId: ${AGORA_APP_ID}
|
||||||
|
|||||||
Reference in New Issue
Block a user