From a619ac838053998b3f82d3ab028ee3dbc9d13a3a Mon Sep 17 00:00:00 2001 From: opficdev Date: Wed, 8 Apr 2026 23:21:43 +0900 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20=EA=B9=83=ED=97=99=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=B5=9C=EC=B4=88=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=8B=A4=ED=8C=A8=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=20/user/emails=20=EB=A1=9C=20=ED=95=9C=EB=B2=88=20=EB=8D=94=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=ED=95=B4=EC=84=9C=20=EB=B0=9B=EC=95=84?= =?UTF-8?q?=EC=99=80=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GithubAuthenticationService.swift | 43 ++++++++++- Firebase/functions/src/auth/github.ts | 76 ++++++++++++++----- 2 files changed, 97 insertions(+), 22 deletions(-) diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index 869c4ee7..a095cb0f 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -251,7 +251,42 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } let decoder = JSONDecoder() - return try decoder.decode(GitHubUser.self, from: data) + let gitHubUser = try decoder.decode(GitHubUser.self, from: data) + + if gitHubUser.email != nil { + return gitHubUser + } + + let email = try await requestPrimaryVerifiedEmail(accessToken: accessToken) + return GitHubUser( + login: gitHubUser.login, + name: gitHubUser.name, + avatarURL: gitHubUser.avatarURL, + email: email + ) + } + + func requestPrimaryVerifiedEmail(accessToken: String) async throws -> String? { + var request = URLRequest(url: URL(string: "https://api.github.com/user/emails")!) + request.httpMethod = "GET" + request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + request.addValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept") + + let (data, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw URLError(.badServerResponse) + } + + let decoder = JSONDecoder() + let gitHubEmails = try decoder.decode([GitHubEmail].self, from: data) + + if let primaryVerifiedEmail = gitHubEmails.first(where: { $0.primary && $0.verified }) { + return primaryVerifiedEmail.email + } + + return gitHubEmails.first(where: { $0.verified })?.email } } @@ -274,4 +309,10 @@ extension GithubAuthenticationService: ASWebAuthenticationPresentationContextPro } } + struct GitHubEmail: Codable { + let email: String + let primary: Bool + let verified: Bool + } + } diff --git a/Firebase/functions/src/auth/github.ts b/Firebase/functions/src/auth/github.ts index d1fc7b05..beb34fd6 100644 --- a/Firebase/functions/src/auth/github.ts +++ b/Firebase/functions/src/auth/github.ts @@ -2,6 +2,30 @@ import {onCall, HttpsError} from "firebase-functions/v2/https"; import * as admin from "firebase-admin"; import axios from "axios"; +// GitHub OAuth 응답 타입 정의 +interface GitHubOAuthResponse { + access_token: string; + token_type: string; + scope: string; + error?: string; +} + +// GitHub 사용자 정보 응답 타입 정의 +interface GitHubUser { + id: number; + login: string; + name?: string; + email?: string; + avatar_url?: string; +} + +// GitHub 이메일 목록 응답 타입 정의 +interface GitHubEmail { + email: string; + primary: boolean; + verified: boolean; +} + // GitHub OAuth 인증 및 커스텀 토큰 발급 함수 export const requestGithubTokens = onCall({ cors: true, @@ -23,14 +47,6 @@ export const requestGithubTokens = onCall({ throw new HttpsError('internal', 'GitHub 환경 설정이 누락되었습니다.'); } - // GitHub OAuth 응답 타입 정의 - interface GitHubOAuthResponse { - access_token: string; - token_type: string; - scope: string; - error?: string; - } - // 1. GitHub OAuth 토큰 획득 const tokenResponse = await axios.post ('https://github.com/login/oauth/access_token', { @@ -48,15 +64,6 @@ export const requestGithubTokens = onCall({ const accessToken = tokenData.access_token; - // GitHub 사용자 정보 응답 타입 정의 - interface GitHubUser { - id: number; - login: string; - name?: string; - email?: string; - avatar_url?: string; - } - // 2. GitHub 사용자 정보 가져오기 const userResponse = await axios.get('https://api.github.com/user', { headers: { @@ -65,7 +72,9 @@ export const requestGithubTokens = onCall({ }); const userData = userResponse.data; - if (!userData.id || !userData.email) { + const email = await resolveGitHubEmail(accessToken, userData.email); + + if (!userData.id || !email) { throw new HttpsError('internal', 'GitHub 사용자 데이터를 가져오지 못했습니다.'); } @@ -73,14 +82,14 @@ export const requestGithubTokens = onCall({ let uid; try { - const userRecord = await admin.auth().getUserByEmail(userData.email); + const userRecord = await admin.auth().getUserByEmail(email); uid = userRecord.uid; // 기존 UID 사용 - console.log(`이메일(${userData.email})로 기존 사용자를 찾았습니다.`); + console.log(`이메일(${email})로 기존 사용자를 찾았습니다.`); } catch (error) { // 사용자가 없으면 Firebase에 새 사용자 생성 const userRecord = await admin.auth().createUser({ displayName: userData.name || userData.login, - email: userData.email, + email, photoURL: userData.avatar_url, }); uid = userRecord.uid; // 새로 생성된 UID 사용 @@ -101,6 +110,31 @@ export const requestGithubTokens = onCall({ } }); +async function resolveGitHubEmail( + accessToken: string, + profileEmail?: string +): Promise { + if (profileEmail) { + return profileEmail; + } + + const emailResponse = await axios.get('https://api.github.com/user/emails', { + headers: { + 'Authorization': `token ${accessToken}` + } + }); + + const primaryVerifiedEmail = emailResponse.data.find((item) => + item.primary && item.verified + )?.email + + if (primaryVerifiedEmail) { + return primaryVerifiedEmail; + } + + return emailResponse.data.find((item) => item.verified)?.email; +} + export const revokeGithubAccessToken = onCall({ cors: true, maxInstances: 3, From 9d02aab0bef610e20c9344223d5d06b97cf77607 Mon Sep 17 00:00:00 2001 From: opficdev Date: Wed, 8 Apr 2026 23:45:47 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EB=B0=9B=EC=A7=80=20=EB=AA=BB=ED=96=88=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=97=90=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A0=A4=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Firebase/functions/src/auth/github.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Firebase/functions/src/auth/github.ts b/Firebase/functions/src/auth/github.ts index beb34fd6..b6eebf1a 100644 --- a/Firebase/functions/src/auth/github.ts +++ b/Firebase/functions/src/auth/github.ts @@ -26,6 +26,8 @@ interface GitHubEmail { verified: boolean; } +const GITHUB_EMAIL_UNAVAILABLE_REASON = "email_not_found"; + // GitHub OAuth 인증 및 커스텀 토큰 발급 함수 export const requestGithubTokens = onCall({ cors: true, @@ -75,7 +77,11 @@ export const requestGithubTokens = onCall({ const email = await resolveGitHubEmail(accessToken, userData.email); if (!userData.id || !email) { - throw new HttpsError('internal', 'GitHub 사용자 데이터를 가져오지 못했습니다.'); + throw new HttpsError( + 'internal', + 'GitHub 사용자 데이터를 가져오지 못했습니다.', + { reason: GITHUB_EMAIL_UNAVAILABLE_REASON } + ); } // 3. Firebase에서 GitHub 제공자로 사용자를 찾거나 생성 @@ -106,6 +112,9 @@ export const requestGithubTokens = onCall({ }; } catch (error) { console.error('GitHub 커스텀 토큰 생성 오류:', error); + if (error instanceof HttpsError) { + throw error; + } throw new HttpsError('internal', error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.'); } }); From 3754ee42a21619bf3a22b0f2deeb68ca8db330b3 Mon Sep 17 00:00:00 2001 From: opficdev Date: Wed, 8 Apr 2026 23:46:20 +0900 Subject: [PATCH 3/9] =?UTF-8?q?refactor:=20=EB=B0=9B=EC=9D=80=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=BD=94=EB=93=9C=EB=A5=BC=20=EB=B3=B4=EA=B3=A0=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=9B=84=20=ED=95=B4=EB=8B=B9=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=96=BC=EB=9F=BF=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=9D=84=EC=9A=B0=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Infra/Common/InfraLayerError.swift | 9 +++++ .../GithubAuthenticationService.swift | 31 +++++++++++---- .../ViewModel/LoginViewModel.swift | 38 ++++++++++++++++--- DevLog/Resource/Localizable.xcstrings | 34 +++++++++++++++++ 4 files changed, 99 insertions(+), 13 deletions(-) diff --git a/DevLog/Infra/Common/InfraLayerError.swift b/DevLog/Infra/Common/InfraLayerError.swift index 4bbd9c67..43a86bb4 100644 --- a/DevLog/Infra/Common/InfraLayerError.swift +++ b/DevLog/Infra/Common/InfraLayerError.swift @@ -27,6 +27,15 @@ enum UIError: Error { enum EmailFetchError: Error, Equatable { case emailNotFound case emailMismatch + + var code: String { + switch self { + case .emailMismatch: + "email_mismatch" + case .emailNotFound: + "email_not_found" + } + } } enum SocialLoginError: Error { diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index a095cb0f..3105b5e1 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -214,14 +214,19 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { // Firebase Function 호출: Custom Token 발급 func requestTokens(authorizationCode: String) async throws -> (String, String) { let requestTokenFunction = functions.httpsCallable(FunctionName.requestGithubTokens) - let result = try await requestTokenFunction.call(["code": authorizationCode]) - - if let data = result.data as? [String: Any], - let accessToken = data["accessToken"] as? String, - let customToken = data["customToken"] as? String { - return (accessToken, customToken) + + do { + let result = try await requestTokenFunction.call(["code": authorizationCode]) + + if let data = result.data as? [String: Any], + let accessToken = data["accessToken"] as? String, + let customToken = data["customToken"] as? String { + return (accessToken, customToken) + } + throw URLError(.badServerResponse) + } catch { + throw mapRequestTokensError(error) } - throw URLError(.badServerResponse) } func revokeAccessToken(accessToken: String? = nil) async throws { @@ -288,6 +293,18 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { return gitHubEmails.first(where: { $0.verified })?.email } + + func mapRequestTokensError(_ error: Error) -> Error { + let nsError = error as NSError + guard nsError.domain == FunctionsErrorDomain, + let details = nsError.userInfo[FunctionsErrorDetailsKey] as? [String: Any], + let reason = details["reason"] as? String, + reason == EmailFetchError.emailNotFound.code else { + return error + } + + return EmailFetchError.emailNotFound + } } extension GithubAuthenticationService: ASWebAuthenticationPresentationContextProviding { diff --git a/DevLog/Presentation/ViewModel/LoginViewModel.swift b/DevLog/Presentation/ViewModel/LoginViewModel.swift index 98470d49..a5a41507 100644 --- a/DevLog/Presentation/ViewModel/LoginViewModel.swift +++ b/DevLog/Presentation/ViewModel/LoginViewModel.swift @@ -12,12 +12,13 @@ final class LoginViewModel: Store { struct State: Equatable { var isLoading = false var showAlert: Bool = false + var alertType: AlertType? var alertTitle: String = "" var alertMessage: String = "" } enum Action { - case setAlert(Bool) + case setAlert(Bool, AlertType? = nil) case tapSignInButton(AuthProvider) case setLoading(Bool) } @@ -26,6 +27,11 @@ final class LoginViewModel: Store { case signIn(AuthProvider) } + enum AlertType { + case emailUnavailable + case error + } + private let signInUseCase: SignInUseCase private let loadingState = LoadingState() @@ -42,8 +48,8 @@ final class LoginViewModel: Store { var effects: [SideEffect] = [] switch action { - case .setAlert(let isPresented): - setAlert(&state, isPresented: isPresented) + case .setAlert(let isPresented, let alertType): + setAlert(&state, isPresented: isPresented, alertType: alertType) case .tapSignInButton(let authProvider): effects = [.signIn(authProvider)] case .setLoading(let value): @@ -64,7 +70,7 @@ final class LoginViewModel: Store { try await self.signInUseCase.execute(authProvider) } catch { if error.isSocialLoginCancelled { return } - send(.setAlert(true)) + send(.setAlert(true, alertType(for: error))) } } } @@ -75,10 +81,30 @@ private extension LoginViewModel { func setAlert( _ state: inout State, isPresented: Bool, + alertType: AlertType?, ) { - state.alertTitle = String(localized: "common_error_title") - state.alertMessage = String(localized: "common_error_message") + switch alertType { + case .emailUnavailable: + state.alertTitle = String(localized: "login_alert_email_unavailable_title") + state.alertMessage = String(localized: "login_alert_email_unavailable_message") + case .error: + state.alertTitle = String(localized: "common_error_title") + state.alertMessage = String(localized: "common_error_message") + case .none: + state.alertTitle = "" + state.alertMessage = "" + } state.showAlert = isPresented + state.alertType = alertType + } + + func alertType(for error: Error) -> AlertType { + if let emailFetchError = error as? EmailFetchError, + emailFetchError == .emailNotFound { + return .emailUnavailable + } + + return .error } func beginLoading(_ mode: LoadingState.Mode) { diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index 4a057543..e3a4e65f 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -537,6 +537,40 @@ } } }, + "login_alert_email_unavailable_message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The selected GitHub account's email could not be verified, so sign in could not be completed. Check the GitHub account settings and try again." + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "선택한 GitHub 계정의 이메일 정보를 확인할 수 없어 로그인할 수 없어요. GitHub 계정 설정을 확인한 뒤 다시 시도해주세요." + } + } + } + }, + "login_alert_email_unavailable_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to Verify Email" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이메일 확인 불가" + } + } + } + }, "login_google_sign_in" : { "extractionState" : "manual", "localizations" : { From 17ac5345eaadca0be072f04d631039920ec8cd18 Mon Sep 17 00:00:00 2001 From: opficdev Date: Wed, 8 Apr 2026 23:48:38 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Infra/Common/InfraLayerError.swift | 4 ++++ .../Service/SocialLogin/GithubAuthenticationService.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DevLog/Infra/Common/InfraLayerError.swift b/DevLog/Infra/Common/InfraLayerError.swift index 43a86bb4..a11af19c 100644 --- a/DevLog/Infra/Common/InfraLayerError.swift +++ b/DevLog/Infra/Common/InfraLayerError.swift @@ -38,6 +38,10 @@ enum EmailFetchError: Error, Equatable { } } +enum TokenError: Error { + case invalidResponse +} + enum SocialLoginError: Error { case invalidOAuthState case failedToStartWebAuthenticationSession diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index 3105b5e1..a5a17381 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -223,7 +223,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { let customToken = data["customToken"] as? String { return (accessToken, customToken) } - throw URLError(.badServerResponse) + throw TokenError.invalidResponse } catch { throw mapRequestTokensError(error) } From 49ab9c9c65a865b46208f47b7ecd920c30b5277a Mon Sep 17 00:00:00 2001 From: opficdev Date: Wed, 8 Apr 2026 23:52:12 +0900 Subject: [PATCH 5/9] =?UTF-8?q?docs:=20=EC=95=B1=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EC=96=B4=20=EB=A7=81=ED=81=AC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1b72ef7b..ae2c3302 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,11 @@ ## 앱 사용해보기 -iOS 17 이상 환경에서 TestFlight 베타 테스트 가능 +iOS 17 이상 환경에서 App Store에서 다운로드 가능 + + + - - - ## 프로젝트 개요 From 30ede479347e05b342ee43861d3eb3943f8eef46 Mon Sep 17 00:00:00 2001 From: opficdev Date: Wed, 8 Apr 2026 23:55:52 +0900 Subject: [PATCH 6/9] =?UTF-8?q?docs:=20=EC=98=81=EC=96=B4=20l10n=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Resource/Localizable.xcstrings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DevLog/Resource/Localizable.xcstrings b/DevLog/Resource/Localizable.xcstrings index e3a4e65f..62d9b01d 100644 --- a/DevLog/Resource/Localizable.xcstrings +++ b/DevLog/Resource/Localizable.xcstrings @@ -125,7 +125,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Email Unavailable" + "value" : "Unable to Verify Email" } }, "ko" : { From fb58eea7a13f4984e0fbb34fead2ca302640242c Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 9 Apr 2026 00:02:56 +0900 Subject: [PATCH 7/9] =?UTF-8?q?chore:=20=EB=B2=84=EC=A0=84=201.0=20->=201.?= =?UTF-8?q?0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DevLog.xcodeproj/project.pbxproj b/DevLog.xcodeproj/project.pbxproj index 6342b918..d2bdc23d 100644 --- a/DevLog.xcodeproj/project.pbxproj +++ b/DevLog.xcodeproj/project.pbxproj @@ -355,7 +355,7 @@ "@executable_path/Frameworks", ); LOCALIZED_STRING_SWIFTUI_SUPPORT = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -402,7 +402,7 @@ "@executable_path/Frameworks", ); LOCALIZED_STRING_SWIFTUI_SUPPORT = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 83ef7a39cfcae78109a16a326a77c51e2236e6b0 Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 9 Apr 2026 00:17:53 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20=EA=B0=95=EC=A0=9C=20=EC=98=B5?= =?UTF-8?q?=EC=85=94=EB=84=90=20=EC=96=B8=EB=9E=98=ED=95=91=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 --- .../SocialLogin/GithubAuthenticationService.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index a5a17381..959edd04 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -243,7 +243,11 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { // GitHub API로 사용자 프로필 정보 가져오기 func requestUserProfile(accessToken: String) async throws -> GitHubUser { - var request = URLRequest(url: URL(string: "https://api.github.com/user")!) + guard let url = URL(string: "https://api.github.com/user") else { + throw URLError(.badURL) + } + + var request = URLRequest(url: url) request.httpMethod = "GET" request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") request.addValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept") @@ -272,7 +276,11 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } func requestPrimaryVerifiedEmail(accessToken: String) async throws -> String? { - var request = URLRequest(url: URL(string: "https://api.github.com/user/emails")!) + guard let url = URL(string: "https://api.github.com/user/emails") else { + throw URLError(.badURL) + } + + var request = URLRequest(url: url) request.httpMethod = "GET" request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") request.addValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept") From 2a1a27189306677cedcb71fd41e510e6b35a0fae Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 9 Apr 2026 00:25:24 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B0=8F=20=ED=83=80=EC=9E=85=20private=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GithubAuthenticationService.swift | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index 959edd04..991e5974 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -152,7 +152,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } @MainActor - func requestAuthorizationCode() async throws -> String { + private func requestAuthorizationCode() async throws -> String { guard let clientID = Bundle.main.object(forInfoDictionaryKey: "GITHUB_CLIENT_ID") as? String, let redirectURL = Bundle.main.object(forInfoDictionaryKey: "APP_REDIRECT_URL") as? String, let urlComponents = URLComponents(string: redirectURL), @@ -212,7 +212,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } // Firebase Function 호출: Custom Token 발급 - func requestTokens(authorizationCode: String) async throws -> (String, String) { + private func requestTokens(authorizationCode: String) async throws -> (String, String) { let requestTokenFunction = functions.httpsCallable(FunctionName.requestGithubTokens) do { @@ -229,7 +229,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } } - func revokeAccessToken(accessToken: String? = nil) async throws { + private func revokeAccessToken(accessToken: String? = nil) async throws { var param: [String: Any] = [:] if let accessToken = accessToken { @@ -242,7 +242,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } // GitHub API로 사용자 프로필 정보 가져오기 - func requestUserProfile(accessToken: String) async throws -> GitHubUser { + private func requestUserProfile(accessToken: String) async throws -> GitHubUser { guard let url = URL(string: "https://api.github.com/user") else { throw URLError(.badURL) } @@ -275,7 +275,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { ) } - func requestPrimaryVerifiedEmail(accessToken: String) async throws -> String? { + private func requestPrimaryVerifiedEmail(accessToken: String) async throws -> String? { guard let url = URL(string: "https://api.github.com/user/emails") else { throw URLError(.badURL) } @@ -302,7 +302,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { return gitHubEmails.first(where: { $0.verified })?.email } - func mapRequestTokensError(_ error: Error) -> Error { + private func mapRequestTokensError(_ error: Error) -> Error { let nsError = error as NSError guard nsError.domain == FunctionsErrorDomain, let details = nsError.userInfo[FunctionsErrorDetailsKey] as? [String: Any], @@ -315,11 +315,7 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } } -extension GithubAuthenticationService: ASWebAuthenticationPresentationContextProviding { - func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { - return provider.keyWindow() ?? ASPresentationAnchor() - } - +private extension GithubAuthenticationService { struct GitHubUser: Codable { let login: String let name: String? @@ -339,5 +335,10 @@ extension GithubAuthenticationService: ASWebAuthenticationPresentationContextPro let primary: Bool let verified: Bool } +} +extension GithubAuthenticationService: ASWebAuthenticationPresentationContextProviding { + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return provider.keyWindow() ?? ASPresentationAnchor() + } }