-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT/#292] 구글 로그인 구현합니다. #310
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
Changes from all commits
4b8e4b3
87d03d7
e8c32c6
196334a
0fdb143
3932c57
89ce95c
fcca072
c7fdbb0
ff0653b
ba767df
8f766ef
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,22 @@ | ||
| package com.sopt.clody.data.datastore | ||
|
|
||
| import android.content.Context | ||
| import dagger.Module | ||
| import dagger.Provides | ||
| import dagger.hilt.InstallIn | ||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||
| import dagger.hilt.components.SingletonComponent | ||
| import javax.inject.Singleton | ||
|
|
||
| @Module | ||
| @InstallIn(SingletonComponent::class) | ||
| object DataStoreModule { | ||
|
|
||
| @Provides | ||
| @Singleton | ||
| fun provideOAuthDataStore( | ||
| @ApplicationContext context: Context, | ||
| ): OAuthDataStore { | ||
| return OAuthDataStore(context) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package com.sopt.clody.data.datastore | ||
|
|
||
| import android.content.Context | ||
| import androidx.datastore.preferences.core.edit | ||
| import androidx.datastore.preferences.preferencesDataStore | ||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||
| import kotlinx.coroutines.flow.first | ||
| import javax.inject.Inject | ||
|
|
||
| class OAuthDataStore @Inject constructor(@ApplicationContext context: Context) { | ||
| private val Context.dataStore by preferencesDataStore(name = "oauth_pref") | ||
| private val dataStore = context.dataStore | ||
|
|
||
| suspend fun saveIdToken(token: String) { | ||
| dataStore.edit { it[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] = token } | ||
| } | ||
|
|
||
| suspend fun getIdToken(): String? { | ||
| return dataStore.data.first()[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] | ||
| } | ||
|
Comment on lines
+14
to
+20
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. Security concern: Consider encrypting sensitive OAuth tokens. The ID token is stored in plain text. OAuth tokens are sensitive data that should be encrypted at rest. Consider using EncryptedSharedPreferences or Android Keystore for secure token storage. For example, you could use EncryptedSharedPreferences: val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val encryptedPrefs = EncryptedSharedPreferences.create(
context,
"oauth_encrypted_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)🤖 Prompt for AI Agents |
||
|
|
||
| suspend fun savePlatform(provider: OAuthProvider) { | ||
| dataStore.edit { it[OAuthDataStoreKeys.OAUTH_PLATFORM] = provider.name } | ||
| } | ||
|
|
||
| suspend fun getPlatform(): OAuthProvider? { | ||
| return dataStore.data.first()[OAuthDataStoreKeys.OAUTH_PLATFORM]?.let { | ||
| runCatching { OAuthProvider.valueOf(it) }.getOrNull() | ||
| } | ||
| } | ||
|
|
||
| suspend fun clear() { | ||
| dataStore.edit { | ||
| it.remove(OAuthDataStoreKeys.GOOGLE_ID_TOKEN) | ||
| it.remove(OAuthDataStoreKeys.OAUTH_PLATFORM) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.sopt.clody.data.datastore | ||
|
|
||
| import androidx.datastore.preferences.core.stringPreferencesKey | ||
|
|
||
| object OAuthDataStoreKeys { | ||
| val GOOGLE_ID_TOKEN = stringPreferencesKey("google_id_token") | ||
| val OAUTH_PLATFORM = stringPreferencesKey("oauth_platform") | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.sopt.clody.data.datastore | ||
|
|
||
| enum class OAuthProvider(val apiValue: String) { | ||
| GOOGLE("google"), | ||
| KAKAO("kakao"), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.sopt.clody.data.remote.dto.request | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class GoogleSignUpRequestDto( | ||
| @SerialName("idToken") val idToken: String, | ||
| @SerialName("fcmToken") val fcmToken: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,12 @@ | ||
| package com.sopt.clody.data.remote.dto.response | ||
|
|
||
| import com.sopt.clody.data.datastore.OAuthProvider | ||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class UserInfoResponseDto( | ||
| @SerialName("email") val email: String, | ||
| @SerialName("name") val name: String, | ||
| @SerialName("platform") val platform: String, | ||
| @SerialName("platform") val platform: OAuthProvider?, | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -29,13 +29,10 @@ fun SignUpRoute( | |||||||||||
| viewModel.sideEffects.collect { effect -> | ||||||||||||
| when (effect) { | ||||||||||||
| is SignUpContract.SignUpSideEffect.NavigateToTimeReminder -> navigateToHome() | ||||||||||||
| is SignUpContract.SignUpSideEffect.ShowMessage -> { | ||||||||||||
| // TODO: Snackbar나 Dialog로 에러 메시지 처리 | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| is SignUpContract.SignUpSideEffect.NavigateToWebView -> { | ||||||||||||
| navigateToWebView(effect.url) // ✅ WebView 이동 처리 | ||||||||||||
| } | ||||||||||||
| is SignUpContract.SignUpSideEffect.ShowMessage -> {} | ||||||||||||
|
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. 🛠️ Refactor suggestion Address the empty error message handler. The Consider implementing proper error message display: -is SignUpContract.SignUpSideEffect.ShowMessage -> {}
+is SignUpContract.SignUpSideEffect.ShowMessage -> {
+ // TODO: Show toast, snackbar, or other user feedback
+ // Example: Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
@@ -83,7 +80,9 @@ fun SignUpScreen( | |||||||||||
| NickNamePage( | ||||||||||||
| nickname = state.nickname, | ||||||||||||
| onNicknameChange = { onIntent(SignUpContract.SignUpIntent.SetNickname(it)) }, | ||||||||||||
| onCompleteClick = { onIntent(SignUpContract.SignUpIntent.CompleteSignUp(context)) }, | ||||||||||||
| onCompleteClick = { | ||||||||||||
| onIntent(SignUpContract.SignUpIntent.CompleteSignUp(context)) | ||||||||||||
| }, | ||||||||||||
| onBackClick = { onIntent(SignUpContract.SignUpIntent.BackToTerms) }, | ||||||||||||
| isLoading = state.isLoading, | ||||||||||||
| isValidNickname = state.isValidNickname, | ||||||||||||
|
|
||||||||||||
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.
[p4]
preferences 기반 토큰 저장 방식이 아닌 preferences datastore 로 사용한 것 같은데, 추후에 카카오 로그인도 이것과 합치나요? 동일하게 preferences가 아닌 해당 방식을 채택한 이유가 궁금합니다!
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.
음.
SharedPreferences는 Sync I/O 기반이라 UI thread를 막을 수 있어서 구조적으로 위험할 수 잇죠.반면,
DataStore는 Coroutine 기반으로 설계되어 IO 안정성이 좋습니다.그래서 공식문서에서도 장기적으로 Datastore을 지향하고 있슴다.
카카오같은 경우는 로그인/회원가입에 loginsdk를 통해서 auth token을 발급하는 구조라 구글 로그인과는 조금 다를 수 있습니다.
구글 로그인은 요청하면 또 이메일 선택 UI가 뜨기 때문에 로컬에 캐싱했다가 회원가입 때 쏘는 구조로 했슴다이.