Skip to content

[FEAT] 유저 로그인, 회원 가입, 토큰 갱신, 자동 로그인, 로그아웃, Authorization 전역 헤더 구현#20

Merged
Sangyoon98 merged 5 commits into
devfrom
SPM-244
Oct 26, 2025
Merged

[FEAT] 유저 로그인, 회원 가입, 토큰 갱신, 자동 로그인, 로그아웃, Authorization 전역 헤더 구현#20
Sangyoon98 merged 5 commits into
devfrom
SPM-244

Conversation

@Sangyoon98
Copy link
Copy Markdown
Member

@Sangyoon98 Sangyoon98 commented Oct 24, 2025

📝 Summary

  • 유저 로그인 로직 검증
  • 회원 가입 로직 검증
  • 토큰 갱신 로직 Interceptor 구현
  • 자동 로그인 로직 구현
  • 로그아웃 구현
  • Authorization 전역 헤더 구현

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 자동 토큰 갱신 기능 추가로 세션 유지 개선
    • 대시보드에 로그아웃 버튼 추가
    • 인증 상태에 따른 자동 화면 분기
  • 개선 사항

    • 인증 시스템 아키텍처 리팩토링
    • 토큰 안전한 저장소 적용
    • 인증 상태 관리 최적화

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 24, 2025

Warning

Rate limit exceeded

@Sangyoon98 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 85b24b5 and 3f7780c.

📒 Files selected for processing (6)
  • app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/usecase/CheckLoginStateUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt (1 hunks)

워크스루

인증 시스템을 feature.auth에서 feature.user로 리팩터링하고 암호화된 토큰 저장소를 도입했습니다. 네트워크 레이어에 자동 토큰 갱신 기능을 추가하고, 암호화된 인증 저장소(AuthPreferences), 토큰 인터셉터, 토큰 갱신 서비스, 인증 담당자를 새로 추가했습니다. 네비게이션을 로그인 상태 기반으로 업데이트하고 새로운 ViewModel과 UseCase를 생성했습니다.

변경사항

코호트 / 파일 요약
인증 모듈 리팩터링
app/src/main/java/com/sampoom/android/feature/user/domain/model/User.kt, app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt, app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt
사용자 도메인 모델과 인증 저장소를 feature.auth에서 feature.user로 이동하고, refreshToken() 메서드와 개선된 매개변수 구조 추가
원격 API 및 DTO 재구성
app/src/main/java/com/sampoom/android/feature/user/data/remote/api/AuthApi.kt, app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/*
새로운 인증 API 인터페이스로 로그인, 로그아웃, 토큰 갱신, 회원가입, 검증, 업데이트 엔드포인트 추가. RefreshRequestDto, RefreshResponseDto, UpdateRequestDto, UpdateResponseDto, VerifyRequestDto, VerifyResponseDto 신규 DTO 추가. DTO 패키지를 feature.user로 이동
UI 레이어 마이그레이션
app/src/main/java/com/sampoom/android/feature/user/ui/LoginScreen.kt, app/src/main/java/com/sampoom/android/feature/user/ui/LoginViewModel.kt, app/src/main/java/com/sampoom/android/feature/user/ui/SignUpScreen.kt, app/src/main/java/com/sampoom/android/feature/user/ui/SignUpViewModel.kt, app/src/main/java/com/sampoom/android/feature/user/ui/LoginUiState.kt, app/src/main/java/com/sampoom/android/feature/user/ui/LoginUiEvent.kt, app/src/main/java/com/sampoom/android/feature/user/ui/SignUpUiState.kt, app/src/main/java/com/sampoom/android/feature/user/ui/SignUpUiEvent.kt
모든 UI 컴포넌트를 feature.auth.ui에서 feature.user.ui로 이동하고, 매개변수 구조 업데이트
인증 ViewModel 및 UseCase
app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt, app/src/main/java/com/sampoom/android/feature/user/domain/usecase/CheckLoginStateUseCase.kt, app/src/main/java/com/sampoom/android/feature/user/domain/usecase/SignOutUseCase.kt, app/src/main/java/com/sampoom/android/feature/user/domain/usecase/ClearTokensUseCase.kt
로그인 상태 관리, 로그아웃, 토큰 정리 기능을 제공하는 새로운 AuthViewModel 및 UseCase 클래스 추가
네트워크 암호화 및 토큰 관리
app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt, app/src/main/java/com/sampoom/android/core/datastore/CryptoManager.kt, app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt, app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt, app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt, app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt
암호화된 토큰 저장소, 자동 토큰 인젝션, 토큰 갱신 서비스, 401 응답 처리 인증자 추가. NetworkModule에 새로운 프로바이더 추가
네비게이션 업데이트
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt
로그인 상태 기반 시작 화면 결정, 로그아웃 콜백을 포함한 DashboardScreen 서명 변경, AuthViewModel 통합
DI 및 매퍼
app/src/main/java/com/sampoom/android/feature/user/di/AuthModules.kt, app/src/main/java/com/sampoom/android/feature/user/data/mapper/AuthMappers.kt, app/src/main/java/com/sampoom/android/feature/user/domain/AuthValidator.kt
패키지 경로를 feature.auth에서 feature.user로 변경하고, 매퍼 재구성
제거된 기존 인증 모듈
app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt (이전), app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt (이전), app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/SignUpRequestDto.kt (이전), app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt (이전), app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (이전), app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt (이전), app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (이전)
기존 feature.auth 모듈에서 인증 관련 파일 제거
버전 업데이트
app/build.gradle.kts
versionName을 "1.0"에서 "1.0.0"으로 업데이트

시퀀스 다이어그램

sequenceDiagram
    participant User
    participant LoginScreen
    participant AuthViewModel
    participant AuthRepository
    participant TokenRefreshService
    participant AuthPreferences
    participant NetworkModule

    User->>LoginScreen: 로그인 시도 (이메일, 비밀번호)
    LoginScreen->>AuthRepository: signIn(email, password)
    AuthRepository->>NetworkModule: API 호출 (login)
    NetworkModule-->>AuthRepository: LoginResponseDto 수신
    AuthRepository->>AuthPreferences: saveUser(user)
    AuthPreferences-->>AuthRepository: 저장 완료
    AuthRepository-->>LoginScreen: User 반환
    LoginScreen->>AuthViewModel: updateLoginState()
    AuthViewModel->>AuthViewModel: isLoggedIn = true
    AuthViewModel-->>LoginScreen: 상태 업데이트
    LoginScreen->>User: 홈 화면으로 네비게이션

    rect rgb(200, 220, 255)
    Note over TokenRefreshService,AuthPreferences: 자동 토큰 갱신 흐름 (401 응답 시)
    NetworkModule->>TokenAuthenticator: 401 응답 감지
    TokenAuthenticator->>TokenRefreshService: refreshToken()
    TokenRefreshService->>AuthPreferences: 저장된 refreshToken 조회
    TokenRefreshService->>NetworkModule: API 호출 (refresh)
    NetworkModule-->>TokenRefreshService: 새로운 토큰 수신
    TokenRefreshService->>AuthPreferences: 새로운 토큰 저장
    TokenRefreshService-->>TokenAuthenticator: Result<User> 반환
    TokenAuthenticator->>NetworkModule: 재시도 요청 (새 토큰 포함)
    end
Loading

코드 리뷰 예상 소요 시간

🎯 4 (복잡함) | ⏱️ ~60분

분석 근거:

  • 범위: 30개 이상의 파일이 변경되었으며, 구조적 리팩터링이 광범위함
  • 이질성: 모듈 이동, 새로운 암호화 인프라, 네트워크 계층 개선, UI 마이그레이션, UseCase 추가 등 다양한 변경
  • 논리 밀도: TokenAuthenticator, CryptoManager, TokenRefreshService, AuthViewModel의 새로운 로직은 상당한 검토 필요
  • 복잡성: 토큰 갱신 흐름, 암호화/복호화 로직, 상태 관리, 종속성 주입이 정확하게 통합되어야 함

관련 PR

  • PR [FEAT] 로그인 기능 구현 #15: 동일한 인증/네비게이션 코드베이스 수정 (AppNavHost, LoginScreen, AuthPreferences, AuthApi/매퍼 및 AuthRepository 구현) - 같은 인증/로그인 기능 대상
  • PR Spm 130 #17: AppNavHost.kt 네비게이션 연결 수정 (경로 상수 및 라우트 연결) - 같은 네비게이션 코드에서 잠재적 충돌 가능성

제안된 라벨

ready-to-merge

제안된 리뷰어

  • CHOOSLA
  • yangjiseonn
  • Lee-Jong-Jin
  • vivivim

🐰 토큰 새것 되고 암호로 안전하게,
사용자 영역 이제 아주 넓어지고,
자동 갱신 흐름 우아하고 예쁘고,
네비게이션 깔끔해지니 기쁘네!
리팩터링 마쳤으니 축하할 시간! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.96% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed PR 제목 "[FEAT] 유저 로그인, 회원 가입, 토큰 갱신, 자동 로그인, 로그아웃, Authorization 전역 헤더 구현"은 변경사항의 모든 주요 기능을 정확하게 반영하고 있습니다. 제목에서 언급한 항목들이 raw_summary에서 확인됩니다: TokenInterceptor와 TokenAuthenticator를 통한 Authorization 헤더 설정, TokenRefreshService를 통한 토큰 갱신 로직, AuthViewModel과 CheckLoginStateUseCase를 통한 자동 로그인, SignOutUseCase와 DashboardScreen 수정을 통한 로그아웃, 그리고 LoginUseCase와 SignUpUseCase를 통한 로그인 및 회원가입 기능이 모두 구현되어 있습니다. 제목은 명확하고 구체적이며, 개발자의 주요 관심사인 인증 관련 전체 플로우를 적절하게 요약하고 있습니다.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@taemin3 taemin3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/com/sampoom/android/feature/user/domain/AuthValidator.kt (1)

7-18: 이메일 검증 정규식 패턴 개선 필요

현재 정규식 패턴에 다음 문제가 있습니다:

  • 도메인 부분이 소문자만 허용하여 유효한 이메일을 거부할 수 있음
  • \\.+ 패턴이 연속된 점을 허용하여 user@domain..com 같은 무효한 이메일을 허용
  • TLD 구조를 제대로 검증하지 않음

더 견고한 패턴으로 개선하거나 Android의 Patterns.EMAIL_ADDRESS를 사용하는 것을 권장합니다:

-        val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
-        if (!email.matches(emailPattern)) {
+        if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
             return ValidationResult.Error(R.string.validation_email_invalid)
         }
🧹 Nitpick comments (15)
app/src/main/java/com/sampoom/android/feature/user/domain/AuthValidator.kt (1)

21-43: 특수 문자 검증 로직 명확화 권장

Line 36의 특수 문자 검증이 !it.isLetterOrDigit()를 사용하여 공백, 탭 등도 특수 문자로 인정합니다. 의도한 동작이라면 문제없지만, 일반적으로 특수 문자는 !@#$% 등의 기호만 포함하도록 명시하는 것이 보안상 더 명확합니다.

명시적인 특수 문자 집합을 사용하는 것을 고려해보세요:

         val hasLetter = password.any { it.isLetter() }
         val hasDigit = password.any { it.isDigit() }
-        val hasSpecial = password.any { !it.isLetterOrDigit() }
+        val hasSpecial = password.any { it in "!@#$%^&*()_+-=[]{}|;:',.<>?/" }
app/src/main/java/com/sampoom/android/feature/user/ui/LoginViewModel.kt (1)

53-65: 로그인 이메일 형식 검증 추가 권장

현재 로그인 화면에서는 이메일이 비어있지 않은지만 확인하고 형식 검증을 하지 않습니다. 회원가입과 달리 AuthValidator.validateEmail()을 사용하지 않아, 사용자가 잘못된 이메일 형식을 입력해도 서버 요청 후에야 오류를 알 수 있습니다.

이메일 형식 검증을 추가하여 UX를 개선하는 것을 고려해보세요:

     private fun validateEmail() {
-        val result = AuthValidator.validateNotEmpty(_uiState.value.email, emailLabel)
+        val result = AuthValidator.validateEmail(_uiState.value.email)
         _uiState.value = _uiState.value.copy(
             emailError = result.toErrorMessage()
         )
     }
app/src/main/java/com/sampoom/android/feature/user/ui/SignUpScreen.kt (1)

88-88: 주석 처리된 코드 제거 권장

사용하지 않는 주석 처리된 코드는 제거하는 것이 코드베이스를 깔끔하게 유지하는 데 도움이 됩니다.

-//        snackbarHost = { CommonSnackBarHost(snackBarHostState) }
app/src/main/java/com/sampoom/android/feature/user/data/mapper/AuthMappers.kt (1)

6-6: named parameter 사용을 권장합니다.

현재 positional parameter를 사용한 매핑은 User 데이터 클래스의 필드 순서가 변경될 경우 런타임 오류나 잘못된 매핑을 유발할 수 있습니다.

다음과 같이 named parameter를 사용하도록 리팩토링하세요:

-fun LoginResponseDto.toModel(): User = User(userId, userName, role, accessToken, refreshToken, expiresIn)
+fun LoginResponseDto.toModel(): User = User(
+    userId = userId,
+    userName = userName,
+    role = role,
+    accessToken = accessToken,
+    refreshToken = refreshToken,
+    expiresIn = expiresIn
+)
app/src/main/java/com/sampoom/android/feature/user/domain/model/User.kt (1)

3-10: 보안 및 관심사 분리를 고려해보세요.

현재 User 도메인 모델에 accessToken과 refreshToken이 포함되어 있습니다. 비즈니스 데이터(userId, userName, role)와 인증 토큰을 함께 보관하는 것은 다음과 같은 우려사항이 있습니다:

  • 토큰은 민감한 데이터로 로깅이나 직렬화 시 주의가 필요합니다
  • 도메인 모델이 인증 관련 책임까지 가지게 됩니다

인증 플로우에서만 사용되는 것이라면 현재 구조도 충분하지만, 향후 확장성을 고려하여 토큰을 별도 데이터 클래스로 분리하는 것을 검토해보세요.

app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (3)

84-86: popUpTo(0) 대신 명시적 대상 사용.

popUpTo(0)은 구현 의존적이며 깨질 수 있습니다. 그래프 id/시작 목적지를 명시하세요.

- navController.navigate(ROUTE_LOGIN) {
-     popUpTo(0) { inclusive = true }
- }
+ navController.navigate(ROUTE_LOGIN) {
+     popUpTo(navController.graph.startDestinationId) { inclusive = true }
+     launchSingleTop = true
+ }

동일 패턴을 Dashboard 쪽 네비게이션에도 적용하세요.

수정 후 "로그아웃 > 백스택 완전 초기화" 동작을 실제 기기에서 한 번 확인 부탁드립니다.

Also applies to: 181-183


270-284: DashboardScreen: 네비 콜백 제거 및 padding 중복 정리.

  • onClick 콜백으로 네비를 직접 트리거하는 대신 logoutEvent 한 곳에서만 처리하도록 제거하세요.
  • Column 에 padding 을 주었는데 Text 에 동일 padding 을 중복 적용했습니다.
-private fun DashboardScreen(
-    paddingValues: PaddingValues,
-    onClick: () -> Unit
-) {
+private fun DashboardScreen(
+    paddingValues: PaddingValues
+) {
     val authViewModel: AuthViewModel = hiltViewModel()
-    Column(Modifier.padding(paddingValues)) {
-        Text("대시보드 화면", modifier = Modifier.padding(paddingValues))
+    Column(Modifier.padding(paddingValues)) {
+        Text("대시보드 화면")
 
-        Button(onClick = {
-            authViewModel.signOut()
-            onClick()
-        }) {
+        Button(onClick = { authViewModel.signOut() }) {
             Text("로그아웃")
         }
     }
 }

126-129: agencyId 하드코딩 제거 TODO 처리.

현재 1로 고정되어 있습니다. 실제 사용자 정보에서 가져오도록 수정이 필요합니다. 런타임 혼선을 피하려면 주석 대신 NotImplementedError() 등으로 임시 가드하거나, 안전 기본값/게이트를 두세요.

원하시면 사용자 컨텍스트에서 agencyId 를 주입하는 패턴(예: Session/AccountManager) 제안드리겠습니다.

Also applies to: 200-202

app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt (2)

20-27: Retrofit 인스턴스를 매번 생성하지 말고 DI로 싱글톤 주입.

refreshToken 호출마다 Retrofit 을 만들면 불필요한 오버헤드와 설정 분산이 생깁니다. 인터셉터 없는 전용 OkHttp/Retrofit 을 DI 모듈에서 싱글톤으로 제공하고, 여기서는 AuthApi 만 주입받아 호출하세요. 또한 baseUrl 하드코딩 대신 BuildConfig 등 환경설정을 사용하세요.

예시(개념 스케치):

  • NetworkModule 에 @Named("refresh") OkHttpClient(인터셉터 없음) + @Named("refresh") Retrofit + @Named("refresh") AuthApi 제공
  • TokenRefreshService 생성자: TokenRefreshService(authPreferences: AuthPreferences, @Named("refresh") authApi: AuthApi)
-        // 새로운 Retrofit 인스턴스 생성 (인터셉터 없이)
-        val retrofit = Retrofit.Builder()
-            .baseUrl("https://sampoom.store/api/")
-            .addConverterFactory(GsonConverterFactory.create())
-            .build()
-
-        val authApi = retrofit.create(AuthApi::class.java)
+        // DI로 주입받은 전용 AuthApi 사용 (인터셉터 없음, 싱글톤)

29-40: User 업데이트 범위와 에러 구분 개선 제안.

  • 서버가 사용자 속성(권한/이름 등)을 함께 갱신할 수 있다면, refresh 응답 기반으로 필요한 필드도 업데이트하는게 안전합니다. 현재는 토큰/만료만 갱신.
  • runCatching 으로 예외를 전부 Result 로 감싸는 대신, 토큰 없음/사용자 없음/네트워크 오류를 구분해 상위에서 UX 를 달리 처리할 수 있게 하면 좋습니다(예: sealed error).
app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt (1)

15-19: 토큰 갱신 책임 단일화.

인터셉터에서 TokenRefreshService 를 직접 사용하고, 레포에도 refreshToken() 이 있습니다. 두 경로가 분기되면 중복/불일치가 생깁니다. 한 곳(AuthRepository or Service)으로 책임을 모으고, 다른 곳은 이를 위임만 하도록 정리하세요.

app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt (1)

32-36: signOut 실패 처리 및 이벤트 방출 안정화

예외 미처리로 크래시/침묵 실패 위험이 있습니다. 이벤트는 tryEmit로 비차단 방출 권장.

-    fun signOut() = viewModelScope.launch {
-        signOutUseCase()
-        _isLoggedIn.value = false
-        _logoutEvent.emit(Unit)
-    }
+    fun signOut() = viewModelScope.launch {
+        runCatching { signOutUseCase() }
+            .onFailure { /* TODO: 오류 상태 노출(스낵바/로깅) */ }
+        _isLoggedIn.value = false
+        _logoutEvent.tryEmit(Unit)
+    }
app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt (2)

51-65: runBlocking 기반 동기 getter 사용 최소화 및 IO 디스패처로 오프로딩

refreshToken 내 getRefreshToken/getStoredUser는 블로킹입니다. 우선 IO로 오프로딩하고, 추후 suspend getter로 전환 권장.

-            val refreshToken = preferences.getRefreshToken() ?: throw Exception("No refresh token available")
+            val refreshToken = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
+                preferences.getRefreshToken()
+            } ?: throw Exception("No refresh token available")
@@
-            val existingUser = preferences.getStoredUser() ?: throw Exception("No user information available")
+            val existingUser = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
+                preferences.getStoredUser()
+            } ?: throw Exception("No user information available")

추가로, 도메인 친화적 오류 타입(예: NoRefreshTokenError)로 감싸면 상위 처리 단순화됩니다.


71-71: 만료 고려 없는 isSignedIn 판단

토큰 존재만 확인하면 만료 토큰으로도 로그인 상태로 인식될 수 있습니다. 만료 검사 포함 권장.

-    override fun isSignedIn(): Boolean = preferences.hasToken()
+    override fun isSignedIn(): Boolean =
+        preferences.hasToken() && !preferences.isTokenExpired()
app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt (1)

87-88: 만료 무시한 hasToken

만료된 토큰이 있어도 true 반환합니다. 만료 검사를 포함하세요.

-    fun hasToken(): Boolean = !getAccessToken().isNullOrEmpty() && !getRefreshToken().isNullOrEmpty()
+    fun hasToken(): Boolean =
+        !getAccessToken().isNullOrEmpty() &&
+        !getRefreshToken().isNullOrEmpty() &&
+        !isTokenExpired()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27f70dc and 53efebb.

📒 Files selected for processing (43)
  • app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (6 hunks)
  • app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/TokenRefreshInterceptor.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/SignUpRequestDto.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/mapper/AuthMappers.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/api/AuthApi.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/LoginRequestDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/LoginResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/RefreshRequestDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/RefreshResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/SignUpRequestDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/SignUpResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateRequestDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/VerifyRequestDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/VerifyResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/di/AuthModules.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/AuthValidator.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/model/User.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/usecase/CheckLoginStateUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/usecase/LoginUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/usecase/SignOutUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/usecase/SignUpUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/LoginScreen.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/LoginUiEvent.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/LoginUiState.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/LoginViewModel.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/SignUpScreen.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/SignUpUiEvent.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/SignUpUiState.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/SignUpViewModel.kt (2 hunks)
💤 Files with no reviewable changes (7)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt
  • app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt
  • app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/SignUpRequestDto.kt
  • app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt
  • app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt (1)
app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (1)
  • signUp (5-18)
app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt (2)
app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt (1)
  • signIn (15-15)
app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (1)
  • api (12-47)
🔇 Additional comments (31)
app/src/main/java/com/sampoom/android/feature/user/ui/SignUpUiState.kt (1)

1-1: 패키지 이동이 적절하게 수행되었습니다.

인증 관련 기능을 feature.user 패키지로 이동하는 것은 PR의 목적과 일치하며, 코드 구조 개선에 도움이 됩니다.

app/src/main/java/com/sampoom/android/feature/user/ui/LoginUiState.kt (1)

1-1: 패키지 이동이 완벽하게 적용되었습니다.

검증 결과:

  • LoginUiState.kt가 새 패키지(com.sampoom.android.feature.user.ui)로 올바르게 이동됨
  • ✅ 구 패키지(com.sampoom.android.feature.auth.ui) 참조 없음
  • LoginViewModel.kt가 같은 패키지에 위치하여 import 정상 처리됨
  • ✅ 모든 의존성이 올바르게 업데이트됨

리팩토링이 완료되었고 빌드 문제가 없습니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/VerifyRequestDto.kt (1)

3-6: LGTM!

DTO 구조가 명확하고 올바르게 정의되어 있습니다.

app/src/main/java/com/sampoom/android/feature/user/domain/AuthValidator.kt (2)

1-1: LGTM!

패키지 마이그레이션이 PR 목표와 일치합니다.


46-72: LGTM!

비밀번호 확인 검증과 일반 필드 검증 로직이 올바르게 구현되어 있으며, ValidationResult sealed class 구조가 깔끔합니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/VerifyResponseDto.kt (1)

3-8: LGTM!

응답 DTO가 명확하게 정의되어 있습니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/SignUpRequestDto.kt (1)

3-10: LGTM!

회원가입에 필요한 모든 필드가 적절하게 정의되어 있습니다.

app/src/main/java/com/sampoom/android/feature/user/ui/LoginViewModel.kt (2)

1-1: LGTM!

패키지 마이그레이션과 import 업데이트가 일관성 있게 적용되었습니다.

Also applies to: 7-9


75-92: LGTM!

로그인 제출 로직이 올바르게 구현되어 있습니다. 코루틴 사용, 에러 핸들링, 상태 관리가 적절합니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/SignUpResponseDto.kt (1)

1-7: LGTM!

패키지 마이그레이션이 적절하게 수행되었습니다.

app/src/main/java/com/sampoom/android/feature/user/ui/LoginUiEvent.kt (1)

1-7: LGTM!

패키지 마이그레이션이 완료되었으며, 이벤트 구조가 적절합니다.

app/src/main/java/com/sampoom/android/feature/user/ui/SignUpScreen.kt (3)

1-1: LGTM!

패키지 마이그레이션이 올바르게 적용되었습니다.


3-42: LGTM!

사용하지 않는 import 제거가 적절하게 수행되었습니다.


44-210: LGTM!

회원가입 화면 구현이 올바르게 되어 있으며, 폼 검증과 에러 처리가 적절합니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/RefreshResponseDto.kt (1)

3-7: LGTM! 토큰 갱신 DTO 구조가 적절합니다.

토큰 갱신을 위한 DTO 구조가 명확하며, expiresInLong 타입으로 정의한 것이 타임스탬프/만료 시간 처리에 적합합니다.

