Skip to content

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

Merged
Sangyoon98 merged 3 commits intodevfrom
SPM-244
Oct 29, 2025
Merged

[SPM-244] 유저 로그인, 회원 가입, 토큰 갱신, 자동 로그인, 로그아웃, Authorization 전역 헤더 구현#17
Sangyoon98 merged 3 commits intodevfrom
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

  • New Features

    • 대시보드에서 로그아웃 기능 추가.
    • 액세스 토큰 자동 갱신(세션 연장) 기능 도입.
  • Improvements

    • 인증 상태 관리를 중앙화한 새 인증 흐름 및 상태 동기화.
    • 탭 구조 재정비 및 전역 배경 적용으로 앱 레이아웃 개선.
    • 일부 뷰에 의존성 주입 적용 및 내비게이션 흐름 개선.
    • 여러 UI 문자열을 지역화 키로 대체하여 다국어 지원 강화.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 24, 2025

Walkthrough

인증(토큰 갱신·인터셉터)과 네트워크 세션을 도입하고 AuthViewModel로 인증 상태를 중앙화했으며, TabView 기반의 앱 UI를 재구성하고 로컬라이제이션 키 및 일부 뷰 의존성 주입을 추가했습니다.

Changes

Cohort / File(s) 변경 요약
앱 진입/탭 재구성
SampoomManagement/App/ContentView.swift, SampoomManagement/App/RootView.swift, SampoomManagement/App/SampoomManagementApp.swift, SampoomManagement/App/Screens/DashboardScreen.swift
TabView 레이아웃 재구성(대시보드/Outbound/Cart/Orders/Parts), DashboardScreen 추가, AuthViewModel 기반 조건부 렌더링, 글로벌 배경 설정 추가
DI 및 앱 의존성
SampoomManagement/Core/DI/AppDependencies.swift
네트워크 인증 구성(tokenRefreshService, authRequestInterceptor) 추가, NetworkManager에 인터셉터 주입, Check/SignOut/ClearToken 유스케이스 및 AuthViewModel 노출
네트워크 인증 인프라
SampoomManagement/Core/Network/TokenRefreshService.swift, SampoomManagement/Core/Network/AuthRequestInterceptor.swift, SampoomManagement/Core/Network/NetworkManager.swift, SampoomManagement/Core/Network/NetworkError.swift
TokenRefreshService 및 AuthRequestInterceptor 도입(401 재시도/토큰 갱신 직렬화), NetworkManager를 세션 기반으로 변경, 에러 타입에 unauthorized/tokenRefreshFailed 추가
Auth 레이어: API/DTO/레포/프리퍼런스
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift, .../DTO/RefreshRequestDTO.swift, .../DTO/RefreshResponseDTO.swift, .../Repository/AuthRepositoryImpl.swift, .../Local/Preferences/AuthPreferences.swift
logout/refresh API 추가, Refresh DTO 추가, 레포지토리에 refreshToken/clearTokens/로그아웃 흐름 및 사용자 전체 저장(saveUser/getStoredUser) 추가
도메인/유스케이스/모델
SampoomManagement/Features/Auth/Domain/Models/User.swift, SampoomManagement/Features/Auth/Domain/Repository/AuthRepository.swift, SampoomManagement/Features/Auth/Domain/UseCase/*
User에 accessToken/refreshToken 추가, AuthRepository 프로토콜에 토큰 관련 메서드 추가, CheckLoginState/SignOut/ClearTokens 유스케이스 추가
Auth UI 및 ViewModel
SampoomManagement/Features/Auth/UI/AuthViewModel.swift, SampoomManagement/Features/Auth/UI/LoginView.swift
AuthViewModel 추가(isLoggedIn/shouldNavigateToLogin), 로그인 뷰에 초기화자 변경 및 onSuccess/onNavigateSignUp 콜백 전달
로컬라이제이션 및 UI 문자열 변경
SampoomManagement/Core/Resources/StringResources.swift, SampoomManagement/Features/Part/UI/PartView.swift, .../PartListView.swift, .../PartDetailBottomSheetView.swift
새로운 문자열 키 추가 및 하드코딩 문자열을 StringResources로 교체
의존성 주입/뷰 모델 연결 개선
SampoomManagement/Features/Cart/UI/CartListView.swift, SampoomManagement/Features/Order/UI/OrderListViewModel.swift
CartListView에 AppDependencies 전달을 위한 의존성 주입 추가, OrderListViewModel에서 MainActor.run 제거(스레딩 단순화)

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant AuthVM as AuthViewModel
    participant UseCase as CheckLoginStateUC
    participant Repo as AuthRepository
    participant Prefs as AuthPreferences
    participant NetMgr as NetworkManager
    participant Interc as AuthRequestInterceptor
    participant TokenSvc as TokenRefreshService
    participant API as AuthAPI

    App->>AuthVM: updateLoginState()
    AuthVM->>UseCase: execute()
    UseCase->>Repo: isSignedIn()
    Repo->>Prefs: getStoredUser()/토큰 확인
    Prefs-->>Repo: 저장값 반환
    Repo-->>UseCase: 로그인 상태 반환
    UseCase-->>AuthVM: isLoggedIn 업데이트
    AuthVM-->>App: UI 전환 결정

    Note over App,Interc: API 요청 흐름 (요약)
    App->>NetMgr: request(...)
    NetMgr->>Interc: adapt() - 헤더 주입
    Interc->>Prefs: getAccessToken()
    Prefs-->>Interc: accessToken
    Interc-->>NetMgr: 요청 전송(토큰 포함)
    NetMgr->>API: 원격호출
    alt 401 응답
        NetMgr->>Interc: retry() -> TokenRefresh 시도
        Interc->>TokenSvc: refreshToken() via Repo/API
        TokenSvc->>API: refresh(...)
        API-->>TokenSvc: 새 토큰 반환
        TokenSvc->>Prefs: saveUser(updatedUser)
        TokenSvc-->>Interc: 성공 -> 재시도 허용
        Interc-->>NetMgr: 요청 재전송
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ready-to-merge

Suggested reviewers

  • Lee-Jong-Jin
  • CHOOSLA
  • yangjiseonn

Poem

🐰 토큰이 바람처럼 갱신되네,
인터셉터가 길을 지키고,
탭이 새로 정리된 숲길 위,
AuthViewModel이 등불을 들면,
앱은 부드럽게 로그인 길을 걷네. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.03% 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 타이틀이 변경 사항의 주요 내용을 정확하게 반영하고 있습니다. 타이틀에 나열된 모든 항목(유저 로그인, 회원 가입, 토큰 갱신, 자동 로그인, 로그아웃, Authorization 전역 헤더)이 raw_summary에서 확인되며, 각각이 실제로 구현되었습니다. AuthViewModel, CheckLoginStateUseCase, SignOutUseCase 추가를 통한 로그인 상태 관리 개선, TokenRefreshService와 AuthRequestInterceptor를 통한 토큰 갱신 및 Authorization 헤더 구현, RootView의 자동 로그인 로직 강화, DashboardScreen의 로그아웃 기능 등이 모두 타이틀과 일치합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SPM-244

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
Contributor

@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

Caution

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

⚠️ Outside diff range comments (4)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1)

33-52: MainActor 컨텍스트 누락으로 인한 데이터 레이스 위험

코드를 검증한 결과, 이 변경은 안전하지 않습니다.

GetOrderUseCase@MainActor로 표시되지 않았으며, execute() 메서드도 메인 액터 격리되지 않았습니다. Swift 동시성 규칙에 따르면:

  • Line 35의 첫 상태 업데이트는 메인 스레드에서 실행되지만
  • Line 38의 try await getOrderUseCase.execute() 이후, 실행 컨텍스트가 메인 액터에서 벗어날 수 있음
  • Lines 39-43, 45-48의 상태 업데이트가 백그라운드 스레드에서 실행되면 @Published 속성 수정으로 인한 데이터 레이스가 발생

OrderDetailViewModel도 동일한 패턴을 사용하므로 같은 문제를 갖고 있습니다.

필수 수정: 상태 업데이트를 MainActor.run으로 감싸거나, Task { @MainActor in ... } 형태로 명시적으로 메인 액터 격리를 선언해야 합니다.

private func loadOrderList() {
    Task {
        uiState = uiState.copy(orderLoading: true, orderError: nil)
        
        do {
            let orderList = try await getOrderUseCase.execute()
            await MainActor.run {
                uiState = uiState.copy(
                    orderList: orderList.items,
                    orderLoading: false,
                    orderError: nil
                )
            }
        } catch {
            await MainActor.run {
                uiState = uiState.copy(
                    orderLoading: false,
                    orderError: error.localizedDescription
                )
            }
        }
        print("OrderListViewModel - loadOrderList: \(uiState)")
    }
}

또는 더 간단하게:

private func loadOrderList() {
    Task { @MainActor in
        uiState = uiState.copy(orderLoading: true, orderError: nil)
        
        do {
            let orderList = try await getOrderUseCase.execute()
            uiState = uiState.copy(
                orderList: orderList.items,
                orderLoading: false,
                orderError: nil
            )
        } catch {
            uiState = uiState.copy(
                orderLoading: false,
                orderError: error.localizedDescription
            )
        }
    }
}
SampoomManagement/Core/Network/NetworkManager.swift (3)

31-36: HTTP 상태코드 검증 누락으로 4xx/5xx가 성공으로 처리될 수 있습니다

.validate()가 없어 전송 계층만 성공이면 4xx/5xx도 .success로 들어와 잘못된 디코딩/에러 처리로 이어질 수 있습니다. 상태코드를 2xx로 한정해 주세요.

적용 예:

-                let dataRequest = session.request(
+                let dataRequest = session.request(
                     url,
                     method: method,
                     parameters: parameters,
                     encoding: method == .get ? URLEncoding.default : JSONEncoding.default
-                )
+                ).validate()

29-58: Task가 취소돼도 요청이 취소되지 않습니다

withTaskCancellationHandleronCancel에서 DataRequest.cancel()을 호출하지 않아 화면 전환/뒤로가기 시 네트워크가 계속 진행됩니다. 요청 참조를 잡아 취소를 전달하세요.

적용 예:

         return try await withTaskCancellationHandler(operation: {
-            try await withCheckedThrowingContinuation { continuation in
-                let dataRequest = session.request(
+            var runningRequest: DataRequest?
+            try await withCheckedThrowingContinuation { continuation in
+                let req = session.request(
                     url,
                     method: method,
                     parameters: parameters,
                     encoding: method == .get ? URLEncoding.default : JSONEncoding.default
-                )
+                ).validate()
+                runningRequest = req
-                dataRequest.responseData { response in
+                req.responseData { response in
                     switch response.result {
                         ...
                     }
                 }
             }
         }, onCancel: {
-        })
+            runningRequest?.cancel()
+        })

42-53: 민감 정보/토큰이 로그로 노출될 수 있습니다

원문 응답과 디코드 객체를 그대로 print 하면 토큰·개인정보가 콘솔/크래시 로그에 남을 수 있습니다. 운영 빌드에서 비활성화하고(예: #if DEBUG), 민감 필드를 마스킹하거나 OSLog 사용을 권장합니다.

적용 예:

-                            print("NetworkManager - Raw response data: \(String(data: data, encoding: .utf8) ?? "Unable to decode")")
+                            #if DEBUG
+                            print("NetworkManager - Raw response data (truncated): \(String(data: data.prefix(512), encoding: .utf8) ?? "Unable to decode")")
+                            #endif
...
-                            print("NetworkManager - Decoded response: \(apiResponse)")
+                            #if DEBUG
+                            print("NetworkManager - Decoded response: \(String(describing: type(of: apiResponse)))")
+                            #endif
...
-                        print("NetworkManager - Decoding error: \(error)")
+                        #if DEBUG
+                        print("NetworkManager - Decoding error: \(error)")
+                        #endif
🧹 Nitpick comments (16)
SampoomManagement/Core/Network/NetworkManager.swift (1)

21-26: CodableDecodable로 제약 축소 및 불필요 파라미터 제거 제안

응답은 디코딩만 필요합니다. 제네릭을 T: Decodable로 완화하고, 타입 추론이 가능하면 responseType 파라미터는 제거해 깔끔하게 유지하세요. 기존 호출부 영향이 있으면 유지해도 무방합니다.

적용 예:

-    func request<T: Codable>(
+    func request<T: Decodable>(
         endpoint: String,
         method: HTTPMethod = .get,
         parameters: Parameters? = nil,
-        responseType: T.Type
+        responseType: T.Type
     ) async throws -> APIResponse<T> {

또는 호출부에서 타입이 명확하면 responseType 제거를 고려.

SampoomManagement/Features/Auth/UI/AuthViewModel.swift (1)

40-46: print 대신 안전한 로깅으로 교체

에러 메시지를 print로 남기면 운영 빌드에서 로그 누수가 발생합니다. OSLog.Logger로 레벨 구분하고, 운영 빌드에서 과도한 정보가 남지 않도록 조정해 주세요.

예시:

import OSLog
private let logger = Logger(subsystem: "SampoomManagement", category: "Auth")

do {
  try await signOutUseCase.execute()
} catch {
  logger.error("Sign out failed: \(String(describing: error))")
}

Also applies to: 52-62

SampoomManagement/App/RootView.swift (3)

15-16: @ObservedObject 초기화 스타일 일관화

초기화에서 래퍼 형식(_authViewModel = ObservedObject(wrappedValue:))을 쓰면 SwiftUI 경고를 피하고 일관성이 좋아집니다. 기능은 동일합니다.

적용 예:

-    @ObservedObject private var authViewModel: AuthViewModel
+    @ObservedObject private var authViewModel: AuthViewModel
...
-        self.authViewModel = dependencies.authViewModel
+        _authViewModel = ObservedObject(wrappedValue: dependencies.authViewModel)

Also applies to: 22-23


74-79: onChange(of:_:_)는 iOS 17+ 전용 API입니다 — 최소 타겟 확인 필요

프로젝트 최소 타겟이 iOS 16이면 빌드가 실패합니다. 타겟을 확인하거나 iOS 16 호환 시그니처로 대체하세요.

iOS 16 호환 예:

-        .onChange(of: authViewModel.shouldNavigateToLogin) { _, shouldNavigate in
+        .onChange(of: authViewModel.shouldNavigateToLogin) { shouldNavigate in
             if shouldNavigate {
                 showSignUp = false
                 authViewModel.resetNavigationState()
             }
         }

41-45: 성공 시 UI 상태도 함께 정리

로그인/회원가입 성공 시 updateLoginState()만으로도 화면 전환되지만, 잔여 상태를 정리하면 이후 플로우가 명확해집니다. showSignUp = false를 함께 설정하는 것을 권장합니다.

적용 예:

-                                    authViewModel.updateLoginState()
+                                    authViewModel.updateLoginState()
+                                    showSignUp = false
...
-                                authViewModel.updateLoginState()
+                                authViewModel.updateLoginState()
+                                showSignUp = false

Also applies to: 61-68

SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)

106-108: clearTokens()에 불필요한 async throws

내부가 동기·비예외 동작이면 시그니처를 단순화하세요. 현재 프로토콜 제약 때문에 유지해야 한다면 주석으로 의도를 남겨 혼란을 줄이세요.

예:

// NOTE: 현재는 동기/비예외지만 프로토콜 정합성 및 향후 비동기/에러 전파 대비해 async throws 유지
func clearTokens() async throws {
    preferences.clear()
}
SampoomManagement/Features/Auth/Domain/Repository/AuthRepository.swift (1)

22-24: clearTokens() 시그니처 정합성 재검토

구현이 동기·비예외라면 async throws는 과합니다. 향후 확장을 의도한 것이면 주석으로 명시하거나, 지금은 func clearTokens()로 단순화 후 필요 시 승격하는 방식을 권장합니다. 호출부/유즈케이스 영향 범위를 함께 검토해 주세요.

SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3)

16-21: 키 네임스페이스 상수화 제안

"auth.*" 접두사를 상수로 분리하면 오타/변경 리스크를 줄일 수 있습니다. 예: private static let ns = "auth."; static let userId = ns + "userId" 등.


22-40: 부분 실패 롤백 처리 좋습니다.

토큰/유저 필드 동시 저장 시 롤백으로 일관성 보장한 점 좋습니다. 추후 일관성 강화를 위해 단일 JSON(직렬화된 User)을 한 키에 저장하는 옵션도 고려해볼 수 있습니다.


54-79: 파싱 견고성 및 타입 폭 제안

  • expiresIn 이 큰 값일 수 있으므로 Int64(또는 TimeInterval) 사용 검토 바랍니다.
  • 일부 키만 존재하는 부분 장애 상태가 반복되면 return nil 대신 clear() 로 정리하거나, 최소한 경고 로그를 한 번만 남기도록 스로틀링을 고려하세요.
SampoomManagement/App/Screens/DashboardScreen.swift (1)

27-36: 로그아웃 확인 다이얼로그/파괴적 역할 추가 권장

실수 방지를 위해 확인 시트/알럿을 두고, 가능한 경우 버튼 role: .destructive 를 적용하세요. 커스텀 CommonButton이 role 미지원이면 상위에서 .alert 로 감싸는 방식 권장.

로그아웃 시 네비게이션 스택(ordersNavigationPath, partsNavigationPath) 초기화가 상위(Root)에서 보장되는지 확인 부탁드립니다.

SampoomManagement/Core/Network/TokenRefreshService.swift (2)

22-41: 하드코딩된 URL과 타임아웃/헤더 보완

  • Base URL/경로를 구성에서 주입하세요(.plist/DI).
  • 네트워크 지연 대비 타임아웃 설정을 권장합니다.
  • 서버가 JSON만 반환한다면 Accept: application/json 추가.

예시:

-        let url = URL(string: "https://sampoom.store/api/auth/refresh")!
+        let url = URL(string: baseURL.appendingPathComponent("/api/auth/refresh"))!
@@
-        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+        request.setValue("application/json", forHTTPHeaderField: "Accept")
+        request.timeoutInterval = 15

45-61: 저장 경로 일관성 및 에러 맵핑

getStoredUser() 부재로 tokenRefreshFailed 를 던지는 것은 합리적입니다. 추가로 저장 실패 시 AuthError.tokenSaveFailed 로 래핑하여 상위에서 원인 구분이 가능하도록 하는 것도 고려해보세요. 현재 인터셉터와 레포에서의 에러 정책을 일치시켜 주세요.

레포의 AuthRepositoryImpl.refreshToken() 과 본 서비스의 동작이 중복됩니다. 하나로 통합하거나 호출 계층을 명확히 해 중복 갱신을 피하는지 확인 부탁드립니다.

SampoomManagement/App/ContentView.swift (1)

35-43: Tab(value:) 및 role 사용의 iOS 타겟 확인

Tab(value:)/role: .search 는 최신 SwiftUI API입니다. 프로젝트 최소 iOS 버전에서 컴파일/동작 가능한지 확인하세요. 필요 시 기존 .tabItem {} + .tag(...) 패턴으로 폴백을 준비하세요.

다음도 함께 점검 바랍니다:

  • 로그아웃 시 selectedTab 초기화 필요 여부(예: .dashboard).
  • 로그아웃 후 ordersNavigationPath/partsNavigationPath reset 타이밍.

Also applies to: 50-64, 66-83, 85-110, 112-140, 141-143

SampoomManagement/Core/DI/AppDependencies.swift (2)

25-25: AuthViewModel 생성 패턴 통일 + 지연 초기화 제안

현재 DI에서 대부분의 VM은 factory 메서드로 생성하지만, AuthViewModel만 상수 프로퍼티로 즉시 생성되어 패턴이 혼재합니다. 앱 시작 시 불필요한 초기화를 줄이고 일관성을 위해 lazy 또는 factory로 통일하는 것을 권장합니다. 또한 AuthViewModel이 @mainactor 컨텍스트를 요구한다면 메인 스레드에서 초기화되는지 확인해주세요.

적용 예시(lazy로 전환):

-    let authViewModel: AuthViewModel
+    lazy var authViewModel: AuthViewModel = {
+        AuthViewModel(
+            checkLoginStateUseCase: checkLoginStateUseCase,
+            signOutUseCase: signOutUseCase,
+            clearTokensUseCase: clearTokensUseCase
+        )
+    }()

init 내 즉시 생성 제거:

-        // Auth ViewModel
-        authViewModel = AuthViewModel(
-            checkLoginStateUseCase: checkLoginStateUseCase,
-            signOutUseCase: signOutUseCase,
-            clearTokensUseCase: clearTokensUseCase
-        )

또는 다른 VM과 동일하게 makeAuthViewModel() 팩토리로 노출하는 방식을 선택해도 좋습니다.

Also applies to: 93-98


89-91: SignOutUseCase vs ClearTokensUseCase 역할 경계 명확화

두 유스케이스의 책임이 겹쳐 보일 수 있습니다.

  • 예: 사용자 요청 로그아웃(SignOut)과 세션 만료 등 비자발적 로그아웃(ClearTokens) 분리라면, 간단한 문서화/주석으로 의도를 명확히 해두는 것을 권장합니다. 필요 시 네이밍 보완도 고려해주세요(예: PurgeAuthStateUseCase).
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebe57ca and ab52662.

📒 Files selected for processing (28)
  • SampoomManagement/App/ContentView.swift (1 hunks)
  • SampoomManagement/App/RootView.swift (1 hunks)
  • SampoomManagement/App/SampoomManagementApp.swift (2 hunks)
  • SampoomManagement/App/Screens/DashboardScreen.swift (1 hunks)
  • SampoomManagement/Core/DI/AppDependencies.swift (2 hunks)
  • SampoomManagement/Core/Network/AuthRequestInterceptor.swift (1 hunks)
  • SampoomManagement/Core/Network/NetworkError.swift (4 hunks)
  • SampoomManagement/Core/Network/NetworkManager.swift (2 hunks)
  • SampoomManagement/Core/Network/TokenRefreshService.swift (1 hunks)
  • SampoomManagement/Core/Resources/StringResources.swift (2 hunks)
  • SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3 hunks)
  • SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/DTO/RefreshRequestDTO.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/DTO/RefreshResponseDTO.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/Models/User.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/Repository/AuthRepository.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/UseCase/CheckLoginStateUseCase.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/UseCase/ClearTokensUseCase.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/UseCase/SignOutUseCase.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/AuthViewModel.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/LoginView.swift (2 hunks)
  • SampoomManagement/Features/Cart/UI/CartListView.swift (2 hunks)
  • SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1 hunks)
  • SampoomManagement/Features/Part/UI/PartDetailBottomSheetView.swift (1 hunks)
  • SampoomManagement/Features/Part/UI/PartListView.swift (1 hunks)
  • SampoomManagement/Features/Part/UI/PartView.swift (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (20)
SampoomManagement/Features/Auth/Domain/Models/User.swift (2)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • refreshToken (71-104)
SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (2)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • refreshToken (71-104)
SampoomManagement/Core/Resources/StringResources.swift (1)
SampoomManagement/Features/Part/UI/PartViewModel.swift (1)
  • selectCategory (62-67)
SampoomManagement/Features/Auth/Domain/Repository/AuthRepository.swift (3)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (5)
  • refreshToken (71-104)
  • clearTokens (106-108)
  • isSignedIn (110-112)
  • getAccessToken (115-117)
  • getRefreshToken (119-121)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (2)
  • getAccessToken (81-83)
  • getRefreshToken (85-87)
SampoomManagement/Features/Cart/UI/CartListView.swift (1)
SampoomManagement/Core/DI/AppDependencies.swift (1)
  • makeOrderDetailViewModel (193-200)
SampoomManagement/Features/Auth/Domain/UseCase/ClearTokensUseCase.swift (3)
SampoomManagement/Features/Auth/Domain/UseCase/CheckLoginStateUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Domain/UseCase/SignOutUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • clearTokens (106-108)
SampoomManagement/Features/Auth/Data/Remote/DTO/RefreshResponseDTO.swift (2)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • refreshToken (71-104)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (2)
SampoomManagement/Features/Order/UI/OrderListUiState.swift (1)
  • copy (25-35)
SampoomManagement/Features/Order/Domain/UseCase/GetOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/App/Screens/DashboardScreen.swift (1)
SampoomManagement/Features/Auth/UI/AuthViewModel.swift (1)
  • signOut (40-50)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3)
SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (3)
  • save (24-43)
  • delete (73-85)
  • get (45-71)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • refreshToken (71-104)
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (3)
SampoomManagement/Core/Network/NetworkManager.swift (1)
  • request (21-59)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • refreshToken (71-104)
SampoomManagement/Features/Auth/Domain/UseCase/SignOutUseCase.swift (4)
SampoomManagement/Features/Auth/Domain/UseCase/CheckLoginStateUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Domain/UseCase/ClearTokensUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/UI/AuthViewModel.swift (1)
  • signOut (40-50)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • signOut (59-69)
SampoomManagement/Features/Auth/Data/Remote/DTO/RefreshRequestDTO.swift (2)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • refreshToken (71-104)
SampoomManagement/App/ContentView.swift (1)
SampoomManagement/Core/DI/AppDependencies.swift (7)
  • makePartViewModel (147-152)
  • makeOutboundListViewModel (169-177)
  • makeCartListViewModel (179-187)
  • makeOrderListViewModel (189-191)
  • makeOrderDetailViewModel (193-200)
  • makeSearchViewModel (164-167)
  • makePartListViewModel (154-158)
SampoomManagement/Features/Auth/UI/AuthViewModel.swift (4)
SampoomManagement/Features/Auth/Domain/UseCase/CheckLoginStateUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Domain/UseCase/ClearTokensUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Domain/UseCase/SignOutUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • signOut (59-69)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (5)
SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)
  • toModel (11-20)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (4)
  • saveUser (22-40)
  • clear (108-120)
  • getRefreshToken (85-87)
  • getStoredUser (54-79)
SampoomManagement/Features/Auth/UI/AuthViewModel.swift (1)
  • signOut (40-50)
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (2)
  • logout (71-78)
  • refresh (81-94)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Core/Network/AuthRequestInterceptor.swift (2)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (2)
  • getAccessToken (81-83)
  • clear (108-120)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Domain/UseCase/CheckLoginStateUseCase.swift (1)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • isSignedIn (110-112)
SampoomManagement/App/RootView.swift (1)
SampoomManagement/Features/Auth/UI/AuthViewModel.swift (2)
  • updateLoginState (36-38)
  • resetNavigationState (65-67)
SampoomManagement/Core/Network/TokenRefreshService.swift (2)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (2)
  • refreshToken (71-104)
  • getRefreshToken (119-121)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3)
  • getRefreshToken (85-87)
  • getStoredUser (54-79)
  • saveUser (22-40)
🔇 Additional comments (25)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1)

14-14: 코드 구성이 개선되었습니다.

MARK 주석을 추가하여 코드의 가독성과 탐색성이 향상되었습니다.

Also applies to: 19-19, 24-24, 32-32

SampoomManagement/Features/Auth/UI/LoginView.swift (2)

20-24: LGTM! 의존성 주입 패턴이 올바르게 적용되었습니다.

커스텀 이니셜라이저를 통해 뷰모델과 성공/회원가입 네비게이션 콜백을 주입받는 구조가 적절합니다.


112-116: LGTM! 로그인 성공 핸들러가 올바르게 구현되었습니다.

onChange 핸들러의 순서 변경은 기능에 영향을 주지 않으며, 성공 시 콜백 실행 로직이 적절합니다.

SampoomManagement/Features/Auth/Data/Remote/DTO/RefreshResponseDTO.swift (1)

10-13: LGTM! 토큰 갱신 응답 DTO가 올바르게 정의되었습니다.

필드 타입과 네이밍이 적절하며, TokenRefreshService와 AuthRepositoryImpl에서 사용되는 구조와 일치합니다.

SampoomManagement/Features/Part/UI/PartDetailBottomSheetView.swift (1)

173-173: LGTM! 지역화 리소스 적용이 적절합니다.

하드코딩된 문자열을 StringResources로 변경하여 일관성 있는 지역화 패턴을 따르고 있습니다.

SampoomManagement/Features/Cart/UI/CartListView.swift (2)

13-13: LGTM! 의존성 주입 구조가 올바르게 적용되었습니다.

AppDependencies를 통한 중앙화된 DI 패턴이 적절하게 구현되었습니다.


54-54: LGTM! 뷰모델 생성이 DI 컨테이너로 이동되었습니다.

OrderDetailViewModel을 dependencies.makeOrderDetailViewModel을 통해 생성하도록 변경하여 일관된 의존성 관리를 구현했습니다.

SampoomManagement/Features/Part/UI/PartListView.swift (1)

68-68: LGTM! 네비게이션 타이틀 지역화가 올바르게 적용되었습니다.

하드코딩된 문자열을 StringResources로 교체하여 지역화 일관성을 개선했습니다.

SampoomManagement/Features/Auth/Data/Remote/DTO/RefreshRequestDTO.swift (1)

10-11: LGTM! 토큰 갱신 요청 DTO가 올바르게 정의되었습니다.

단순하고 명확한 구조로 토큰 갱신 API 요청에 적절합니다.

SampoomManagement/Core/Network/NetworkError.swift (2)

17-17: LGTM! unauthorized 에러 케이스가 적절하게 추가되었습니다.

인증 실패 처리를 위한 에러 케이스와 설명이 올바르게 정의되었습니다.

Also applies to: 33-34


44-45: LGTM! 인증 관련 에러 케이스가 적절하게 추가되었습니다.

tokenRefreshFailed와 unauthorized 케이스가 토큰 갱신 플로우를 지원하도록 올바르게 구현되었습니다.

Also applies to: 57-60

SampoomManagement/App/SampoomManagementApp.swift (2)

19-19: LGTM! 전역 백그라운드 설정이 초기화에 추가되었습니다.

앱 전체의 일관된 UI 설정을 위해 적절한 위치에서 호출되고 있습니다.


39-51: "Background" 컬러 애셋 존재 확인 완료 - 우려사항 없음

검증 결과, "Background" 컬러 애셋이 SampoomManagement/Resources/Assets.xcassets/Background.colorset/에 정상적으로 존재합니다. 코드는 if-let 바인딩으로 안전하게 처리하고 있으며, 백그라운드 컬러가 정상적으로 적용됩니다.

SampoomManagement/Features/Auth/Domain/Models/User.swift (1)

14-15: 토큰 필드 추가가 적절합니다.

User 모델에 accessTokenrefreshToken 필드를 추가하여 토큰 관리 기능을 지원합니다. 관련 파일들(AuthMappers, TokenRefreshService, AuthRepositoryImpl)에서 일관되게 사용되고 있으며, AuthPreferences를 통해 키체인에 안전하게 저장됩니다.

SampoomManagement/Features/Auth/Domain/UseCase/CheckLoginStateUseCase.swift (1)

10-20: Clean Architecture 패턴을 잘 따르고 있습니다.

로그인 상태 확인 로직을 use case로 캡슐화하여 책임을 명확히 분리하였으며, 의존성 주입을 통해 테스트 가능한 구조를 갖추었습니다.

SampoomManagement/Features/Auth/Domain/UseCase/SignOutUseCase.swift (1)

10-20: 로그아웃 로직이 올바르게 캡슐화되었습니다.

Use case 패턴을 통해 로그아웃 로직을 도메인 레이어에서 명확히 정의하였으며, repository에 위임하여 책임을 분리하였습니다.

SampoomManagement/Features/Part/UI/PartView.swift (1)

32-32: 로컬라이제이션 개선이 우수합니다.

하드코딩된 문자열을 StringResources로 교체하여 유지보수성과 향후 다국어 지원 가능성을 높였습니다.

Also applies to: 58-60, 129-129, 137-137

SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)

16-17: 매핑 로직이 User 모델 변경과 일관됩니다.

LoginResponseDTO에서 User로 변환 시 새로 추가된 토큰 필드들을 올바르게 매핑하고 있습니다.

SampoomManagement/Features/Auth/Domain/UseCase/ClearTokensUseCase.swift (1)

10-20: 토큰 초기화 로직이 명확히 분리되었습니다.

토큰 만료 시 토큰을 초기화하는 책임을 use case로 캡슐화하여 도메인 로직을 명확히 정의하였습니다.

SampoomManagement/Core/Resources/StringResources.swift (1)

134-137: 문자열 리소스가 적절히 추가되었습니다.

Part 및 Auth 기능에 필요한 새로운 문자열 리소스를 일관된 방식으로 추가하였습니다.

Also applies to: 174-174

SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (2)

70-78: 로그아웃 API 메서드가 올바르게 구현되었습니다.

로그아웃 엔드포인트 호출이 적절하며, NetworkManager의 interceptor를 통해 Authorization 헤더가 자동으로 추가됩니다.


81-94: 토큰 갱신 API 메서드가 올바르게 구현되었습니다.

refreshToken을 파라미터로 받아 API를 호출하는 로직이 명확합니다. NetworkManager를 사용하는 이 메서드는 일반적인 API 호출 경로이며, TokenRefreshService는 interceptor 순환 참조를 피하기 위해 URLSession을 직접 사용하는 별도 경로입니다.

SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)

71-104: API 새로고침 요청의 인터셉터 이중 경로 문제 및 clearTokens 시그니처 불일치

코드 검토 결과, TokenRefreshService가 URLSession.shared를 사용해 인터셉터를 회피하므로 무한 재귀 위험은 완화되어 있습니다. 그러나 두 가지 실질적인 문제가 있습니다:

  1. 토큰 갱신 경로의 불일치 (AuthRepositoryImpl 71-104줄):

    • AuthRepositoryImpl.refreshToken()api.refresh() → NetworkManager (인터셉터 포함)
    • AuthRequestInterceptor.retry()TokenRefreshService.refreshToken() → URLSession.shared (인터셉터 없음)

    직접 호출과 재시도 경로가 다릅니다. 일관성을 위해 AuthRepositoryImpl.refreshToken()도 TokenRefreshService를 직접 사용하는 것을 권장합니다.

  2. clearTokens() 함수 시그니처 오류 (AuthRepositoryImpl 106-108줄):

    • async throws로 선언했으나 실제로는 동기이고 예외를 발생하지 않음
    • preferences.clear()는 동기 메서드이며 오류 처리 없음
    • 시그니처를 func clearTokens()로 수정하거나, 실제로 비동기 처리가 필요한 경우 구현 변경 필요

권장사항: AuthRepositoryImpl.refreshToken()을 TokenRefreshService 위임으로 단순화하고, clearTokens() 시그니처 정정

SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1)

108-116: 로그아웃 클리어 범위 적절합니다.

추가된 키까지 모두 삭제해 잔존 상태가 남지 않습니다. LGTM.

SampoomManagement/Core/DI/AppDependencies.swift (1)

68-80: 초기화 순서 적절. Authorization 부착 조건만 재확인 부탁

AuthPreferences → TokenRefreshService → AuthRequestInterceptor → NetworkManager 순으로 초기화된 점은 합리적입니다. 로그인/회원가입 요청 시 토큰이 없을 수 있으므로, Interceptor가 액세스 토큰 존재 시에만 Authorization 헤더를 부착하는지, 그리고 NetworkManager가 헤더를 캐싱하지 않고 매 요청마다 Interceptor를 통해 최신 토큰을 반영하는지 확인 부탁드립니다.

Comment thread SampoomManagement/Core/DI/AppDependencies.swift
Comment thread SampoomManagement/Core/Network/AuthRequestInterceptor.swift
Comment thread SampoomManagement/Core/Network/AuthRequestInterceptor.swift Outdated
Comment thread SampoomManagement/Core/Network/AuthRequestInterceptor.swift Outdated
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.

전역 헤더 좋은 아이디어입니다.

Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (4)
SampoomManagement/Core/DI/AppDependencies.swift (1)

28-29: 가시성 축소 적용 LGTM

refresh 서비스와 인터셉터를 private로 제한한 선택 좋습니다. 이전 피드백 반영 확인됩니다.

SampoomManagement/Core/Network/AuthRequestInterceptor.swift (3)

11-26: 동시 갱신 병합(coalescing) 구현 적절

단일 in-flight Task로 병합하고 완료 후 해제하는 패턴이 명확합니다. 이전 제안이 잘 반영되었습니다.


38-53: Authorization 항상 최신 토큰으로 덮어쓰기 — OK

매 요청마다 AccessToken을 조회해 헤더를 갱신/제거하는 흐름이 적절합니다. 👍

추가로, 방어적 코딩 차원에서 인증 엔드포인트(예: /api/auth/)에는 헤더를 생략하는 조건을 둘 수도 있습니다. (미래에 실수로 refresh를 AF 경로로 태울 경우 대비)

 func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
     var adaptedRequest = urlRequest
-    do {
+    // 인증 관련 경로는 헤더 부착 생략 (방어적)
+    if let url = adaptedRequest.url, url.path.contains("/api/auth/") {
+        adaptedRequest.setValue(nil, forHTTPHeaderField: "Authorization")
+        completion(.success(adaptedRequest))
+        return
+    }
+    do {
         if let accessToken = try authPreferences.getAccessToken() {
             adaptedRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
         } else {
             adaptedRequest.setValue(nil, forHTTPHeaderField: "Authorization")
         }
     } catch {
         print("AuthRequestInterceptor - 토큰 조회 실패: \(error)")
     }
     
     completion(.success(adaptedRequest))
 }

55-80: 컴파일 오류: await authPreferences.clear() 제거 필요

AuthPreferences.clear()는 동기 메서드입니다. await 사용 시 컴파일 실패합니다.

다음 수정 제안:

-                // 토큰 재발급 실패 시 로그아웃 처리
-                await authPreferences.clear()
+                // 토큰 재발급 실패 시 로그아웃 처리
+                authPreferences.clear()
                 completion(.doNotRetry)

보완 제안:

  • 인증/로그인/리프레시 경로에서는 리프레시를 시도하지 않도록 우회 조건을 추가해 불필요한 재시도를 줄이세요.

빠른 점검 스크립트:

#!/bin/bash
# await 사용 여부 점검
rg -nP --type=swift '\bawait\s+authPreferences\.clear\s*\(' -C1
# 인증 경로 우회 필요 여부 (retry에서 URL 확인)
rg -nP --type=swift 'func\s+retry\(' -n -C3
🧹 Nitpick comments (2)
SampoomManagement/Core/DI/AppDependencies.swift (1)

89-99: AuthViewModel 수명/스레드 문맥 확인 권장

앱 전역 단일 인스턴스로 보입니다. SwiftUI에서 메인 액터 상호작용이 많다면 생성/접근이 MainActor에서 이뤄지는지 확인해 주세요. 필요 시 @mainactor 주석 또는 생성 시점 보장을 고려하세요.

SampoomManagement/Core/Network/AuthRequestInterceptor.swift (1)

28-36: @unchecked Sendable 사용 근거 확인

현재 상태로도 안전해 보이나, @unchecked Sendable는 컴파일러 보증을 우회합니다. 내부가 불변(immutable)이고 동시성 보호(Actor 등)가 충분한지 주석으로 근거를 남기거나, 불필요하면 제거를 고려하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab52662 and 29c95cf.

📒 Files selected for processing (2)
  • SampoomManagement/Core/DI/AppDependencies.swift (2 hunks)
  • SampoomManagement/Core/Network/AuthRequestInterceptor.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
SampoomManagement/Core/Network/AuthRequestInterceptor.swift (2)
SampoomManagement/Core/Network/TokenRefreshService.swift (1)
  • refreshToken (17-62)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (2)
  • getAccessToken (81-83)
  • clear (108-120)
🔇 Additional comments (2)
SampoomManagement/Core/DI/AppDependencies.swift (2)

68-80: 네트워크 인증 배선 순서 적절

AuthPreferences → TokenRefreshService → AuthRequestInterceptor → NetworkManager 순으로 초기화하여 인터셉터-리프레시 루프를 방지합니다. 👍


22-26: authViewModel은 외부에서 직접 사용되므로 노출 유지 필수

authViewModelRootView.swiftDashboardScreen.swift에서 상태 바인딩 및 메서드 호출(updateLoginState, signOut)에 직접 활용되고 있습니다. 따라서 private으로 축소할 수 없습니다.

반면 checkLoginStateUseCase, signOutUseCase, clearTokensUseCase는 외부에서 직접 참조되지 않으므로, 만약 DI 내부 배선 전용이라면 private으로 제한하는 것이 캡슐화 관점에서 더 안전할 수 있습니다. 그러나 현재 구조(AuthViewModel 내부에서만 사용)에서는 노출 여부의 실질적 영향이 제한적이므로, 의도된 설계인지 검토하기 바랍니다.

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.

SSL Pinning을 구현할 때가 됐군요 추후에 소통해요 ^^

@33Auto-Bot 33Auto-Bot added the ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR label Oct 24, 2025
@Sangyoon98
Copy link
Copy Markdown
Member Author

SSL Pinning을 구현할 때가 됐군요 추후에 소통해요 ^^

네 교수님^^

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