Skip to content

feat(auth): 소셜 로그인 UI 리디자인 + 애플 인증 서비스 계층 분리 - #113

Merged
gomminjae merged 4 commits into
developfrom
feature/social-login
Jul 12, 2026
Merged

gomminjae merged 4 commits into
developfrom
feature/social-login

Conversation

@gomminjae

Copy link
Copy Markdown
Collaborator

요약

  • 로그인 진입 화면을 카카오/애플 커스텀 버튼으로 리디자인 (네이티브 SignInWithAppleButton 제거)
  • "3초만에 시작하기" 뱃지(#FB4F4F), 카카오 말풍선 에셋 추가
  • 애플 인증을 클린 아키텍처에 맞춰 계층 분리

변경

  • AppleAuthServiceProtocol(Domain) + AppleAuthService(Data) 신설 — 카카오와 동일하게 fetchIDToken() async throws -> String
  • LoginViewModel: loginWithApple이 카카오와 동일 흐름(fetchIDToken → authenticate), AuthenticationServices/UIKit 의존 제거
  • DI: AuthDIContainer에서 AppleAuthService() 주입
  • 테스트: MockAppleAuthService 추가, makeSUT 대칭화

검증

  • Newdok-Workspace 빌드 성공 (Swift 6 strict concurrency 통과)

🤖 Generated with Claude Code

gomminjae and others added 3 commits June 29, 2026 23:11
- 카카오: KakaoSDK OIDC idToken 획득, Data 레이어로 SDK 격리
  (KakaoAuthServiceProtocol/KakaoAuthService)
- 애플: SignInWithAppleButton + identityToken 추출
- POST /auth/social-login { provider, platform, idToken }, 응답은 기존 로그인 DTO 재사용
- LoginView를 소셜 버튼 + 게스트로 교체, 이메일/비번 로그인 제거
- SocialProvider, LoginError(cancelled/missingIDToken) 추가
- Apple Sign In entitlement, NewdokApp 카카오 SDK init + onOpenURL
- 테스트/목 소셜 플로우로 갱신

신규 유저 회원정보 입력 플로우(/auth/social-login/signup)는
백엔드 응답 스펙(신규 판별/signupToken) 확정 후 작업 예정.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- POST /auth/social-login 응답 isRegistered 분기: 신규 회원이면 signupToken으로 회원가입 진입
- POST /auth/social-login/signup 배선(회원정보 + 약관 4종 agreements), 전화인증/아이디/비번 스텝·뷰 제거
- SignupStep을 .enterProfile(회원정보입력)부터 시작, LoginView 신규유저 onNeedSignup 라우팅
- AppRoute.signup에 signupToken/nickname 연관값, 게스트 회원가입 CTA는 .login으로
- AuthUser/AuthUserDTO에서 미사용 loginId/phoneNumber 제거
- 미인증 엔드포인트(로그인/가입) 401이 전역 세션만료를 오발화하지 않도록 APIRequest.emitsUnauthorizedEvent 도입
- 카카오/애플 커스텀 버튼으로 통일 (네이티브 SignInWithAppleButton 제거)
- "3초만에 시작하기" 뱃지, 카카오 말풍선 에셋(kakaoBubble) 추가
- AppleAuthServiceProtocol(Domain) + AppleAuthService(Data)로 애플 인증 분리
  → ViewModel이 카카오와 동일하게 프로토콜만 의존, AuthenticationServices/UIKit 의존 제거
- MockAppleAuthService 추가, 테스트 DI 대칭화

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request transitions the application's authentication flow from a custom ID/password and phone verification system to social login using Kakao and Apple. It introduces Kakao and Apple SDK integrations, updates project configurations and entitlements, and refactors the login and signup views and view models to handle social tokens, providers, and user agreements. The review feedback highlights several key improvements: ensuring the signup loading state is properly reset on failure, marking the AppleSignInManager as @MainActor to prevent concurrency data races, improving type safety when handling login errors, and correcting typos in the SignupStep enum.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 139 to 140
signupTask = Task {
do {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

회원가입 Task가 취소되거나 에러가 발생했을 때 isLoading 상태가 true로 유지되는 버그가 발생할 수 있습니다. Task 시작 부분에 defer { isLoading = false }를 추가하여 어떤 경로로든 Task가 종료될 때 isLoading이 항상 false로 리셋되도록 보장하는 것이 안전합니다.

        signupTask = Task {
            defer { isLoading = false }
            do {

}

/// ASAuthorizationController를 async로 감싸기 위한 델리게이트 래퍼
private final class AppleSignInManager: NSObject {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

AppleSignInManager는 completion 및 controller와 같은 가변 상태를 가지고 있으며, AppleAuthService(MainActor)에서 생성되어 메인 스레드에서 콜백을 받습니다. 데이터 경쟁(Data Race)을 방지하고 Swift 6의 엄격한 동시성(Strict Concurrency) 검사를 안전하게 통과하기 위해 클래스 자체를 @mainactor로 지정하는 것이 안전합니다.

Suggested change
private final class AppleSignInManager: NSObject {
@MainActor
private final class AppleSignInManager: NSObject {

Comment on lines +106 to 118
private func handle(_ error: Error) {
switch error {
case LoginError.cancelled:
// 사용자가 취소 — 조용히 무시
break
case LoginError.missingIDToken:
currentError = .userMessage(LoginError.missingIDToken.localizedDescription)
case LoginError.networkError(let underlying):
handleError(underlying, feature: "login", operation: "socialLogin")
default:
handleError(error, feature: "login", operation: "socialLogin")
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

existential Error 타입을 직접 switch문으로 패턴 매칭하는 것보다, LoginError로 다운캐스팅한 후 switch문을 사용하는 것이 타입 안전성 측면에서 더 안전하고 명확합니다.

    private func handle(_ error: Error) {
        if let loginError = error as? LoginError {
            switch loginError {
            case .cancelled:
                // 사용자가 취소 — 조용히 무시
                break
            case .missingIDToken:
                currentError = .userMessage(LoginError.missingIDToken.localizedDescription)
            case .networkError(let underlying):
                handleError(underlying, feature: "login", operation: "socialLogin")
            }
        } else {
            handleError(error, feature: "login", operation: "socialLogin")
        }
    }

case complete = 2
case recommend = 3
case myIndustry = 4
case indutryList = 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

SignupStep enum의 indutryList 케이스에 오타가 있습니다. industryList로 수정하여 가독성과 유지보수성을 높이는 것이 좋습니다. (파일 내의 다른 indutryList 사용처도 함께 수정해야 합니다.)

Suggested change
case indutryList = 5
case industryList = 5

case .enterProfile: return 0.5
case .agreeTerms: return 0.9
case .myIndustry: return 0.3
case .indutryList: return 0.7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

SignupStep enum의 indutryList 케이스에 오타가 있습니다. industryList로 수정하여 가독성과 유지보수성을 높이는 것이 좋습니다.

Suggested change
case .indutryList: return 0.7
case .industryList: return 0.7

develop의 네비게이션 리팩토링(탭별 Router/Coordinator, AppRoute 제거)·
keychain 전파·Crashlytics·CI 게이트를 수용하고, 그 위로 소셜 로그인을 재이식.

- 소셜 로그인이 이메일 로그인을 대체 (LoginView/LoginViewModel = 소셜)
- AuthRoute.signup에 (signupToken, nickname) payload 추가, AuthFlow에서 스레딩
- LoginView.onNeedSignup → router.push(.signup(token))로 신규 회원 분기
- 로그인 성공/게스트 → coordinator.finishAuth()
- 온보딩/복구/탭의 회원가입 CTA는 소셜 로그인 화면으로 진입
- PhoneVerificationView 삭제 유지 (소셜 회원가입은 회원정보입력부터 시작)
- AuthRepositoryImpl: 소셜 login/signup + keychain 저장 실패 가드 병합
- .omc 언트랙 (develop gitignore 반영)

빌드: Newdok-Workspace 성공

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gomminjae
gomminjae merged commit a2d1df5 into develop Jul 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant