From cc0f9bb778bfcd47b5945611470d0aff97897c6b Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 09:21:00 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20Apple=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=B1=85=EC=9E=84=EC=9D=84=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EB=A1=9C=20=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Service/FunctionAPIClient.swift | 1 + .../AppleAuthenticationServiceImpl.swift | 47 ++++++++----------- .../Service/FunctionAPIEndpointTests.swift | 42 +++++++++++++++-- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/Application/Infra/Sources/Service/FunctionAPIClient.swift b/Application/Infra/Sources/Service/FunctionAPIClient.swift index 54812124..b27b5b1f 100644 --- a/Application/Infra/Sources/Service/FunctionAPIClient.swift +++ b/Application/Infra/Sources/Service/FunctionAPIClient.swift @@ -124,6 +124,7 @@ struct FirebaseCustomTokenResponse: Decodable { struct AppleCustomTokenRequest: Encodable { let challengeId: String let authorizationCode: String + let displayName: String? } struct AppleAccountLinkRequest: Encodable { diff --git a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift index 17f25610..4be77507 100644 --- a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift @@ -42,38 +42,18 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { let response = try await authenticateWithAppleAsync( hashedNonce: challenge.hashedNonce ) - + + let displayName = Self.makeDisplayName(from: response.fullName) + logger.debug("Requesting custom token from Firebase Function") let customToken = try await requestAppleCustomToken( challengeId: challenge.challengeId, - authorizationCode: response.authorizationCode + authorizationCode: response.authorizationCode, + displayName: displayName ) - + logger.debug("Signing in with custom token") let result = try await Auth.auth().signIn(withCustomToken: customToken) - - let changeRequest = result.user.createProfileChangeRequest() - var displayName: String? - - if let fullName = response.fullName { - let formatter = PersonNameComponentsFormatter() - formatter.style = .long - let formattedName = formatter.string(from: fullName) - if !formattedName.isEmpty { - displayName = formattedName - } - } - - if displayName == nil { - let doc = try await store - .document(FirestorePath.userData(result.user.uid, document: .info)) - .getDocument() - displayName = doc.data()?["appleName"] as? String - } - - changeRequest.displayName = displayName ?? "" - changeRequest.photoURL = nil // Apple ID 프로필 사진 URL은 제공되지 않음 - try await changeRequest.commitChanges() logger.info("Successfully signed in with Apple") return result.user.makeResponse(providerID: .apple) @@ -200,6 +180,15 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { return request } + static func makeDisplayName(from fullName: PersonNameComponents?) -> String? { + guard let fullName else { return nil } + + let formatter = PersonNameComponentsFormatter() + formatter.style = .long + let displayName = formatter.string(from: fullName) + return displayName.isEmpty ? nil : displayName + } + @MainActor private func completeAppleSignIn(with result: Result) { guard let continuation = appleSignInContinuation else { return } @@ -224,13 +213,15 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { private func requestAppleCustomToken( challengeId: String, - authorizationCode: String + authorizationCode: String, + displayName: String? ) async throws -> String { let response = try await FunctionAPIClient.shared.send( .requestAppleCustomToken, payload: AppleCustomTokenRequest( challengeId: challengeId, - authorizationCode: authorizationCode + authorizationCode: authorizationCode, + displayName: displayName ), requiresAuthentication: false ) diff --git a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift index ad7a1844..97d4edc8 100644 --- a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift +++ b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift @@ -52,16 +52,52 @@ struct FunctionAPIEndpointTests { #expect(endpoint.path == "/auth/apple/access-token") } - @Test("Apple custom token 요청은 challenge와 authorization code만 인코딩한다") - func Apple_custom_token_요청은_challenge와_authorization_code만_인코딩한다() throws { + @Test("Apple custom token 요청은 display name을 포함할 수 있다") + func Apple_custom_token_요청은_display_name을_포함할_수_있다() throws { let request = AppleCustomTokenRequest( challengeId: "challenge-id", - authorizationCode: "authorization-code" + authorizationCode: "authorization-code", + displayName: "Apple User" + ) + + #expect( + try encodedKeys(request) == [ + "challengeId", + "authorizationCode", + "displayName" + ] + ) + } + + @Test("Apple custom token 요청은 없는 display name을 인코딩하지 않는다") + func Apple_custom_token_요청은_없는_display_name을_인코딩하지_않는다() throws { + let request = AppleCustomTokenRequest( + challengeId: "challenge-id", + authorizationCode: "authorization-code", + displayName: nil ) #expect(try encodedKeys(request) == ["challengeId", "authorizationCode"]) } + @Test("Apple 이름은 custom token 요청용 display name으로 변환한다") + func Apple_이름은_custom_token_요청용_display_name으로_변환한다() { + var fullName = PersonNameComponents() + fullName.givenName = "Apple" + fullName.familyName = "User" + + #expect(AppleAuthenticationServiceImpl.makeDisplayName(from: fullName) == "Apple User") + } + + @Test("비어 있는 Apple 이름은 display name으로 변환하지 않는다") + func 비어_있는_Apple_이름은_display_name으로_변환하지_않는다() { + #expect( + AppleAuthenticationServiceImpl.makeDisplayName( + from: PersonNameComponents() + ) == nil + ) + } + @Test("Apple 계정 연결 요청은 credential email을 포함할 수 있다") func Apple_계정_연결_요청은_credential_email을_포함할_수_있다() throws { let request = AppleAccountLinkRequest( From 520c139600a22c4206afb95251843e05201cbbe5 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 09:28:05 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20Apple=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=EC=9D=98=20static=20=EC=9D=98=EC=A1=B4=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppleAuthenticationServiceImpl.swift | 20 +++++++++---------- .../Service/FunctionAPIEndpointTests.swift | 8 ++------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift index 4be77507..2568933f 100644 --- a/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift +++ b/Application/Infra/Sources/Service/SocialLogin/AppleAuthenticationServiceImpl.swift @@ -43,7 +43,7 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { hashedNonce: challenge.hashedNonce ) - let displayName = Self.makeDisplayName(from: response.fullName) + let displayName = response.fullName?.displayName logger.debug("Requesting custom token from Firebase Function") let customToken = try await requestAppleCustomToken( @@ -180,15 +180,6 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { return request } - static func makeDisplayName(from fullName: PersonNameComponents?) -> String? { - guard let fullName else { return nil } - - let formatter = PersonNameComponentsFormatter() - formatter.style = .long - let displayName = formatter.string(from: fullName) - return displayName.isEmpty ? nil : displayName - } - @MainActor private func completeAppleSignIn(with result: Result) { guard let continuation = appleSignInContinuation else { return } @@ -230,6 +221,15 @@ final class AppleAuthenticationServiceImpl: AuthenticationService { } } +extension PersonNameComponents { + var displayName: String? { + let formatter = PersonNameComponentsFormatter() + formatter.style = .long + let name = formatter.string(from: self) + return name.isEmpty ? nil : name + } +} + private extension AppleAuthenticationServiceImpl { func mapAppleAPIError(_ error: Error) -> Error { if let emailError = error.apiEmailError { diff --git a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift index 97d4edc8..941249d5 100644 --- a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift +++ b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift @@ -86,16 +86,12 @@ struct FunctionAPIEndpointTests { fullName.givenName = "Apple" fullName.familyName = "User" - #expect(AppleAuthenticationServiceImpl.makeDisplayName(from: fullName) == "Apple User") + #expect(fullName.displayName == "Apple User") } @Test("비어 있는 Apple 이름은 display name으로 변환하지 않는다") func 비어_있는_Apple_이름은_display_name으로_변환하지_않는다() { - #expect( - AppleAuthenticationServiceImpl.makeDisplayName( - from: PersonNameComponents() - ) == nil - ) + #expect(PersonNameComponents().displayName == nil) } @Test("Apple 계정 연결 요청은 credential email을 포함할 수 있다") From a0b6e528e8bfe453ee7cf94c5d5fab86a2d0b4f5 Mon Sep 17 00:00:00 2001 From: opficdev Date: Mon, 13 Jul 2026 13:41:10 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test:=20Apple=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9D=98=20?= =?UTF-8?q?=EB=A1=9C=EC=BC=80=EC=9D=BC=20=EC=9D=98=EC=A1=B4=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 --- Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift index 941249d5..15e65d69 100644 --- a/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift +++ b/Application/Infra/Tests/Service/FunctionAPIEndpointTests.swift @@ -84,9 +84,8 @@ struct FunctionAPIEndpointTests { func Apple_이름은_custom_token_요청용_display_name으로_변환한다() { var fullName = PersonNameComponents() fullName.givenName = "Apple" - fullName.familyName = "User" - #expect(fullName.displayName == "Apple User") + #expect(fullName.displayName == "Apple") } @Test("비어 있는 Apple 이름은 display name으로 변환하지 않는다")