app/src/main/java/com/sampoom/android/feature/user/ui/SignUpUiEvent.kt (1)

1-1: 패키지 이동이 올바르게 적용되었습니다.

feature.auth.ui에서 feature.user.ui로의 패키지 이동이 PR의 리팩토링 목표와 일치합니다.

app/src/main/java/com/sampoom/android/feature/user/di/AuthModules.kt (1)

1-5: DI 모듈의 패키지 및 임포트 업데이트가 정확합니다.

패키지 선언과 임포트 경로가 새로운 feature.user 구조에 맞게 올바르게 업데이트되었습니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/LoginRequestDto.kt (1)

1-1: 패키지 이동이 올바르게 적용되었습니다.

DTO의 패키지가 feature.user 구조로 적절하게 이동되었습니다.

app/src/main/java/com/sampoom/android/feature/user/ui/LoginScreen.kt (1)

1-1: 패키지 이동 및 불필요한 임포트 제거가 잘 적용되었습니다.

feature.user.ui로의 패키지 이동과 함께 사용하지 않는 임포트를 제거하여 코드가 깔끔해졌습니다.

app/src/main/java/com/sampoom/android/feature/user/domain/usecase/SignOutUseCase.kt (1)

6-10: LGTM! UseCase 구현이 표준 패턴을 잘 따르고 있습니다.

로그아웃 로직을 캡슐화한 UseCase 구현이 명확하며, suspend 함수와 operator를 적절히 활용하고 있습니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/LoginResponseDto.kt (1)

9-9: expiresIn 타입을 Long으로 변경한 것이 적절합니다.

Int에서 Long으로 변경하여 타임스탬프 값의 오버플로우를 방지하고, RefreshResponseDto와 일관성을 유지하고 있습니다. 만료 시간 처리에 더 안전한 선택입니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateResponseDto.kt (1)

3-10: LGTM! 사용자 정보 업데이트 DTO가 명확하게 정의되었습니다.

사용자 프로필 정보를 담는 DTO 구조가 깔끔하고 적절하게 정의되어 있습니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/RefreshRequestDto.kt (1)

3-5: LGTM!

토큰 갱신 요청을 위한 간단하고 명확한 DTO 구조입니다.

app/src/main/java/com/sampoom/android/feature/user/domain/usecase/CheckLoginStateUseCase.kt (1)

6-10: LGTM!

로그인 상태 확인을 위한 간결하고 적절한 use case 구현입니다.

app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateRequestDto.kt (1)

3-8: LGTM!

유저 정보 업데이트를 위한 명확한 DTO 구조입니다.

app/src/main/java/com/sampoom/android/feature/user/domain/usecase/LoginUseCase.kt (1)

1-4: LGTM!

auth에서 user 패키지로의 마이그레이션이 올바르게 적용되었습니다.

app/src/main/java/com/sampoom/android/feature/user/domain/usecase/SignUpUseCase.kt (1)

10-24: LGTM!

파라미터 순서 변경(email/password 우선)과 이름 변경(name → userName)이 일관되게 적용되었으며, named parameter를 사용하여 안전하게 repository를 호출하고 있습니다.

app/src/main/java/com/sampoom/android/feature/user/ui/SignUpViewModel.kt (1)

139-146: LGTM!

SignUpUseCase의 변경된 파라미터 시그니처와 일치하도록 올바르게 업데이트되었습니다. Named parameter를 사용하여 안전하게 호출하고 있으며, userName = s.name 매핑도 적절합니다.

app/src/main/java/com/sampoom/android/feature/user/domain/repository/AuthRepository.kt (2)

19-19: isSignedIn 동기 여부 확인.

동기 Boolean 반환은 DataStore 등 I/O 를 동반하면 ANR/블로킹 리스크가 있습니다. 메모리 캐시 기반이 아니라면 suspend 또는 Flow 로 노출하는 방식을 고려하세요.


6-13: 네임드 아규먼트 안전성 확인 완료: 추가 조치 불필요

전체 코드베이스 검색 결과, signUp() 호출부는 모두 네임드 아규먼트를 사용하고 있습니다.

  • SignUpUseCase.kt (라인 17-23): repository.signUp(email = email, password = password, workspace = workspace, branch = branch, userName = userName, position = position) 형태로 호출
  • 위치 기반 인자(positional arguments)를 사용하는 호출부 없음

Kotlin에서 네임드 아규먼트는 파라미터 순서와 무관하므로, 현재 상태에서는 파라미터 순서 변경이 버그를 야기하지 않습니다.

app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt (1)

36-44: 검증 결과: 토큰/만료 필드가 모두 포함되어 있으며 코드는 정상입니다

검증을 통해 확인한 결과, LoginResponseDto.toModel() 매퍼가 accessToken, refreshToken, expiresIn을 모두 올바르게 User 도메인 모델로 매핑하고 있습니다. saveUser(user)는 이 모든 필드에 접근하며, 데이터 흐름에 문제가 없습니다.

Comment thread app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/network/TokenRefreshInterceptor.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (11)
app/build.gradle.kts (1)

36-36: 버전 범프가 PR의 변경 범위와 정렬되는지 확인해주세요.

