-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] 구글 자동 로그인을 구현 한다. #129
Changes from all commits
b846483
fdfb666
0d42800
9b59d5b
002eab0
54f8dbf
e7a62e4
ee8ea8d
936ad9c
d5b71f6
74a2842
5924e94
45b300f
5ed501f
851b1ba
217bf2d
ac0edfe
ac02f16
97e1617
baf79f7
03256aa
d8356d7
7d91c21
ba09535
a21107a
d55bcd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.benenfeldt.remote.api | ||
|
||
import com.benenfeldt.remote.dto.BaseResponse | ||
import com.benenfeldt.remote.dto.TokenInformationResponse | ||
import retrofit2.http.GET | ||
|
||
interface TokenService { | ||
@GET("/api/v1/token") | ||
suspend fun getTokenInformation(): Result<BaseResponse<TokenInformationResponse>> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.benenfeldt.remote.dto | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class TokenInformationResponse( | ||
val role: String, | ||
val validToken: Boolean, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
package com.benenfeldt.remote.dto | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class UserSignUpResponse( | ||
val id: Int, | ||
val department: String, | ||
val nickname: String, | ||
val username: String, | ||
@SerialName("username") | ||
val email: String, | ||
val accessToken: String, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.benenfeldt.remote.mapper | ||
|
||
import com.benenfeldt.remote.dto.TokenInformationResponse | ||
import com.benenfeldt.remote.token.TokenInformation | ||
import com.benenfeldt.remote.token.TokenRole | ||
|
||
fun TokenInformationResponse.toTokenInformation(): TokenInformation { | ||
return TokenInformation( | ||
role = TokenRole.valueOf(role), | ||
isValidToken = validToken, | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.benenfeldt.remote.token | ||
|
||
interface AuthCallback { | ||
fun onAuthRequired() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.benenfeldt.remote.token | ||
|
||
import kotlinx.coroutines.runBlocking | ||
import okhttp3.Authenticator | ||
import okhttp3.Request | ||
import okhttp3.Response | ||
import okhttp3.Route | ||
import javax.inject.Inject | ||
|
||
class HY2Authenticator | ||
@Inject | ||
constructor( | ||
private val authCallback: AuthCallback, | ||
private val jwtManager: JwtManager, | ||
) : Authenticator { | ||
override fun authenticate( | ||
route: Route?, | ||
response: Response, | ||
): Request? { | ||
if (response.code == 401) { | ||
runBlocking { jwtManager.clearAllTokens() } | ||
authCallback.onAuthRequired() | ||
|
||
// null을 반환하여 현재 요청을 중단 | ||
return null | ||
} | ||
return null | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.benenfeldt.remote.token | ||
|
||
data class TokenInformation( | ||
val role: TokenRole, | ||
val isValidToken: Boolean, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.benenfeldt.remote.token | ||
|
||
/** | ||
* USER - 구글 로그인을 마치고, 회원가입을 한 유저 | ||
* | ||
* GUEST - 구글 로그인을 했지만, 회원가입을 하지 않은 유저 | ||
* | ||
* ADMIN - 관리자 계정, 하지만 앱으로 로그인 하는 경우는 없음 | ||
* */ | ||
|
||
enum class TokenRole { | ||
USER, | ||
GUEST, | ||
ADMIN, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.benenfeldt.remote.token | ||
|
||
import com.benenfeldt.remote.api.TokenService | ||
import com.benenfeldt.remote.mapper.toResult | ||
import com.benenfeldt.remote.mapper.toTokenInformation | ||
import javax.inject.Inject | ||
|
||
class TokenValidator | ||
@Inject | ||
constructor( | ||
private val tokenService: TokenService, | ||
) { | ||
suspend fun validate(): Result<TokenInformation> { | ||
return tokenService.getTokenInformation() | ||
.toResult { it.data.toTokenInformation() } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,18 @@ package com.teamhy2.feature.main | |
import android.util.Log | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.benenfeldt.remote.token.JwtManager | ||
import com.benenfeldt.remote.token.TokenRole | ||
import com.benenfeldt.remote.token.TokenValidator | ||
import com.google.firebase.firestore.FirebaseFirestore | ||
import com.teamhy2.feature.home.navigation.Home | ||
import com.teamhy2.hongikyeolgong2.notification.NotificationHandler | ||
import com.teamhy2.onboarding.domain.repository.WebViewRepository | ||
import com.teamhy2.onboarding.navigation.Onboarding | ||
import com.teamhy2.onboarding.navigation.SignUp | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.Deferred | ||
import kotlinx.coroutines.async | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharedFlow | ||
|
@@ -24,6 +31,8 @@ class InitialViewModel | |
@Inject | ||
constructor( | ||
private val webViewRepository: WebViewRepository, | ||
private val jwtManager: JwtManager, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전 PR에서 다루지 못해서 죄송하지만 |
||
private val tokenValidator: TokenValidator, | ||
val notificationHandler: NotificationHandler, | ||
) : ViewModel() { | ||
private val _initialUiState: MutableStateFlow<InitialUiState> = | ||
|
@@ -34,40 +43,44 @@ class InitialViewModel | |
val errorFlow: SharedFlow<Throwable> = _errorFlow.asSharedFlow() | ||
|
||
init { | ||
fetchStartDestination() | ||
fetchFirebaseUrls() | ||
initInitialState() | ||
} | ||
|
||
private fun fetchStartDestination() { | ||
fun setStartDestination(startDestination: String) { | ||
if (_initialUiState.value is InitialUiState.Loading) { | ||
_initialUiState.update { InitialUiState.Success(startDestination = startDestination) } | ||
return | ||
} | ||
if (_initialUiState.value is InitialUiState.Success) { | ||
_initialUiState.update { | ||
(it as InitialUiState.Success).copy(startDestination = startDestination) | ||
} | ||
private fun initInitialState() { | ||
viewModelScope.launch { | ||
val startDestination: Deferred<String> = async { getStartDestination() } | ||
val urls: Deferred<Map<String, String>> = async { webViewRepository.fetchFirebaseUrls() } | ||
|
||
_initialUiState.update { | ||
InitialUiState.Success( | ||
startDestination = startDestination.await(), | ||
urls = urls.await(), | ||
) | ||
} | ||
} | ||
|
||
// TODO: 자동 로그인 구현 시 변경 | ||
setStartDestination(Onboarding.ROUTE) | ||
} | ||
|
||
private fun fetchFirebaseUrls() { | ||
viewModelScope.launch { | ||
val urls = webViewRepository.fetchFirebaseUrls() | ||
if (_initialUiState.value is InitialUiState.Loading) { | ||
_initialUiState.update { InitialUiState.Success(urls = urls) } | ||
return@launch | ||
} | ||
if (_initialUiState.value is InitialUiState.Success) { | ||
_initialUiState.update { (it as InitialUiState.Success).copy(urls = urls) } | ||
} | ||
private suspend fun getStartDestination(): String { | ||
jwtManager.getAccessJwt() ?: return resetToken() | ||
|
||
val isValidToken = tokenValidator.validate().getOrNull() | ||
if (isValidToken == null) { | ||
_errorFlow.emit(Throwable("서버 연결에 이상이 있습니다.")) | ||
return resetToken() | ||
} | ||
|
||
return when (isValidToken.role) { | ||
TokenRole.USER -> Home.ROUTE | ||
TokenRole.GUEST -> SignUp.ROUTE | ||
TokenRole.ADMIN -> resetToken() | ||
} | ||
} | ||
|
||
private suspend fun resetToken(): String { | ||
jwtManager.clearAllTokens() | ||
return Onboarding.ROUTE | ||
} | ||
|
||
fun getMinVersion(currentVersion: Long) { | ||
viewModelScope.launch { | ||
val firebaseStore = FirebaseFirestore.getInstance() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.teamhy2.feature.main | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import com.benenfeldt.remote.token.AuthCallback | ||
import dagger.hilt.android.qualifiers.ApplicationContext | ||
import javax.inject.Inject | ||
|
||
class MainAuthCallback | ||
@Inject | ||
constructor( | ||
@ApplicationContext private val context: Context, | ||
) : AuthCallback { | ||
override fun onAuthRequired() { | ||
val intent = | ||
Intent(context, MainActivity::class.java).apply { | ||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK | ||
} | ||
context.startActivity(intent) | ||
Comment on lines
+15
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컴포즈에서 Intent를 쓰니까 새롭군요 ㅋㅋㅋㅋ |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
카톡으로 이야기를 나누었지만 USER, GUEST, ADMIN의 의미가 혼동이 올 수 있을꺼 같았는데 주석을 작성해두는 것은 어떨까요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GUEST
는UNREGISTERED_USER
로만 변경해도 혼동사항은 없을꺼 같기도 합니다!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GUEST는 서버에서 사용한 네이밍임으로 통일성을 위해 일단은 그냥 두겠습니다
추후 게스트로그인 등을 구현시 처리하겠습니다!