From 753875b19fd5d387dd970038bd84af5f94a6b082 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 18:01:07 +0900 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20API?= =?UTF-8?q?=EC=9D=98=20provider=20=EC=97=B0=EA=B2=B0=20=EC=B6=A9=EB=8F=8C?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Infra/Sources/Service/FunctionAPIClient.swift | 11 ++--------- .../AppleAuthenticationServiceImpl.swift | 8 +++----- .../Tests/Service/FunctionAPIEndpointTests.swift | 15 ++++----------- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/Application/Infra/Sources/Service/FunctionAPIClient.swift b/Application/Infra/Sources/Service/FunctionAPIClient.swift index b27b5b1f..5d9a502c 100644 --- a/Application/Infra/Sources/Service/FunctionAPIClient.swift +++ b/Application/Infra/Sources/Service/FunctionAPIClient.swift @@ -148,11 +148,8 @@ private enum FunctionAPIErrorCode: String { case lastProvider = "last-provider" } -enum AppleAuthenticationAPIError: Error, Equatable { - case providerLinkConflict -} - enum AuthenticationAPIError: Error, Equatable { + case providerLinkConflict case lastProvider } @@ -175,7 +172,7 @@ struct FunctionAPIServerErrorDecoder: NXServerErrorDecoder { case .githubEmailConflict: return EmailError.githubEmailConflict case .appleProviderLinkConflict: - return AppleAuthenticationAPIError.providerLinkConflict + return AuthenticationAPIError.providerLinkConflict case .lastProvider: return AuthenticationAPIError.lastProvider } @@ -197,10 +194,6 @@ extension Error { functionAPIUnderlyingError as? EmailError } - var apiAppleAuthenticationError: AppleAuthenticationAPIError? { - functionAPIUnderlyingError as? AppleAuthenticationAPIError - } - var apiAuthenticationError: AuthenticationAPIError? { functionAPIUnderlyingError as? AuthenticationAPIError } diff --git a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift index 2568933f..b4158c14 100644 --- a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift @@ -236,13 +236,11 @@ private extension AppleAuthenticationServiceImpl { return emailError } - if error.apiAuthenticationError == .lastProvider { - return DataLayerError.failedToUnlinkLastProvider - } - - switch error.apiAppleAuthenticationError { + switch error.apiAuthenticationError { case .providerLinkConflict: return DataLayerError.linkCredentialAlreadyInUse + case .lastProvider: + return DataLayerError.failedToUnlinkLastProvider case .none: return error } diff --git a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift index 15e65d69..38950e79 100644 --- a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift +++ b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift @@ -132,16 +132,9 @@ struct FunctionAPIEndpointTests { #expect(request.requestedScopes == [.fullName, .email]) } - @Test( - "Apple 서버 오류를 앱 인증 오류 원인으로 분류한다", - arguments: [ - ("apple-provider-link-conflict", AppleAuthenticationAPIError.providerLinkConflict) - ] - ) - func Apple_서버_오류를_앱_인증_오류_원인으로_분류한다( - code: String, - expected: AppleAuthenticationAPIError - ) throws { + @Test("Apple provider 연결 충돌을 공통 인증 오류로 분류한다") + func Apple_provider_연결_충돌을_공통_인증_오류로_분류한다() throws { + let code = "apple-provider-link-conflict" let data = try JSONEncoder().encode(FunctionAPIErrorFixture(code: code)) let response = try #require( HTTPURLResponse( @@ -158,7 +151,7 @@ struct FunctionAPIEndpointTests { decoder: JSONDecoder() ) - #expect(error as? AppleAuthenticationAPIError == expected) + #expect(error as? AuthenticationAPIError == .providerLinkConflict) } @Test("마지막 provider 서버 오류를 공통 인증 오류로 분류한다") From 3509a7831a1aef5a257aca7166092d5e3ca1a582 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 18:01:41 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20OAuth=20ticket=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=A1=9C=EC=A7=81=20=EA=B3=B5=ED=86=B5=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GithubAuthenticationServiceImpl.swift | 27 ++-------------- .../OAuthAuthenticationTicketRequester.swift | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift diff --git a/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift index 554580e9..c8ea288b 100644 --- a/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift @@ -33,7 +33,7 @@ final class GithubAuthenticationServiceImpl: AuthenticationService { logger.info("Starting GitHub sign in") do { - let request = try await requestAuthenticationTicket( + let request = try await OAuthAuthenticationTicketRequester.request( endpoint: .requestGithubSignInSession, requiresAuthentication: false ) @@ -93,7 +93,7 @@ final class GithubAuthenticationServiceImpl: AuthenticationService { logger.info("Linking GitHub account for user: \(uid)") do { - let request = try await requestAuthenticationTicket( + let request = try await OAuthAuthenticationTicketRequester.request( endpoint: .requestGithubAccountLinkSession, requiresAuthentication: true ) @@ -130,29 +130,6 @@ final class GithubAuthenticationServiceImpl: AuthenticationService { } private extension GithubAuthenticationServiceImpl { - func requestAuthenticationTicket( - endpoint: FunctionAPIEndpoint, - requiresAuthentication: Bool - ) async throws -> OAuthAuthenticationTicketRequest { - let proof = OAuthWebAuthenticationProof() - let response = try await FunctionAPIClient.shared.send( - endpoint, - payload: OAuthAuthenticationSessionRequest( - appChallenge: proof.appChallenge - ), - requiresAuthentication: requiresAuthentication - ) - let callbackURL = try await OAuthWebAuthenticationSession.authenticate( - url: response.authorizationURL, - callbackURLScheme: OAuthWebAuthenticationProof.callbackURLScheme - ) - - return OAuthAuthenticationTicketRequest( - ticket: try proof.ticket(from: callbackURL), - appVerifier: proof.appVerifier - ) - } - func mapGithubAPIError(_ error: Error) -> Error { if let emailError = error.apiEmailError { return emailError diff --git a/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift b/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift new file mode 100644 index 00000000..24a349de --- /dev/null +++ b/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift @@ -0,0 +1,31 @@ +// +// OAuthAuthenticationTicketRequester.swift +// Infra +// +// Created by opfic on 7/13/26. +// + +enum OAuthAuthenticationTicketRequester { + static func request( + endpoint: FunctionAPIEndpoint, + requiresAuthentication: Bool + ) async throws -> OAuthAuthenticationTicketRequest { + let proof = OAuthWebAuthenticationProof() + let response = try await FunctionAPIClient.shared.send( + endpoint, + payload: OAuthAuthenticationSessionRequest( + appChallenge: proof.appChallenge + ), + requiresAuthentication: requiresAuthentication + ) + let callbackURL = try await OAuthWebAuthenticationSession.authenticate( + url: response.authorizationURL, + callbackURLScheme: OAuthWebAuthenticationProof.callbackURLScheme + ) + + return OAuthAuthenticationTicketRequest( + ticket: try proof.ticket(from: callbackURL), + appVerifier: proof.appVerifier + ) + } +} From e77ed0903d7f922b9ccfe09b83abd17791f54839 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 18:02:04 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20Google=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EC=9D=98=20=EC=84=9C=EB=B2=84=20callback=20=ED=9D=90=EB=A6=84?= =?UTF-8?q?=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Service/FunctionAPIClient.swift | 3 +- .../Sources/Service/FunctionAPIEndpoint.swift | 12 ++ .../GoogleAuthenticationServiceImpl.swift | 136 ++++++++---------- .../Service/FunctionAPIEndpointTests.swift | 74 ++++++++++ 4 files changed, 145 insertions(+), 80 deletions(-) diff --git a/Application/Infra/Sources/Service/FunctionAPIClient.swift b/Application/Infra/Sources/Service/FunctionAPIClient.swift index 5d9a502c..4ed24fb4 100644 --- a/Application/Infra/Sources/Service/FunctionAPIClient.swift +++ b/Application/Infra/Sources/Service/FunctionAPIClient.swift @@ -145,6 +145,7 @@ private enum FunctionAPIErrorCode: String { case emailMismatch = "email-mismatch" case githubEmailConflict = "github-email-changed-account-conflict" case appleProviderLinkConflict = "apple-provider-link-conflict" + case googleProviderLinkConflict = "google-provider-link-conflict" case lastProvider = "last-provider" } @@ -171,7 +172,7 @@ struct FunctionAPIServerErrorDecoder: NXServerErrorDecoder { return EmailError.mismatch case .githubEmailConflict: return EmailError.githubEmailConflict - case .appleProviderLinkConflict: + case .appleProviderLinkConflict, .googleProviderLinkConflict: return AuthenticationAPIError.providerLinkConflict case .lastProvider: return AuthenticationAPIError.lastProvider diff --git a/Application/Infra/Sources/Service/FunctionAPIEndpoint.swift b/Application/Infra/Sources/Service/FunctionAPIEndpoint.swift index 008153f6..070eb6d0 100644 --- a/Application/Infra/Sources/Service/FunctionAPIEndpoint.swift +++ b/Application/Infra/Sources/Service/FunctionAPIEndpoint.swift @@ -37,6 +37,9 @@ extension FunctionAPIEndpoint where Response == EmptyAPIResponse { static let revokeGithubAccessToken = Self(method: .delete, path: "/auth/github/access-token") static let linkGithubAccount = Self(method: .put, path: "/auth/github/account-link") static let unlinkGithubAccount = Self(method: .delete, path: "/auth/github/account-link") + static let revokeGoogleAccessToken = Self(method: .delete, path: "/auth/google/access-token") + static let linkGoogleAccount = Self(method: .put, path: "/auth/google/account-link") + static let unlinkGoogleAccount = Self(method: .delete, path: "/auth/google/account-link") static let linkAppleAccount = Self(method: .put, path: "/auth/apple/account-link") static let unlinkAppleAccount = Self(method: .delete, path: "/auth/apple/account-link") static let revokeAppleAccessToken = Self(method: .delete, path: "/auth/apple/access-token") @@ -49,6 +52,7 @@ extension FunctionAPIEndpoint where Response == AppleChallengeResponse { extension FunctionAPIEndpoint where Response == FirebaseCustomTokenResponse { static let requestAppleCustomToken = Self(method: .post, path: "/auth/apple/custom-token") static let requestGithubCustomToken = Self(method: .post, path: "/auth/github/custom-token") + static let requestGoogleCustomToken = Self(method: .post, path: "/auth/google/custom-token") } extension FunctionAPIEndpoint where Response == OAuthAuthenticationSessionResponse { @@ -60,6 +64,14 @@ extension FunctionAPIEndpoint where Response == OAuthAuthenticationSessionRespon method: .post, path: "/auth/github/account-link-sessions" ) + static let requestGoogleSignInSession = Self( + method: .post, + path: "/auth/google/sign-in-sessions" + ) + static let requestGoogleAccountLinkSession = Self( + method: .post, + path: "/auth/google/account-link-sessions" + ) } private func functionAPIPathSegment(_ value: String) -> String { diff --git a/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift index 3c34ff1b..fee85924 100644 --- a/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift @@ -9,7 +9,6 @@ import FirebaseAuth import FirebaseFirestore import FirebaseMessaging import Foundation -import GoogleSignIn import Core import Data @@ -29,48 +28,34 @@ final class GoogleAuthenticationServiceImpl: AuthenticationService { private let store = FirebaseConfiguration.firestore private let messaging = Messaging.messaging() private var user: User? { Auth.auth().currentUser } - private let provider = TopViewControllerProvider() private let logger = Logger(category: "GoogleAuthService") - @MainActor func signIn() async throws -> AuthDataResponse? { logger.info("Starting Google sign in") - - guard let topViewController = provider.topViewController() else { - logger.error("Top view controller not found") - throw UIError.notFoundTopViewController - } do { - let signIn = try await GIDSignIn.sharedInstance.signIn(withPresenting: topViewController) - - guard let idToken = signIn.user.idToken?.tokenString else { - logger.error("ID token not found") - throw URLError(.badServerResponse) - } - - let accessToken = signIn.user.accessToken.tokenString - let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken) - - logger.debug("Signing in with Google credential") - let result = try await Auth.auth().signIn(with: credential) - - if let photoURL = signIn.user.profile?.imageURL(withDimension: 200) { - let changeRequest = result.user.createProfileChangeRequest() - changeRequest.photoURL = photoURL - changeRequest.displayName = signIn.user.profile?.name - - try await changeRequest.commitChanges() - } + let request = try await OAuthAuthenticationTicketRequester.request( + endpoint: .requestGoogleSignInSession, + requiresAuthentication: false + ) + let response = try await FunctionAPIClient.shared.send( + .requestGoogleCustomToken, + payload: request, + requiresAuthentication: false + ) + + logger.debug("Signing in with custom token") + let result = try await Auth.auth().signIn(withCustomToken: response.customToken) logger.info("Successfully signed in with Google") return result.user.makeResponse(providerID: .google) } catch { if error.isSocialLoginCancelled { return nil } - logger.error("Failed to sign in with Google", error: error) - record(error, code: .signIn) - throw error + let mappedError = mapGoogleAPIError(error) + logger.error("Failed to sign in with Google", error: mappedError) + record(mappedError, code: .signIn) + throw mappedError } } @@ -79,9 +64,6 @@ final class GoogleAuthenticationServiceImpl: AuthenticationService { let infoRef = store.document(FirestorePath.userData(uid, document: .tokens)) try? await infoRef.updateData(["fcmToken": FieldValue.delete()]) - GIDSignIn.sharedInstance.signOut() - try await GIDSignIn.sharedInstance.disconnect() - if messaging.fcmToken != nil { do { try await messaging.deleteToken() @@ -100,8 +82,7 @@ final class GoogleAuthenticationServiceImpl: AuthenticationService { func deleteAuth(_ uid: String) async throws { do { - GIDSignIn.sharedInstance.signOut() - try await GIDSignIn.sharedInstance.disconnect() + try await FunctionAPIClient.shared.send(.revokeGoogleAccessToken) } catch { logger.error("Failed to delete Google auth", error: error) record(error, code: .deleteAuth) @@ -109,65 +90,62 @@ final class GoogleAuthenticationServiceImpl: AuthenticationService { } } - @MainActor - func link(uid: String, email: String) async throws -> Bool { - do { - guard let topViewController = provider.topViewController() else { - throw UIError.notFoundTopViewController - } - - if GIDSignIn.sharedInstance.hasPreviousSignIn() { - GIDSignIn.sharedInstance.signOut() - } - - let signIn = try await GIDSignIn.sharedInstance.signIn(withPresenting: topViewController) - - guard let googleEmail = signIn.user.profile?.email else { - throw EmailError.notFound - } - - if googleEmail != email { - throw EmailError.mismatch - } - - guard let idToken = signIn.user.idToken?.tokenString else { - throw URLError(.badServerResponse) - } - - let accessToken = signIn.user.accessToken.tokenString - let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken) + func link(uid: String, email _: String) async throws -> Bool { + logger.info("Linking Google account for user: \(uid)") - try await user?.link(with: credential) + do { + let request = try await OAuthAuthenticationTicketRequester.request( + endpoint: .requestGoogleAccountLinkSession, + requiresAuthentication: true + ) + try await FunctionAPIClient.shared.send( + .linkGoogleAccount, + payload: request + ) + try await user?.reload() + + logger.info("Successfully linked Google account") return true } catch { if error.isSocialLoginCancelled { return false } - logger.error("Failed to link Google account", error: error) - record(error, code: .link) - if error.isFirebaseCredentialAlreadyInUse { - throw DataLayerError.linkCredentialAlreadyInUse - } - throw error + let mappedError = mapGoogleAPIError(error) + logger.error("Failed to link Google account", error: mappedError) + record(mappedError, code: .link) + throw mappedError } } func unlink(_ uid: String) async throws { do { - logger.info("Starting Google disconnect for unlink. uid: \(uid)") - GIDSignIn.sharedInstance.signOut() - try await GIDSignIn.sharedInstance.disconnect() - - logger.info("Starting Firebase Google provider unlink. uid: \(uid)") - _ = try await user?.unlink(fromProvider: AuthProviderID.google.rawValue) + logger.info("Unlinking Google account for user: \(uid)") + try await FunctionAPIClient.shared.send(.unlinkGoogleAccount) + try await user?.reload() } catch { - logger.error("Failed to unlink Google account", error: error) - record(error, code: .unlink) - throw error + let mappedError = mapGoogleAPIError(error) + logger.error("Failed to unlink Google account", error: mappedError) + record(mappedError, code: .unlink) + throw mappedError } } } private extension GoogleAuthenticationServiceImpl { + func mapGoogleAPIError(_ error: Error) -> Error { + if let emailError = error.apiEmailError { + return emailError + } + + switch error.apiAuthenticationError { + case .providerLinkConflict: + return DataLayerError.linkCredentialAlreadyInUse + case .lastProvider: + return DataLayerError.failedToUnlinkLastProvider + case .none: + return error + } + } + private static func record(_ error: Error, code: CrashlyticsError.Code) { FirebaseCrashlyticsHelper.record( error, diff --git a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift index 38950e79..fffd87d3 100644 --- a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift +++ b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift @@ -154,6 +154,29 @@ struct FunctionAPIEndpointTests { #expect(error as? AuthenticationAPIError == .providerLinkConflict) } + @Test("Google provider 연결 충돌을 공통 인증 오류로 분류한다") + func Google_provider_연결_충돌을_공통_인증_오류로_분류한다() throws { + let data = try JSONEncoder().encode( + FunctionAPIErrorFixture(code: "google-provider-link-conflict") + ) + let response = try #require( + HTTPURLResponse( + url: URL(string: "https://example.com")!, + statusCode: 409, + httpVersion: nil, + headerFields: nil + ) + ) + + let error = FunctionAPIServerErrorDecoder().decodeServerError( + data: data, + response: response, + decoder: JSONDecoder() + ) + + #expect(error as? AuthenticationAPIError == .providerLinkConflict) + } + @Test("마지막 provider 서버 오류를 공통 인증 오류로 분류한다") func 마지막_provider_서버_오류를_공통_인증_오류로_분류한다() throws { let data = try JSONEncoder().encode( @@ -236,6 +259,57 @@ struct FunctionAPIEndpointTests { #expect(endpoint.method.rawValue == "DELETE") #expect(endpoint.path == "/auth/github/account-link") } + + @Test("Google 로그인 session endpoint는 sign-in-sessions 경로를 사용한다") + func Google_로그인_session_endpoint는_sign_in_sessions_경로를_사용한다() { + let endpoint = FunctionAPIEndpoint + .requestGoogleSignInSession + + #expect(endpoint.method.rawValue == "POST") + #expect(endpoint.path == "/auth/google/sign-in-sessions") + } + + @Test("Google custom token endpoint는 custom-token 경로를 사용한다") + func Google_custom_token_endpoint는_custom_token_경로를_사용한다() { + let endpoint = FunctionAPIEndpoint + .requestGoogleCustomToken + + #expect(endpoint.method.rawValue == "POST") + #expect(endpoint.path == "/auth/google/custom-token") + } + + @Test("Google 계정 연결 session endpoint는 account-link-sessions 경로를 사용한다") + func Google_계정_연결_session_endpoint는_account_link_sessions_경로를_사용한다() { + let endpoint = FunctionAPIEndpoint + .requestGoogleAccountLinkSession + + #expect(endpoint.method.rawValue == "POST") + #expect(endpoint.path == "/auth/google/account-link-sessions") + } + + @Test("Google 계정 연결 endpoint는 PUT account-link 경로를 사용한다") + func Google_계정_연결_endpoint는_PUT_account_link_경로를_사용한다() { + let endpoint = FunctionAPIEndpoint.linkGoogleAccount + + #expect(endpoint.method.rawValue == "PUT") + #expect(endpoint.path == "/auth/google/account-link") + } + + @Test("Google 계정 연결 해제 endpoint는 DELETE account-link 경로를 사용한다") + func Google_계정_연결_해제_endpoint는_DELETE_account_link_경로를_사용한다() { + let endpoint = FunctionAPIEndpoint.unlinkGoogleAccount + + #expect(endpoint.method.rawValue == "DELETE") + #expect(endpoint.path == "/auth/google/account-link") + } + + @Test("Google access token endpoint는 DELETE access-token 경로를 사용한다") + func Google_access_token_endpoint는_DELETE_access_token_경로를_사용한다() { + let endpoint = FunctionAPIEndpoint.revokeGoogleAccessToken + + #expect(endpoint.method.rawValue == "DELETE") + #expect(endpoint.path == "/auth/google/access-token") + } } private struct FunctionAPIErrorFixture: Encodable { From abe12fc06ed04514dba08c4b8028384d72fde7de Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 18:02:25 +0900 Subject: [PATCH 4/9] =?UTF-8?q?chore:=20GoogleSignIn=20SDK=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=EA=B3=BC=20=EC=84=A4=EC=A0=95=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/App/Delegate/AppDelegate.swift | 8 -------- Application/App/Sources/Resource/Info.plist | 3 --- .../Infra/Sources/Common/InfraLayerError.swift | 3 +-- .../SocialLogin/GoogleSignInURLHandler.swift | 15 --------------- README.md | 2 +- .../Project+Packages.swift | 6 ------ 6 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 Application/Infra/Sources/Service/SocialLogin/GoogleSignInURLHandler.swift diff --git a/Application/App/Sources/App/Delegate/AppDelegate.swift b/Application/App/Sources/App/Delegate/AppDelegate.swift index 8dbd2565..2c34c8fb 100644 --- a/Application/App/Sources/App/Delegate/AppDelegate.swift +++ b/Application/App/Sources/App/Delegate/AppDelegate.swift @@ -15,14 +15,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private let logger = Logger(category: "AppDelegate") private let container = AppDIContainer.shared - func application( - _ app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - return GoogleSignInURLHandler.handle(url) - } - func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil diff --git a/Application/App/Sources/Resource/Info.plist b/Application/App/Sources/Resource/Info.plist index 84f1d58e..4ae74740 100644 --- a/Application/App/Sources/Resource/Info.plist +++ b/Application/App/Sources/Resource/Info.plist @@ -27,7 +27,6 @@ Editor CFBundleURLSchemes - $(REVERSED_CLIENT_ID) DevLog @@ -48,8 +47,6 @@ $(FIRESTORE_DATABASE_ID) FUNCTION_API_BASE_URL $(FUNCTION_API_BASE_URL) - GIDClientID - $(CLIENT_ID) ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/Application/Infra/Sources/Common/InfraLayerError.swift b/Application/Infra/Sources/Common/InfraLayerError.swift index ed9d4ca8..a475413e 100644 --- a/Application/Infra/Sources/Common/InfraLayerError.swift +++ b/Application/Infra/Sources/Common/InfraLayerError.swift @@ -43,8 +43,7 @@ extension Error { case let webAuthError as ASWebAuthenticationSessionError: return webAuthError.code == .canceledLogin default: - let nsError = self as NSError - return nsError.domain == "com.google.GIDSignIn" && nsError.code == -5 + return false } } } diff --git a/Application/Infra/Sources/Service/SocialLogin/GoogleSignInURLHandler.swift b/Application/Infra/Sources/Service/SocialLogin/GoogleSignInURLHandler.swift deleted file mode 100644 index 39ffa680..00000000 --- a/Application/Infra/Sources/Service/SocialLogin/GoogleSignInURLHandler.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// GoogleSignInURLHandler.swift -// Infra -// -// Created by opfic on 5/23/26. -// - -import Foundation -import GoogleSignIn - -public enum GoogleSignInURLHandler { - public static func handle(_ url: URL) -> Bool { - GIDSignIn.sharedInstance.handle(url) - } -} diff --git a/README.md b/README.md index 9a2cf199..df38de89 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Todo, 저장 링크, 오늘 할 일, 받은 알림, 누적 활동을 하나의 | Backend | Firebase Authentication, Firestore, Cloud Functions, Cloud Messaging | | Monitoring | Firebase Analytics, Crashlytics | | Apple Frameworks | AuthenticationServices, UserNotifications, LinkPresentation, Network, CryptoKit, os.log | -| External Packages | ComposableArchitecture, MarkdownUI, OrderedCollections, GoogleSignIn, Nexa | +| External Packages | ComposableArchitecture, MarkdownUI, OrderedCollections, Nexa | | Testing | swift-testing, TCA TestStore | | Tooling | Xcode, Tuist, mise, Swift Package Manager, SwiftLint, Fastlane | diff --git a/Tuist/ProjectDescriptionHelpers/Project+Packages.swift b/Tuist/ProjectDescriptionHelpers/Project+Packages.swift index dd115836..bde4bbf6 100644 --- a/Tuist/ProjectDescriptionHelpers/Project+Packages.swift +++ b/Tuist/ProjectDescriptionHelpers/Project+Packages.swift @@ -17,10 +17,6 @@ public enum DevLogPackages { url: "https://github.com/firebase/firebase-ios-sdk", .exact("11.15.0") ) - public static let googleSignInPackage: Package = .package( - url: "https://github.com/google/GoogleSignIn-iOS", - .revision("02616ac6b469e8f00212436d2cac16e6efad7954") - ) public static let nexaPackage: Package = .package( url: "https://github.com/opficdev/Nexa", .upToNextMinor(from: "1.1.1") @@ -40,7 +36,6 @@ public enum DevLogPackages { .package(product: "FirebaseCrashlytics"), .package(product: "FirebaseMessaging"), .package(product: "FirebaseFirestore"), - .package(product: "GoogleSignIn"), .package(product: "Nexa"), ] @@ -54,7 +49,6 @@ public enum DevLogPackages { public static let infraPackages: [Package] = [ firebasePackage, - googleSignInPackage, nexaPackage, ] } From c070122d89db781c2a7c9cb5a10508af5e04452d Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 18:17:54 +0900 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20=EA=B3=84=EC=A0=95=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=EC=9D=98=20email=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tests/PushNotification/FCMTokenSyncHandlerTests.swift | 1 - .../Tests/UserTimeZone/UserTimeZoneSyncHandlerTests.swift | 1 - Application/Data/Sources/Protocol/AuthService.swift | 1 - .../Data/Sources/Protocol/AuthenticationService.swift | 2 +- .../Data/Sources/Repository/AuthDataRepositoryImpl.swift | 5 ++--- .../Data/Tests/Repository/AuthDataRepositoryImplTests.swift | 1 - .../Tests/Repository/AuthenticationRepositoryImplTests.swift | 5 +---- .../Data/Tests/Repository/WebPageRepositoryImplTests.swift | 1 - Application/Infra/Sources/Service/AuthServiceImpl.swift | 4 ---- .../Service/SocialLogin/AppleAuthenticationServiceImpl.swift | 2 +- .../SocialLogin/GithubAuthenticationServiceImpl.swift | 2 +- .../SocialLogin/GoogleAuthenticationServiceImpl.swift | 2 +- 12 files changed, 7 insertions(+), 20 deletions(-) diff --git a/Application/App/Tests/PushNotification/FCMTokenSyncHandlerTests.swift b/Application/App/Tests/PushNotification/FCMTokenSyncHandlerTests.swift index 6c156b93..b5402c77 100644 --- a/Application/App/Tests/PushNotification/FCMTokenSyncHandlerTests.swift +++ b/Application/App/Tests/PushNotification/FCMTokenSyncHandlerTests.swift @@ -306,7 +306,6 @@ private actor UserServiceSpy: UserService { private final class AuthServiceSpy: AuthService { var uid: String? let providerIDs = [String]() - let currentUserEmail: String? = nil let providerCount = 0 private let subject = PassthroughSubject() diff --git a/Application/App/Tests/UserTimeZone/UserTimeZoneSyncHandlerTests.swift b/Application/App/Tests/UserTimeZone/UserTimeZoneSyncHandlerTests.swift index 33105332..9474cd37 100644 --- a/Application/App/Tests/UserTimeZone/UserTimeZoneSyncHandlerTests.swift +++ b/Application/App/Tests/UserTimeZone/UserTimeZoneSyncHandlerTests.swift @@ -218,7 +218,6 @@ private actor UserServiceSpy: UserService { private final class AuthServiceSpy: AuthService { var uid: String? let providerIDs = [String]() - let currentUserEmail: String? = nil let providerCount = 0 private let subject = PassthroughSubject() diff --git a/Application/Data/Sources/Protocol/AuthService.swift b/Application/Data/Sources/Protocol/AuthService.swift index 82f7d84b..789befa4 100644 --- a/Application/Data/Sources/Protocol/AuthService.swift +++ b/Application/Data/Sources/Protocol/AuthService.swift @@ -11,7 +11,6 @@ import Foundation public protocol AuthService { var uid: String? { get } var providerIDs: [String] { get } - var currentUserEmail: String? { get } var providerCount: Int { get } func observeSignedIn() -> AnyPublisher diff --git a/Application/Data/Sources/Protocol/AuthenticationService.swift b/Application/Data/Sources/Protocol/AuthenticationService.swift index 31a9845c..1d2dc1ff 100644 --- a/Application/Data/Sources/Protocol/AuthenticationService.swift +++ b/Application/Data/Sources/Protocol/AuthenticationService.swift @@ -11,6 +11,6 @@ public protocol AuthenticationService { func signIn() async throws -> AuthDataResponse? func signOut(_ uid: String) async throws func deleteAuth(_ uid: String) async throws - func link(uid: String, email: String) async throws -> Bool + func link(uid: String) async throws -> Bool func unlink(_ uid: String) async throws } diff --git a/Application/Data/Sources/Repository/AuthDataRepositoryImpl.swift b/Application/Data/Sources/Repository/AuthDataRepositoryImpl.swift index c5dfc743..542d9f18 100644 --- a/Application/Data/Sources/Repository/AuthDataRepositoryImpl.swift +++ b/Application/Data/Sources/Repository/AuthDataRepositoryImpl.swift @@ -38,8 +38,7 @@ final class AuthDataRepositoryImpl: AuthDataRepository { } func linkProvider(_ provider: AuthProvider) async throws -> Bool { - guard let uid = authService.uid, - let email = authService.currentUserEmail else { + guard let uid = authService.uid else { throw AuthError.notAuthenticated } @@ -54,7 +53,7 @@ final class AuthDataRepositoryImpl: AuthDataRepository { } do { - return try await service.link(uid: uid, email: email) + return try await service.link(uid: uid) } catch { throw mapLinkError(error) } diff --git a/Application/Data/Tests/Repository/AuthDataRepositoryImplTests.swift b/Application/Data/Tests/Repository/AuthDataRepositoryImplTests.swift index d92db11e..70c6d08a 100644 --- a/Application/Data/Tests/Repository/AuthDataRepositoryImplTests.swift +++ b/Application/Data/Tests/Repository/AuthDataRepositoryImplTests.swift @@ -89,7 +89,6 @@ struct AuthDataRepositoryImplTests { uid: "user-id", providerID: "apple.com", providerIDs: ["apple.com", "google.com"], - currentUserEmail: "apple@example.com", providerCount: providerCount, events: events ) diff --git a/Application/Data/Tests/Repository/AuthenticationRepositoryImplTests.swift b/Application/Data/Tests/Repository/AuthenticationRepositoryImplTests.swift index 4739ca26..6a5a0b10 100644 --- a/Application/Data/Tests/Repository/AuthenticationRepositoryImplTests.swift +++ b/Application/Data/Tests/Repository/AuthenticationRepositoryImplTests.swift @@ -175,14 +175,12 @@ final class AuthenticationRepositoryAuthServiceSpy: AuthService { var uid: String? let providerIDs: [String] - let currentUserEmail: String? let providerCount: Int init( uid: String?, providerID: String?, providerIDs: [String], - currentUserEmail: String? = nil, providerCount: Int? = nil, deleteCurrentUserError: Error? = nil, events: AuthenticationRepositoryEventRecorder @@ -190,7 +188,6 @@ final class AuthenticationRepositoryAuthServiceSpy: AuthService { self.uid = uid self.providerID = providerID self.providerIDs = providerIDs - self.currentUserEmail = currentUserEmail self.providerCount = providerCount ?? providerIDs.count self.deleteCurrentUserError = deleteCurrentUserError self.events = events @@ -263,7 +260,7 @@ actor AuthenticationServiceSpy: AuthenticationService { events.record("\(provider).deleteAuth") } - func link(uid: String, email: String) async throws -> Bool { + func link(uid: String) async throws -> Bool { events.record("\(provider).link") return try linkResult.get() } diff --git a/Application/Data/Tests/Repository/WebPageRepositoryImplTests.swift b/Application/Data/Tests/Repository/WebPageRepositoryImplTests.swift index 01c0396f..efb8869f 100644 --- a/Application/Data/Tests/Repository/WebPageRepositoryImplTests.swift +++ b/Application/Data/Tests/Repository/WebPageRepositoryImplTests.swift @@ -131,7 +131,6 @@ private final class WebPageRepositoryAuthServiceSpy: AuthService { var uid: String? var providerIDs: [String] { [] } - var currentUserEmail: String? { nil } var providerCount: Int { 0 } init(uid: String?) { diff --git a/Application/Infra/Sources/Service/AuthServiceImpl.swift b/Application/Infra/Sources/Service/AuthServiceImpl.swift index b635a46b..b04a6e32 100644 --- a/Application/Infra/Sources/Service/AuthServiceImpl.swift +++ b/Application/Infra/Sources/Service/AuthServiceImpl.swift @@ -39,10 +39,6 @@ final class AuthServiceImpl: AuthService { Auth.auth().currentUser?.providerData.map { $0.providerID } ?? [] } - var currentUserEmail: String? { - Auth.auth().currentUser?.email - } - var providerCount: Int { Auth.auth().currentUser?.providerData.count ?? 0 } diff --git a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift index b4158c14..2cc9b4e1 100644 --- a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift @@ -98,7 +98,7 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { } } - func link(uid: String, email _: String) async throws -> Bool { + func link(uid: String) async throws -> Bool { do { logger.info("Linking Apple account for user: \(uid)") let challenge = try await requestAppleChallenge() diff --git a/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift index c8ea288b..a616dee6 100644 --- a/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/GithubAuthenticationServiceImpl.swift @@ -89,7 +89,7 @@ final class GithubAuthenticationServiceImpl: AuthenticationService { } } - func link(uid: String, email _: String) async throws -> Bool { + func link(uid: String) async throws -> Bool { logger.info("Linking GitHub account for user: \(uid)") do { diff --git a/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift index fee85924..21f4c517 100644 --- a/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift @@ -90,7 +90,7 @@ final class GoogleAuthenticationServiceImpl: AuthenticationService { } } - func link(uid: String, email _: String) async throws -> Bool { + func link(uid: String) async throws -> Bool { logger.info("Linking Google account for user: \(uid)") do { From 46489f1e13d902720a0f274e45fc8e1d9cdca346 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 18:31:44 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=20OAuth=20ticket=20=EA=B5=90?= =?UTF-8?q?=ED=99=98=20=EC=B2=98=EB=A6=AC=20=ED=83=80=EC=9E=85=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuthAuthenticationTicketRequester.swift | 10 +++---- ...swift => OAuthTicketExchangeHandler.swift} | 6 ++--- ... => OAuthTicketExchangeHandlerTests.swift} | 26 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) rename Application/Infra/Sources/Service/SocialLogin/{OAuthWebAuthenticationProof.swift => OAuthTicketExchangeHandler.swift} (93%) rename Application/Infra/Tests/Service/{OAuthWebAuthenticationProofTests.swift => OAuthTicketExchangeHandlerTests.swift} (63%) diff --git a/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift b/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift index 24a349de..4281b588 100644 --- a/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift +++ b/Application/Infra/Sources/Service/SocialLogin/OAuthAuthenticationTicketRequester.swift @@ -10,22 +10,22 @@ enum OAuthAuthenticationTicketRequester { endpoint: FunctionAPIEndpoint, requiresAuthentication: Bool ) async throws -> OAuthAuthenticationTicketRequest { - let proof = OAuthWebAuthenticationProof() + let handler = OAuthTicketExchangeHandler() let response = try await FunctionAPIClient.shared.send( endpoint, payload: OAuthAuthenticationSessionRequest( - appChallenge: proof.appChallenge + appChallenge: handler.appChallenge ), requiresAuthentication: requiresAuthentication ) let callbackURL = try await OAuthWebAuthenticationSession.authenticate( url: response.authorizationURL, - callbackURLScheme: OAuthWebAuthenticationProof.callbackURLScheme + callbackURLScheme: OAuthTicketExchangeHandler.callbackURLScheme ) return OAuthAuthenticationTicketRequest( - ticket: try proof.ticket(from: callbackURL), - appVerifier: proof.appVerifier + ticket: try handler.ticket(from: callbackURL), + appVerifier: handler.appVerifier ) } } diff --git a/Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationProof.swift b/Application/Infra/Sources/Service/SocialLogin/OAuthTicketExchangeHandler.swift similarity index 93% rename from Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationProof.swift rename to Application/Infra/Sources/Service/SocialLogin/OAuthTicketExchangeHandler.swift index a73b6880..3ad4c97d 100644 --- a/Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationProof.swift +++ b/Application/Infra/Sources/Service/SocialLogin/OAuthTicketExchangeHandler.swift @@ -1,5 +1,5 @@ // -// OAuthWebAuthenticationProof.swift +// OAuthTicketExchangeHandler.swift // Infra // // Created by opfic on 7/12/26. @@ -8,7 +8,7 @@ import CryptoKit import Foundation -struct OAuthWebAuthenticationProof { +struct OAuthTicketExchangeHandler { static let callbackURLScheme = "DevLog" let appVerifier: String @@ -44,7 +44,7 @@ struct OAuthWebAuthenticationProof { } } -private extension OAuthWebAuthenticationProof { +private extension OAuthTicketExchangeHandler { static func makeVerifier() -> String { [UUID().uuidString, UUID().uuidString] .joined() diff --git a/Application/Infra/Tests/Service/OAuthWebAuthenticationProofTests.swift b/Application/Infra/Tests/Service/OAuthTicketExchangeHandlerTests.swift similarity index 63% rename from Application/Infra/Tests/Service/OAuthWebAuthenticationProofTests.swift rename to Application/Infra/Tests/Service/OAuthTicketExchangeHandlerTests.swift index 961f671e..f70eaf94 100644 --- a/Application/Infra/Tests/Service/OAuthWebAuthenticationProofTests.swift +++ b/Application/Infra/Tests/Service/OAuthTicketExchangeHandlerTests.swift @@ -1,5 +1,5 @@ // -// OAuthWebAuthenticationProofTests.swift +// OAuthTicketExchangeHandlerTests.swift // InfraTests // // Created by opfic on 7/12/26. @@ -9,54 +9,54 @@ import Foundation import Testing @testable import Infra -struct OAuthWebAuthenticationProofTests { +struct OAuthTicketExchangeHandlerTests { @Test("app challenge는 app verifier의 SHA-256 base64url 값이다") func app_challenge는_app_verifier의_SHA_256_base64url_값이다() { - let proof = OAuthWebAuthenticationProof( + let handler = OAuthTicketExchangeHandler( appVerifier: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" ) - #expect(proof.appChallenge == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") + #expect(handler.appChallenge == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") } @Test("생성된 app verifier는 64자리 소문자 16진수다") func 생성된_app_verifier는_64자리_소문자_16진수다() { - let proof = OAuthWebAuthenticationProof() + let handler = OAuthTicketExchangeHandler() let allowedCharacters = CharacterSet(charactersIn: "0123456789abcdef") - #expect(proof.appVerifier.count == 64) - #expect(proof.appVerifier.unicodeScalars.allSatisfy(allowedCharacters.contains)) + #expect(handler.appVerifier.count == 64) + #expect(handler.appVerifier.unicodeScalars.allSatisfy(allowedCharacters.contains)) } @Test("DevLog OAuth callback에서 ticket을 추출한다") func DevLog_OAuth_callback에서_ticket을_추출한다() throws { - let proof = OAuthWebAuthenticationProof(appVerifier: "verifier") + let handler = OAuthTicketExchangeHandler(appVerifier: "verifier") let callbackURL = try #require( URL(string: "DevLog://oauth-callback?ticket=one-time-ticket") ) - #expect(try proof.ticket(from: callbackURL) == "one-time-ticket") + #expect(try handler.ticket(from: callbackURL) == "one-time-ticket") } @Test("다른 callback host는 거부한다") func 다른_callback_host는_거부한다() throws { - let proof = OAuthWebAuthenticationProof(appVerifier: "verifier") + let handler = OAuthTicketExchangeHandler(appVerifier: "verifier") let callbackURL = try #require( URL(string: "DevLog://unexpected?ticket=one-time-ticket") ) #expect(throws: SocialLoginError.self) { - try proof.ticket(from: callbackURL) + try handler.ticket(from: callbackURL) } } @Test("ticket이 없는 callback은 거부한다") func ticket이_없는_callback은_거부한다() throws { - let proof = OAuthWebAuthenticationProof(appVerifier: "verifier") + let handler = OAuthTicketExchangeHandler(appVerifier: "verifier") let callbackURL = try #require(URL(string: "DevLog://oauth-callback")) #expect(throws: SocialLoginError.self) { - try proof.ticket(from: callbackURL) + try handler.ticket(from: callbackURL) } } } From 7db0ffbf4c11a64312606ed9f0704480e321e537 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 19:30:18 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20OAuth=20=EC=9B=B9=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=9D=98=20MainActor=20=EA=B2=BD=EA=B3=84=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TopViewControllerProvider.swift | 47 ------------------- .../OAuthWebAuthenticationSession.swift | 10 ++-- 2 files changed, 7 insertions(+), 50 deletions(-) delete mode 100644 Application/Infra/Sources/Common/TopViewControllerProvider.swift diff --git a/Application/Infra/Sources/Common/TopViewControllerProvider.swift b/Application/Infra/Sources/Common/TopViewControllerProvider.swift deleted file mode 100644 index 14d47301..00000000 --- a/Application/Infra/Sources/Common/TopViewControllerProvider.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// TopViewControllerProvider.swift -// Infra -// -// Created by 최윤진 on 2/12/26. -// - -import UIKit -import Data - -final class TopViewControllerProvider { - @MainActor - func topViewController() -> UIViewController? { - guard let keyWindow = keyWindow() else { - return nil - } - - let rootController = keyWindow.rootViewController - return topViewController(controller: rootController) - } - - @MainActor - func keyWindow() -> UIWindow? { - return UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .flatMap { $0.windows } - .first { $0.isKeyWindow } - } - - @MainActor - private func topViewController(controller: UIViewController?) -> UIViewController? { - if let navigationController = controller as? UINavigationController { - return topViewController(controller: navigationController.visibleViewController) - } - - if let tabController = controller as? UITabBarController, - let selectedController = tabController.selectedViewController { - return topViewController(controller: selectedController) - } - - if let presentedController = controller?.presentedViewController { - return topViewController(controller: presentedController) - } - - return controller - } -} diff --git a/Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationSession.swift b/Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationSession.swift index 594ebde7..a98ce24b 100644 --- a/Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationSession.swift +++ b/Application/Infra/Sources/Service/SocialLogin/OAuthWebAuthenticationSession.swift @@ -7,10 +7,9 @@ import AuthenticationServices import Foundation +import UIKit -@MainActor final class OAuthWebAuthenticationSession: NSObject { - private let provider = TopViewControllerProvider() private var session: ASWebAuthenticationSession? static func authenticate( @@ -24,6 +23,7 @@ final class OAuthWebAuthenticationSession: NSObject { ) } + @MainActor private func authenticate( url: URL, callbackURLScheme: String @@ -63,7 +63,11 @@ final class OAuthWebAuthenticationSession: NSObject { } extension OAuthWebAuthenticationSession: ASWebAuthenticationPresentationContextProviding { + @MainActor func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { - provider.keyWindow() ?? ASPresentationAnchor() + UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow } ?? ASPresentationAnchor() } } From 915ba7ed79a8ecfcce001ec939740a1069d4b349 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 19:44:30 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=9D=B8=EC=A6=9D=20=EB=B3=B5?= =?UTF-8?q?=EC=9B=90=20=EA=B3=84=EC=95=BD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Repository/AuthenticationRepositoryImpl.swift | 5 ----- .../Domain/Sources/Protocol/AuthenticationRepository.swift | 1 - 2 files changed, 6 deletions(-) diff --git a/Application/Data/Sources/Repository/AuthenticationRepositoryImpl.swift b/Application/Data/Sources/Repository/AuthenticationRepositoryImpl.swift index 7dc3208f..7d405e60 100644 --- a/Application/Data/Sources/Repository/AuthenticationRepositoryImpl.swift +++ b/Application/Data/Sources/Repository/AuthenticationRepositoryImpl.swift @@ -93,11 +93,6 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { widgetSnapshotUpdater.clear() } - func restore() -> Bool { - // MARK: 후에 Google API를 사용 시 Google만의 restorePreviousSignIn 로직 추가 - return authService.uid != nil - } - func delete() async throws { guard let uid = authService.uid else { throw AuthError.notAuthenticated diff --git a/Application/Domain/Sources/Protocol/AuthenticationRepository.swift b/Application/Domain/Sources/Protocol/AuthenticationRepository.swift index a353a44e..7e8c0847 100644 --- a/Application/Domain/Sources/Protocol/AuthenticationRepository.swift +++ b/Application/Domain/Sources/Protocol/AuthenticationRepository.swift @@ -10,6 +10,5 @@ import Foundation public protocol AuthenticationRepository { func signIn(_ provider: AuthProvider) async throws -> Bool func signOut() async throws - func restore() -> Bool func delete() async throws } From cec68c9dc33768185b36ee7a1e16532724cd5558 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 22:37:49 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20=EB=A0=88?= =?UTF-8?q?=EA=B1=B0=EC=8B=9C=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Infra/Sources/Common/InfraLayerError.swift | 10 ---------- .../SocialLogin/AppleAuthenticationServiceImpl.swift | 1 - .../SocialLogin/GoogleAuthenticationServiceImpl.swift | 1 - 3 files changed, 12 deletions(-) diff --git a/Application/Infra/Sources/Common/InfraLayerError.swift b/Application/Infra/Sources/Common/InfraLayerError.swift index a475413e..bcd092d9 100644 --- a/Application/Infra/Sources/Common/InfraLayerError.swift +++ b/Application/Infra/Sources/Common/InfraLayerError.swift @@ -7,7 +7,6 @@ import AuthenticationServices import Foundation -import Data enum FirestoreError: Error, LocalizedError { case dataNotFound(_ key: String) @@ -20,16 +19,7 @@ enum FirestoreError: Error, LocalizedError { } } -enum UIError: Error { - case notFoundTopViewController -} - -enum TokenError: Error { - case invalidResponse -} - enum SocialLoginError: Error { - case invalidOAuthState case invalidOAuthCallback case failedToStartWebAuthenticationSession case authenticationAlreadyInProgress diff --git a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift index 2cc9b4e1..43bcf746 100644 --- a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift @@ -31,7 +31,6 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { private let store = FirebaseConfiguration.firestore private let messaging = Messaging.messaging() private var user: User? { Auth.auth().currentUser } - private let providerID = AuthProviderID.apple private let logger = Logger(category: "AppleAuthService") func signIn() async throws -> AuthDataResponse? { diff --git a/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift index 21f4c517..44aae388 100644 --- a/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/GoogleAuthenticationServiceImpl.swift @@ -8,7 +8,6 @@ import FirebaseAuth import FirebaseFirestore import FirebaseMessaging -import Foundation import Core import Data