현재 versionName을 "1.0"에서 "1.0.0"으로 변경했습니다. 이는 기술적으로 패치 버전 추가일 뿐 버전 번호 증가가 아닙니다. 이 PR에서 구현한 사용자 인증 기능들(로그인, 회원가입, 토큰 갱신, 자동 로그인, 로그아웃, Authorization 헤더)은 상당한 새로운 기능이므로, 의미 있는 버전(예: "1.1.0" 또는 더 높은 버전)으로 범프하는 것을 권장합니다.

app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt (1)

37-47: 에러 처리 추가를 권장합니다.

signOut()handleTokenExpired() 함수에서 유즈케이스 호출 시 예외가 발생하면 _isLoggedIn.value = false_logoutEvent.emit()이 실행되지 않을 수 있습니다. 보안상 토큰 삭제 실패 시에도 사용자를 로그아웃 상태로 표시해야 합니다.

다음과 같이 try-finally 블록으로 감싸는 것을 권장합니다:

 fun signOut() = viewModelScope.launch {
-    signOutUseCase()
-    _isLoggedIn.value = false
-    _logoutEvent.emit(Unit)
+    try {
+        signOutUseCase()
+    } finally {
+        _isLoggedIn.value = false
+        _logoutEvent.emit(Unit)
+    }
 }

 fun handleTokenExpired() = viewModelScope.launch {
-    clearTokensUseCase()
-    _isLoggedIn.value = false
-    _logoutEvent.emit(Unit)
+    try {
+        clearTokensUseCase()
+    } finally {
+        _isLoggedIn.value = false
+        _logoutEvent.emit(Unit)
+    }
 }
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (1)

17-17: 사용하지 않는 import 제거.

LaunchedEffect가 import되었지만 파일 내에서 사용되지 않습니다.

-import androidx.compose.runtime.LaunchedEffect
app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt (1)

17-22: 401 재시도 루프 방지 로직 보강 제안

X-Retry-Count로 1회 제한은 좋습니다. 추가로 priorResponse 체인 기반 방어를 넣으면 프록시/리다이렉트 경유 시 루프 위험을 더 낮출 수 있습니다.

다음과 같이 보완을 제안합니다:

 override fun authenticate(route: Route?, response: Response): Request? {
+        // 과도한 재도전 방지 (priorResponse 체인 검사)
+        fun responseCount(r: Response?): Int {
+            var count = 0
+            var cur = r
+            while (cur?.priorResponse != null) {
+                count++
+                cur = cur.priorResponse
+                if (count >= 1) break // 1회만 허용
+            }
+            return count
+        }
         // 이미 재시도된 요청인지 확인
         if (response.request.header("X-Retry-Count") != null) {
             return null // 재시도 제한
         }
+        if (responseCount(response) >= 1) return null
app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (2)

47-59: 인터셉터 순서 조정: 로깅을 마지막에 배치

현재 로깅이 토큰 주입 이전에 실행되어 최종 요청/재시도 요청의 상태가 로그에 반영되지 않을 수 있습니다. 민감 헤더는 이미 비식별화하므로 로깅을 마지막으로 이동하는 편이 관측에 유리합니다.

-            .addInterceptor(
-                HttpLoggingInterceptor().apply {
-                    level = if (BuildConfig.DEBUG)
-                        HttpLoggingInterceptor.Level.BODY
-                    else
-                        HttpLoggingInterceptor.Level.NONE
-                    redactHeader("Authorization") // 토큰 비식별화
-                    redactHeader("Cookie") // 쿠키 비식별화
-                }
-            )
-            .addInterceptor(tokenInterceptor) // 토큰 자동 삽입
+            .addInterceptor(tokenInterceptor) // 토큰 자동 삽입
+            .addInterceptor(
+                HttpLoggingInterceptor().apply {
+                    level = if (BuildConfig.DEBUG)
+                        HttpLoggingInterceptor.Level.BODY
+                    else
+                        HttpLoggingInterceptor.Level.NONE
+                    redactHeader("Authorization")
+                    redactHeader("Cookie")
+                }
+            )
             .authenticator(tokenAuthenticator) // 토큰 갱신 (Interceptor 대신)

69-69: 하드코딩 baseUrl 외부화 제안

환경별 전환(dev/stage/prod)을 위해 BuildConfig 또는 DI 설정으로 baseUrl 외부화를 권장합니다.

app/src/main/java/com/sampoom/android/core/datastore/CryptoManager.kt (3)

44-50: Base64 플래그 NO_WRAP 사용 권장 (개행 방지/일관성)

Preferences에 저장 시 DEFAULT는 개행을 삽입할 수 있습니다. NO_WRAP으로 저장/복호화 일관성을 유지하세요.

-        return Base64.encodeToString(iv + encrypted, Base64.DEFAULT)
+        return Base64.encodeToString(iv + encrypted, Base64.NO_WRAP)

추가로 decrypt에서도 동일 플래그로 맞춰주세요(아래 코멘트 참조).


52-61: 복호화 입력 검증 + Base64 플래그 정합성

최소 길이 검증 없이 IV(12바이트)를 슬라이싱합니다. 잘못된 입력 시 예외가 발생합니다.

-        val encrypted = Base64.decode(encryptedText, Base64.DEFAULT)
+        val encrypted = Base64.decode(encryptedText, Base64.NO_WRAP)
+        require(encrypted.size >= 12) { "Invalid ciphertext" }
         val iv = encrypted.sliceArray(0..11)
         val ciphertext = encrypted.sliceArray(12 until encrypted.size)

27-41: 키 파라미터 강화(옵션): 256비트, StrongBox 우선

가능한 환경에서 256비트 키와 StrongBox 백엔드를 우선 사용하면 보안성이 향상됩니다.

             val keyGenParameterSpec = KeyGenParameterSpec.Builder(
                 keyAlias,
                 KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
             )
                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                 .setUserAuthenticationRequired(false)
                 .setRandomizedEncryptionRequired(true)
+                .setKeySize(256)
                 .build()
             keyGenerator.init(keyGenParameterSpec)
             keyGenerator.generateKey()

StrongBox 사용은 기기별 가용성 체크 후 .setIsStrongBoxBacked(true) 적용을 고려하세요.

app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt (2)

56-84: 복호화 실패 예외 삼킴 — 진단 로그 추가 권장(민감정보 비노출)

예외를 모두 null로 삼키면 원인 분석이 어려워집니다. 토큰/사용자 값은 로그에 출력하지 말고 에러 타입만 기록하세요.

-            } catch (e: Exception) {
-                return null
-            }
+            } catch (e: Exception) {
+                android.util.Log.w("AuthPreferences", "Failed to decode stored user: ${e::class.simpleName}")
+                return null
+            }

86-103: 토큰 복호화 실패 예외 삼킴 — 최소한의 워닝 로그 권장

동일 사유로 간단한 경고 로그를 남겨 추적성을 확보하세요.

-        return try {
-            cryptoManager.decrypt(encrypted)
-        } catch (e: Exception) {
-            null
-        }
+        return try {
+            cryptoManager.decrypt(encrypted)
+        } catch (e: Exception) {
+            android.util.Log.w("AuthPreferences", "Failed to decrypt access token: ${e::class.simpleName}")
+            null
+        }
-        return try {
-            cryptoManager.decrypt(encrypted)
-        } catch (e: Exception) {
-            null
-        }
+        return try {
+            cryptoManager.decrypt(encrypted)
+        } catch (e: Exception) {
+            android.util.Log.w("AuthPreferences", "Failed to decrypt refresh token: ${e::class.simpleName}")
+            null
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53efebb and 85b24b5.

📒 Files selected for processing (11)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (6 hunks)
  • app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/datastore/CryptoManager.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/api/AuthApi.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/domain/usecase/ClearTokensUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt
  • app/src/main/java/com/sampoom/android/feature/user/data/repository/AuthRepositoryImpl.kt
  • app/src/main/java/com/sampoom/android/feature/user/data/remote/api/AuthApi.kt
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt

[warning] 80-80: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 90-90: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 99-99: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (3)
app/src/main/java/com/sampoom/android/feature/user/domain/usecase/ClearTokensUseCase.kt (1)

1-10: LGTM!

토큰 삭제 유즈케이스가 올바르게 구현되었습니다. 의존성 주입과 suspend 함수 사용이 적절하며, 클린 아키텍처 패턴을 잘 따르고 있습니다.

app/src/main/java/com/sampoom/android/feature/user/ui/AuthViewModel.kt (1)

18-31: LGTM!

ViewModel 구조가 잘 설계되었으며, 이전 리뷰에서 지적된 MutableSharedFlow 버퍼 문제가 extraBufferCapacity = 1로 적절히 해결되었습니다. 상태 관리와 이벤트 처리가 올바르게 구현되었습니다.

app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt (1)

23-27: 무한 루프 우려는 해결됨 - TokenRefreshService가 별도 Retrofit 인스턴스 사용

검증 결과, TokenRefreshService가 이미 별도의 Retrofit 인스턴스를 생성하고 있어 원래 우려했던 무한 재귀 호출 문제는 발생하지 않습니다. 리프레시 API 요청이 TokenAuthenticator/TokenInterceptor를 거치지 않으므로 안전합니다.

다만 TokenAuthenticator와 TokenInterceptor 모두에서 runBlocking 사용 시 OkHttp 스레드 풀의 데드락 위험이 존재합니다. 특히 TokenInterceptor의 authPreferences.getAccessToken() 호출은 I/O 작업입니다. 향후 코루틴 기반으로 리팩토링하거나, 스레드 풀 크기 설정을 검토하시기 바랍니다.

Comment thread app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt
Comment thread app/src/main/java/com/sampoom/android/core/datastore/AuthPreferences.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt Outdated
Copy link
Copy Markdown
Member

@CHOOSLA CHOOSLA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숭인!

Copy link
Copy Markdown

@vivivim vivivim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다

@33Auto-Bot 33Auto-Bot added the ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR label Oct 26, 2025
@Sangyoon98 Sangyoon98 merged commit 16fb117 into dev Oct 26, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants