From 1a7177b612f84f3ffbadc649807144f244cac850 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 17:57:57 +0900 Subject: [PATCH 01/18] =?UTF-8?q?[feat]=20#85=20signIn,=20signOut,=20resen?= =?UTF-8?q?d=20request=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Supabase/Auth/SupabaseAuthRequest.swift | 68 +++++++++++++++++++ .../Supabase/Auth/SupabaseAuthTypes.swift | 47 +++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthTypes.swift diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthRequest.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthRequest.swift index 84b5817f..154b919f 100644 --- a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthRequest.swift +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthRequest.swift @@ -9,6 +9,11 @@ import Foundation enum SupabaseAuthRequest { case signInAnonymously + case signInWithEmail(parameter: SupabaseEmailAuthParameter) + case signInWithIDToken(credentials: OpenIDConnectCredentials) + case signUpWithEmail(parameter: SupabaseEmailAuthParameter, redirectTo: String?) + case signOut(accessToken: String, scope: SignOutScope) + case resendEmail(parameter: ResendEmailParameter, redirectTo: String?) } extension SupabaseAuthRequest: SNMRequestConvertible { @@ -20,6 +25,41 @@ extension SupabaseAuthRequest: SNMRequestConvertible { path: "auth/v1/signup", method: .post ) + case .signUpWithEmail(_, let redirectTo): + return Endpoint( + baseURL: SupabaseConfig.baseURL, + path: "auth/v1/signup", + method: .post, + query: redirectTo == nil ? [:] : ["redirect_to": redirectTo!] + ) + case .signInWithEmail: + return Endpoint( + baseURL: SupabaseConfig.baseURL, + path: "auth/v1/token", + method: .post, + query: ["grant_type": "password"] + ) + case .signInWithIDToken: + return Endpoint( + baseURL: SupabaseConfig.baseURL, + path: "auth/v1/token", + method: .post, + query: ["grant_type": "id_token"] + ) + case .signOut(_, let scope): + return Endpoint( + baseURL: SupabaseConfig.baseURL, + path: "auth/v1/logout", + method: .post, + query: ["scope": scope.rawValue] + ) + case .resendEmail(_, let redirectTo): + return Endpoint( + baseURL: SupabaseConfig.baseURL, + path: "auth/v1/resend", + method: .post, + query: redirectTo == nil ? [:] : ["redirect_to": redirectTo!] + ) } } var requestType: SNMRequestType { @@ -34,6 +74,34 @@ extension SupabaseAuthRequest: SNMRequestConvertible { header: header, body: Data("{}".utf8) ) + case .signUpWithEmail(let parameter, _): + return SNMRequestType.compositeJSONEncodable( + header: header, + body: parameter + ) + case .signInWithEmail(let parameter): + return SNMRequestType.compositeJSONEncodable( + header: header, + body: parameter + ) + case .signInWithIDToken(let credentials): + return SNMRequestType.compositeJSONEncodable( + header: header, + body: credentials + ) + case .signOut(let accessToken, _): + return SNMRequestType.header( + with: [ + "Content-Type": "application/json", + "Authorization": "Bearer \(accessToken)", + "apikey": SupabaseConfig.apiKey + ] + ) + case .resendEmail(let parameter, _): + return SNMRequestType.compositeJSONEncodable( + header: header, + body: parameter + ) } } } diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthTypes.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthTypes.swift new file mode 100644 index 00000000..72248781 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthTypes.swift @@ -0,0 +1,47 @@ +// +// AuthTypes.swift +// SniffMeet +// +// Created by sole on 5/2/25. +// + +struct SupabaseEmailAuthParameter: Encodable { + let email: String + let password: String +} + +struct OpenIDConnectCredentials: Encodable { + /// apple, google 등 idToken 제공자 + let provider: Provider + let idToken: String + + enum Provider: String, Encodable { + case apple + case google + } + + enum CodingKeys: String, CodingKey { + case provider + case idToken = "id_token" + } +} + +/// 로그아웃 세션 범위입니다. +enum SignOutScope: String, Encodable { + /// 모든 세션에서 로그아웃합니다. + case global + /// 현재 세션만 로그아웃 합니다. + case local + /// 현재 세션 외 다른 모든 세션에서 로그아웃합니다. + case others +} + +struct ResendEmailParameter: Encodable { + let email: String + let type: EmailType + + enum EmailType: String, Encodable { + case signUp = "signup" + case emailChange = "email_change" + } +} From 6aa674694fbabdc3756426689fbbc850552aa6a7 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 17:58:34 +0900 Subject: [PATCH 02/18] =?UTF-8?q?[feat]=20#85=20deleteSession=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Common/Util/SNMErrorHandler.swift | 2 +- .../Session/SupabaseSessionManager.swift | 32 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/SniffMeet/SniffMeet/Source/Common/Util/SNMErrorHandler.swift b/SniffMeet/SniffMeet/Source/Common/Util/SNMErrorHandler.swift index 85d04ae5..903b78dd 100644 --- a/SniffMeet/SniffMeet/Source/Common/Util/SNMErrorHandler.swift +++ b/SniffMeet/SniffMeet/Source/Common/Util/SNMErrorHandler.swift @@ -90,7 +90,7 @@ struct SupabaseSessionErrorHandler: ErrorHandler { name: Environment.NotificationCenterName.sessionExpired, object: self ) - case .loadSessionFailed, .refreshSessionFailed, .saveSessionFailed: + case .loadSessionFailed, .refreshSessionFailed, .saveSessionFailed, .deleteSessionFailed: break } } diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Session/SupabaseSessionManager.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Session/SupabaseSessionManager.swift index 79c2db51..b5b608d8 100644 --- a/SniffMeet/SniffMeet/Source/Core/Supabase/Session/SupabaseSessionManager.swift +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Session/SupabaseSessionManager.swift @@ -14,6 +14,7 @@ protocol SessionManageable { func restoreSession() async throws func saveSession(for session: SupabaseSession?) throws func checkSession() async throws + func deleteSession() throws } final class SupabaseSessionManager: SessionManageable { @@ -32,17 +33,17 @@ final class SupabaseSessionManager: SessionManageable { } return .success(accessToken) } - + private init() { networkProvider = SNMNetworkProvider() jsonDecoder = JSONDecoder() } - + func restoreSession() async throws { try loadTokens() try await refreshSession() } - + func saveSession(for session: SupabaseSession?) throws { guard let session else { throw SupabaseSessionError.sessionNotExist } do { @@ -67,14 +68,29 @@ final class SupabaseSessionManager: SessionManageable { throw SupabaseSessionError.sessionNotExist } } - + + func deleteSession() throws { + guard let session else { throw SupabaseSessionError.sessionNotExist } + do { + try KeychainManager.shared.delete(forKey: Environment.KeychainKey.accessToken) + try KeychainManager.shared.delete(forKey: Environment.KeychainKey.refreshToken) + try UserDefaultsManager.shared.delete(forKey: Environment.UserDefaultsKey.expiresAt) + try UserDefaultsManager.shared.delete( + forKey: Environment.UserDefaultsKey.sessionUserInfo + ) + self.session = nil + } catch { + throw SupabaseSessionError.deleteSessionFailed + } + } + func checkSession() async throws { guard let session else { throw SupabaseSessionError.sessionNotExist } if Date(timeIntervalSince1970: TimeInterval(session.expiresAt + Constants.bufferTime)) < Date() { try await refreshSession() } } - + private func refreshSession() async throws { do { guard let refreshToken = session?.refreshToken else { @@ -99,7 +115,7 @@ final class SupabaseSessionManager: SessionManageable { throw SupabaseSessionError.refreshSessionFailed } } - + private func loadTokens() throws { do { let accessToken = try KeychainManager.shared.get( @@ -145,13 +161,15 @@ enum SupabaseSessionError: LocalizedError { case refreshSessionFailed case saveSessionFailed case sessionNotExist - + case deleteSessionFailed + var errorDescription: String? { switch self { case .loadSessionFailed: "세션 불러오기 실패" case .saveSessionFailed: "세션 저장 실패" case .refreshSessionFailed: "세션 갱신 실패" case .sessionNotExist: "세션 존재하지 않음" + case .deleteSessionFailed: "세션 삭제 실패" } } } From b5a5af1b956b7b6217638d9912085230db72e031 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:01:13 +0900 Subject: [PATCH 03/18] =?UTF-8?q?[feat]=20#85=20emailVerified=20=EC=86=8D?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/Response/SupabaseUserResponse.swift | 17 +++++++++++++++++ .../Supabase/Session/Entity/SupabaseUser.swift | 2 ++ 2 files changed, 19 insertions(+) diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/Response/SupabaseUserResponse.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/Response/SupabaseUserResponse.swift index 5912d67d..0902f7dd 100644 --- a/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/Response/SupabaseUserResponse.swift +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/Response/SupabaseUserResponse.swift @@ -9,4 +9,21 @@ import Foundation struct SupabaseUserResponse: Decodable { var id: UUID + let identities: [SupabaseUserIdentity] +} + +struct SupabaseUserIdentity: Decodable { + let identityData: IdentityData + + enum CodingKeys: String, CodingKey { + case identityData = "identity_data" + } + + struct IdentityData: Decodable { + let emailVerified: Bool + + enum CodingKeys: String, CodingKey { + case emailVerified = "email_verified" + } + } } diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/SupabaseUser.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/SupabaseUser.swift index 7a63b510..de8db52d 100644 --- a/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/SupabaseUser.swift +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Session/Entity/SupabaseUser.swift @@ -9,8 +9,10 @@ import Foundation struct SupabaseUser: Encodable { var userID: UUID + let emailVerified: Bool? init(from response: SupabaseUserResponse) { self.userID = response.id + self.emailVerified = response.identities.first?.identityData.emailVerified } } From c76b16ebd0b9e2a36827243db0e263e78f2f0c7a Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:02:02 +0900 Subject: [PATCH 04/18] =?UTF-8?q?[feat]=20#85=20signIn,=20signOut,=20resen?= =?UTF-8?q?d=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Supabase/Auth/SupabaseAuthManager.swift | 152 +++++++++++++++++- 1 file changed, 148 insertions(+), 4 deletions(-) diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift index 8a7ae001..2ca02f7b 100644 --- a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift @@ -10,13 +10,29 @@ import Foundation protocol AuthManageable { func signInAnonymously() async throws + func signIn(idToken: String, provider: OpenIDConnectCredentials.Provider) async throws + func signIn(email: String, password: String) async throws + /// redirectTo: 인증 메일을 클릭했을 때 리다이렉트되는 URL 주소를 입력합니다. + /// 입력하지 않은 경우, supabase dashboard에 기입된 주소로 리다이렉트 됩니다. + @discardableResult func signUp( + email: String, + password: String, + redirectTo: String? + ) async throws -> SupabaseUser + func signOut(scope: SignOutScope) async throws + /// redirectTo: 인증 메일을 클릭했을 때 리다이렉트되는 URL 주소를 입력합니다. + /// 입력하지 않은 경우, supabase dashboard에 기입된 주소로 리다이렉트 됩니다. + func resendVerificationEmail( + parameter: ResendEmailParameter, + redirectTo: String? + ) async throws } final class SupabaseAuthManager: AuthManageable { private let networkProvider: any NetworkProvider private let sessionManager: any SessionManageable private let jsonDecoder: JSONDecoder - + init( networkProvider: any NetworkProvider, sessionManager: any SessionManageable, @@ -26,7 +42,7 @@ final class SupabaseAuthManager: AuthManageable { self.sessionManager = sessionManager self.jsonDecoder = jsonDecoder } - + func signInAnonymously() async throws { do { let response = try await networkProvider.request( @@ -43,11 +59,124 @@ final class SupabaseAuthManager: AuthManageable { user: SupabaseUser(from: sessionResponse.user) ) try sessionManager.saveSession(for: session) - + + } catch { + throw SupabaseAuthError.signInFailed + } + } + + func signIn(email: String, password: String) async throws { + let parameter = SupabaseEmailAuthParameter(email: email, password: password) + do { + let response = try await networkProvider.request( + with: SupabaseAuthRequest.signInWithEmail(parameter: parameter) + ) + try storeSession(from: response) + } catch { + throw SupabaseAuthError.signInFailed + } + } + + /// idToken으로 회원가입, 로그인을 수행합니다. + func signIn(idToken: String, provider: OpenIDConnectCredentials.Provider) async throws { + let credentials = OpenIDConnectCredentials(provider: provider, idToken: idToken) + do { + let response = try await networkProvider.request( + with: SupabaseAuthRequest.signInWithIDToken(credentials: credentials) + ) + try storeSession(from: response) + } catch { + throw SupabaseAuthError.signInFailed + } + } + + /// redirectTo: 인증 메일을 클릭했을 때 리다이렉트되는 URL 주소를 입력합니다. + /// 입력하지 않은 경우, supabase dashboard에 기입된 주소로 리다이렉트 됩니다. + @discardableResult + func signUp( + email: String, + password: String, + redirectTo: String? = nil + ) async throws -> SupabaseUser { + let parameter = SupabaseEmailAuthParameter(email: email, password: password) + do { + let response = try await networkProvider.request( + with: SupabaseAuthRequest.signUpWithEmail( + parameter: parameter, + redirectTo: redirectTo + ) + ) + let userResponse = try jsonDecoder.decode( + SupabaseUserResponse.self, + from: response.data + ) + let user = SupabaseUser(from: userResponse) + if user.emailVerified == nil { // 이미 Authentication에 등록된 사용자가 signUp 시도 + throw SupabaseAuthError.alreadExistEmail + } + return user + } catch SNMNetworkError.failedStatusCode(let reason) { + switch reason.rawValue { + case 422: + throw SupabaseAuthError.weakPassword + case 429: + throw SupabaseAuthError.tooManyEmailRequest + default: + throw SupabaseAuthError.signUpFailed + } + } catch { + print(error.localizedDescription) + throw SupabaseAuthError.signUpFailed + } + } + + func signOut(scope: SignOutScope = .local) async throws { + let accessToken = try sessionManager.accessToken.get() + do { + _ = try await networkProvider.request( + with: SupabaseAuthRequest.signOut( + accessToken: accessToken, + scope: scope + ) + ) + try sessionManager.deleteSession() } catch { throw SupabaseAuthError.signInFailed } } + + /// redirectTo: 인증 메일을 클릭했을 때 리다이렉트되는 URL 주소를 입력합니다. + /// 입력하지 않은 경우, supabase dashboard에 기입된 주소로 리다이렉트 됩니다. + func resendVerificationEmail( + parameter: ResendEmailParameter, + redirectTo: String? = nil + ) async throws { + do { + _ = try await networkProvider.request( + with: SupabaseAuthRequest.resendEmail( + parameter: parameter, + redirectTo: redirectTo + ) + ) + } catch { + throw SupabaseAuthError.resendVerificationEmailFailed + } + } + + /// 네트워크로 받은 응답을 세션에 반영합니다. + private func storeSession(from response: SNMNetworkResponse) throws { + let sessionResponse = try jsonDecoder.decode( + SupabaseSessionResponse.self, + from: response.data + ) + let session = SupabaseSession( + accessToken: sessionResponse.accessToken, + expiresAt: sessionResponse.expiresAt, + refreshToken: sessionResponse.refreshToken, + user: SupabaseUser(from: sessionResponse.user) + ) + try sessionManager.saveSession(for: session) + } } // MARK: - SupabaseAuthError @@ -55,11 +184,26 @@ final class SupabaseAuthManager: AuthManageable { enum SupabaseAuthError: LocalizedError { case signInFailed case userNotFound - + case signOutFailed + case signUpFailed + /// Authentication에 이미 등록 및 인증된 이메일 + case alreadExistEmail + case resendVerificationEmailFailed + /// 이메일 인증을 하지 않은 사용자가 연속적으로 signUp을 시도하면, 이메일 전송량 초과 + /// 또는 하루 이메일 전송량 (50건) 초과 + case tooManyEmailRequest + case weakPassword + var errorDescription: String? { switch self { case .signInFailed: "로그인 실패" case .userNotFound: "유저 존재하지 않음" + case .signOutFailed: "로그아웃 실패" + case .signUpFailed: "회원가입 실패" + case .alreadExistEmail: "이미 존재하는 유저" + case .resendVerificationEmailFailed: "인증 이메일 재발송 실패" + case .tooManyEmailRequest: "이메일 재발송 요청 횟수 초과" + case .weakPassword: "비밀번호 길이가 너무 짧음" } } } From 394115c4eaeed0c3d1d211575fe6a2f8fca44d3f Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:04:09 +0900 Subject: [PATCH 05/18] =?UTF-8?q?[feat]=20#85=20AppleAuthManager=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SniffMeet/SniffMeet/SniffMeet.entitlements | 4 + .../SniffMeet/SniffMeetDebug.entitlements | 12 ++ .../Source/Core/OAuth/AppleAuthManager.swift | 111 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 SniffMeet/SniffMeet/SniffMeetDebug.entitlements create mode 100644 SniffMeet/SniffMeet/Source/Core/OAuth/AppleAuthManager.swift diff --git a/SniffMeet/SniffMeet/SniffMeet.entitlements b/SniffMeet/SniffMeet/SniffMeet.entitlements index 903def2a..80b5221d 100644 --- a/SniffMeet/SniffMeet/SniffMeet.entitlements +++ b/SniffMeet/SniffMeet/SniffMeet.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.developer.applesignin + + Default + diff --git a/SniffMeet/SniffMeet/SniffMeetDebug.entitlements b/SniffMeet/SniffMeet/SniffMeetDebug.entitlements new file mode 100644 index 00000000..80b5221d --- /dev/null +++ b/SniffMeet/SniffMeet/SniffMeetDebug.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.developer.applesignin + + Default + + + diff --git a/SniffMeet/SniffMeet/Source/Core/OAuth/AppleAuthManager.swift b/SniffMeet/SniffMeet/Source/Core/OAuth/AppleAuthManager.swift new file mode 100644 index 00000000..a2336bbd --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Core/OAuth/AppleAuthManager.swift @@ -0,0 +1,111 @@ +// +// AppleAuthManager.swift +// SniffMeet +// +// Created by sole on 5/2/25. +// + +import AuthenticationServices + +protocol AppleAuthManageable: AnyObject { + func signIn() async throws -> String +} + +final class AppleAuthManager: NSObject, AppleAuthManageable { + private let provider: ASAuthorizationAppleIDProvider = ASAuthorizationAppleIDProvider() + private var authorizationController: ASAuthorizationController? +#if DEBUG + private var continuation: CheckedContinuation? +#else + private var continuation: UnsafeContinuation? +#endif + + private override init() { + super.init() + } + + func signIn() async throws -> String { + // 중복 로그인 요청 방지 및 continuation misuse/crash 방지 + guard continuation == nil else { throw AppleAuthError.signInFailed } + let request = provider.createRequest() + authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController?.delegate = self + authorizationController?.presentationContextProvider = self +#if DEBUG + let idToken = try await withCheckedThrowingContinuation { [weak self] continuation in + self?.continuation = continuation + self?.authorizationController?.performRequests() + } +#else + let idToken = try await withUnsafeThrowingContinuation { [weak self] continuation in + self?.continuation = continuation + self?.authorizationController?.performRequests() + } +#endif + return idToken + } + + private func resumeOnce(with result: Result) { + guard let continuation else { return } + self.continuation = nil + switch result { + case .success(let idToken): + continuation.resume(returning: idToken) + case .failure(let error): + continuation.resume(throwing: error) + } + } +} + +// MARK: - AppleAuthManager+ + +extension AppleAuthManager { + static let shared: AppleAuthManager = AppleAuthManager() +} + +extension AppleAuthManager: ASAuthorizationControllerPresentationContextProviding { + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + if let windowScene = UIApplication.shared.keyWindow?.windowScene { + ASPresentationAnchor(windowScene: windowScene) + } else { + ASPresentationAnchor() + } + } +} + +extension AppleAuthManager: ASAuthorizationControllerDelegate { + func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential, + let idTokenData = credential.identityToken, + let idToken = String(data: idTokenData, encoding: .utf8) else { + resumeOnce(with: .failure(AppleAuthError.idTokenNotFound)) + return + } + resumeOnce(with: .success(idToken)) + } + func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: any Error + ) { + resumeOnce(with: .failure(AppleAuthError.signInFailed)) + } +} + +// MARK: - AppleAuthError + +enum AppleAuthError: LocalizedError { + case idTokenNotFound + case signInFailed + case signInAlreadyInProgress + + var localizedDescription: String { + switch self { + case .idTokenNotFound: "idToken을 찾을 수 없습니다." + case .signInFailed: "애플 로그인에 실패했습니다." + case .signInAlreadyInProgress: "애플 로그인 진행 중입니다." + } + } +} From 77a28d1862365200e1d3268c370fd9949e945025 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:04:30 +0900 Subject: [PATCH 06/18] =?UTF-8?q?[feat]=20#85=20GoogleAuthManager=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Common/Constant/Environment.swift | 4 ++ .../Source/Core/OAuth/GoogleAuthManager.swift | 59 +++++++++++++++++++ .../Source/Scene/App/AppDelegate.swift | 4 ++ .../Source/Scene/App/SceneDelegate.swift | 7 +++ 4 files changed, 74 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift diff --git a/SniffMeet/SniffMeet/Source/Common/Constant/Environment.swift b/SniffMeet/SniffMeet/Source/Common/Constant/Environment.swift index 44b6a4d8..ecfd253e 100644 --- a/SniffMeet/SniffMeet/Source/Common/Constant/Environment.swift +++ b/SniffMeet/SniffMeet/Source/Common/Constant/Environment.swift @@ -52,4 +52,8 @@ enum Environment { static let sessionExpired = Notification.Name("sessionExpired") static let profileDropFailed = Notification.Name("profileDropFailed") } + + enum GoogleSignIn { + static var clientID: String = Bundle.main.infoDictionary?["GOOGLE_CLIENT_ID"] as? String ?? "" + } } diff --git a/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift b/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift new file mode 100644 index 00000000..1ebdb4ce --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift @@ -0,0 +1,59 @@ +// +// GoogleAuthManager.swift +// SniffMeet +// +// Created by sole on 5/2/25. +// + +import GoogleSignIn + +protocol GoogleAuthManageable { + @MainActor func signIn() async throws -> String + func signOut() +} + +final class GoogleAuthManager: GoogleAuthManageable { + private init() {} + + @MainActor + func signIn() async throws -> String { + guard let rootViewController = UIViewController.topMostViewController else { + throw GoogleAuthError.viewHierarchyNotFound + } + do { + let result = try await GIDSignIn.sharedInstance.signIn( + withPresenting: rootViewController + ) + guard let idToken = result.user.idToken?.tokenString else { + throw GoogleAuthError.idTokenNotFound + } + return idToken + } catch let error as GoogleAuthError { + throw error + } catch { + throw GoogleAuthError.signInFailed + } + } + + func signOut() { + GIDSignIn.sharedInstance.signOut() + } +} + +extension GoogleAuthManager { + static let shared: GoogleAuthManageable = GoogleAuthManager() +} + +enum GoogleAuthError: Error { + case viewHierarchyNotFound + case idTokenNotFound + case signInFailed + + var localizedDescription: String { + switch self { + case .viewHierarchyNotFound: "로그인 창을 띄울 뷰 계층을 찾을 수 없습니다." + case .idTokenNotFound: "idToken을 찾을 수 없습니다." + case .signInFailed: "구글 로그인에 실패했습니다." + } + } +} diff --git a/SniffMeet/SniffMeet/Source/Scene/App/AppDelegate.swift b/SniffMeet/SniffMeet/Source/Scene/App/AppDelegate.swift index 066c8284..de0504ea 100644 --- a/SniffMeet/SniffMeet/Source/Scene/App/AppDelegate.swift +++ b/SniffMeet/SniffMeet/Source/Scene/App/AppDelegate.swift @@ -8,6 +8,7 @@ #if !DEBUG import Firebase #endif +import GoogleSignIn import UIKit @main @@ -21,6 +22,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { ) -> Bool { UNUserNotificationCenter.current().delegate = self UIApplication.shared.registerForRemoteNotifications() + GIDSignIn.sharedInstance.configuration = GIDConfiguration( + clientID: Environment.GoogleSignIn.clientID + ) #if !DEBUG FirebaseApp.configure() #endif diff --git a/SniffMeet/SniffMeet/Source/Scene/App/SceneDelegate.swift b/SniffMeet/SniffMeet/Source/Scene/App/SceneDelegate.swift index a9ef80cb..3be63665 100644 --- a/SniffMeet/SniffMeet/Source/Scene/App/SceneDelegate.swift +++ b/SniffMeet/SniffMeet/Source/Scene/App/SceneDelegate.swift @@ -5,6 +5,7 @@ // Created by sole on 11/4/24. // +import GoogleSignIn import UIKit final class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -31,6 +32,12 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() } + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let url = URLContexts.first?.url else { return } + // TODO: url scheme에 따라 분기처리 + GIDSignIn.sharedInstance.handle(url) + } + /// push notification을 통해 앱에 처음 진입한 경우 라우팅을 진행합니다. private func routePushNotification(response: UNNotificationResponse) { let userInfo = response.notification.request.content.userInfo From 6398e34fa388c3bd2caff7e3dd296ff9dbc0a96d Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:05:21 +0900 Subject: [PATCH 07/18] =?UTF-8?q?[feat]=20#85=20SignInAppleUsecase=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RequestUsecase/SignInAppleUsecase.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInAppleUsecase.swift diff --git a/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInAppleUsecase.swift b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInAppleUsecase.swift new file mode 100644 index 00000000..af73a253 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInAppleUsecase.swift @@ -0,0 +1,23 @@ +// +// SignInAppleUsecase.swift +// SniffMeet +// +// Created by sole on 5/10/25. +// + +protocol SignInAppleUsecase { + /// 성공 시 idToken을 반환합니다. + func execute() async throws -> String +} + +struct SignInAppleUsecaseImpl: SignInAppleUsecase { + private let appleAuthManager: any AppleAuthManageable + + init(appleAuthManager: any AppleAuthManageable) { + self.appleAuthManager = appleAuthManager + } + + func execute() async throws -> String { + try await appleAuthManager.signIn() + } +} From 217513ce9f7d360ae8fe58c9c6321073988e90db Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:05:40 +0900 Subject: [PATCH 08/18] =?UTF-8?q?[feat]=20#85=20SignInGoogleUsecase=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RequestUsecase/SignInGoogleUsecase.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInGoogleUsecase.swift diff --git a/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInGoogleUsecase.swift b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInGoogleUsecase.swift new file mode 100644 index 00000000..c3c089bf --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInGoogleUsecase.swift @@ -0,0 +1,23 @@ +// +// SignInGoogleUsecase.swift +// SniffMeet +// +// Created by sole on 5/10/25. +// + +protocol SignInGoogleUsecase { + /// 성공 시 idToken을 반환합니다. + func execute() async throws -> String +} + +struct SignInGoogleUsecaseImpl: SignInGoogleUsecase { + private let googleAuthManager: any GoogleAuthManageable + + init(googleAuthManager: any GoogleAuthManageable) { + self.googleAuthManager = googleAuthManager + } + + func execute() async throws -> String { + try await googleAuthManager.signIn() + } +} From 6d47fd41e3f22d52234d8d7748a8bdd35936e48a Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:06:02 +0900 Subject: [PATCH 09/18] =?UTF-8?q?[feat]=20#85=20SignInOpenIDUsecase=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Entity/Auth/OpenIDProvider.swift | 12 +++++++++ .../RequestUsecase/SignInOpenIDUsecase.swift | 27 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Entity/Auth/OpenIDProvider.swift create mode 100644 SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInOpenIDUsecase.swift diff --git a/SniffMeet/SniffMeet/Source/Entity/Auth/OpenIDProvider.swift b/SniffMeet/SniffMeet/Source/Entity/Auth/OpenIDProvider.swift new file mode 100644 index 00000000..4d197ba6 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Entity/Auth/OpenIDProvider.swift @@ -0,0 +1,12 @@ +// +// OpenIDProvider.swift +// SniffMeet +// +// Created by sole on 5/10/25. +// + +/// idToken의 제공자를 의미합니다. +enum OpenIDProvider: String { + case apple + case google +} diff --git a/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInOpenIDUsecase.swift b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInOpenIDUsecase.swift new file mode 100644 index 00000000..b618d3d7 --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInOpenIDUsecase.swift @@ -0,0 +1,27 @@ +// +// SignInOpenIDUsecase.swift +// SniffMeet +// +// Created by sole on 5/10/25. +// + +protocol SignInOpenIDUsecase { + func execute(provider: OpenIDProvider, idToken: String) async throws +} + +final class SignInOpenIDUsecaseImpl: SignInOpenIDUsecase { + private let authManager: any AuthManageable + + init(authManager: any AuthManageable) { + self.authManager = authManager + } + + func execute(provider: OpenIDProvider, idToken: String) async throws { + guard let openIDProvider = OpenIDConnectCredentials.Provider( + rawValue: provider.rawValue + ) else { + throw SupabaseAuthError.signInFailed + } + try await authManager.signIn(idToken: idToken, provider: openIDProvider) + } +} From 301608b656c1c4dc42dc603cfa3ae0247505f1e3 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:07:01 +0900 Subject: [PATCH 10/18] =?UTF-8?q?[feat]=20#85=20SignInEmailUsecase=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RequestUsecase/SignInEmailUsecase.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInEmailUsecase.swift diff --git a/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInEmailUsecase.swift b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInEmailUsecase.swift new file mode 100644 index 00000000..c9b1e61c --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignInEmailUsecase.swift @@ -0,0 +1,22 @@ +// +// SignInEmailUsecase.swift +// SniffMeet +// +// Created by sole on 5/14/25. +// + +protocol SignInEmailUsecase { + func execute(email: String, password: String) async throws +} + +struct SignInEmailUsecaseImpl: SignInEmailUsecase { + private let authManager: any AuthManageable + + init(authManager: any AuthManageable) { + self.authManager = authManager + } + + func execute(email: String, password: String) async throws { + try await authManager.signIn(email: email, password: password) + } +} From 8dca41787f27c0008c58a79e064c03864cfe0ab7 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:07:16 +0900 Subject: [PATCH 11/18] =?UTF-8?q?[feat]=20#85=20SignUpEmailUsecase=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RequestUsecase/SignUpEmailUsecase.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignUpEmailUsecase.swift diff --git a/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignUpEmailUsecase.swift b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignUpEmailUsecase.swift new file mode 100644 index 00000000..c83b6d5a --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/SignUpEmailUsecase.swift @@ -0,0 +1,27 @@ +// +// SignUpEmailUsecase.swift +// SniffMeet +// +// Created by sole on 5/10/25. +// + +protocol SignUpEmailUsecase { + /// 이메일로 회원가입을 시도합니다. redirectTo는 사용자가 인증 이메일을 탭했을 때 연결되는 링크 주소입니다. + func execute(email: String, password: String, redirectTo: String?) async throws +} + +struct SignUpEmailUsecaseImpl: SignUpEmailUsecase { + private let authManager: any AuthManageable + + init(authManager: any AuthManageable) { + self.authManager = authManager + } + + func execute(email: String, password: String, redirectTo: String?) async throws { + try await authManager.signUp( + email: email, + password: password, + redirectTo: redirectTo + ) + } +} From 21305dd1dbfb2653a2df67b04703fd1b28f54bd0 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:07:28 +0900 Subject: [PATCH 12/18] =?UTF-8?q?[feat]=20#85=20ResendSignUpEmailUsecase?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResendSignUpEmailUsecase.swift | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/ResendSignUpEmailUsecase.swift diff --git a/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/ResendSignUpEmailUsecase.swift b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/ResendSignUpEmailUsecase.swift new file mode 100644 index 00000000..916cb5fb --- /dev/null +++ b/SniffMeet/SniffMeet/Source/Usecase/RequestUsecase/ResendSignUpEmailUsecase.swift @@ -0,0 +1,25 @@ +// +// ResendEmailUsecase.swift +// SniffMeet +// +// Created by sole on 5/10/25. +// + +protocol ResendSignUpEmailUsecase { + /// redirectTo: 인증 메일을 클릭했을 때 리다이렉트되는 URL 주소를 입력합니다. + /// 입력하지 않은 경우, supabase dashboard에 기입된 주소로 리다이렉트 됩니다. + func execute(email: String, redirectTo: String?) async throws +} + +struct ResendSignUpEmailUsecaseImpl: ResendSignUpEmailUsecase { + private let authManager: any AuthManageable + + init(authManager: any AuthManageable) { + self.authManager = authManager + } + + func execute(email: String, redirectTo: String?) async throws { + let parameter = ResendEmailParameter(email: email, type: .signUp) + try await authManager.resendVerificationEmail(parameter: parameter, redirectTo: redirectTo) + } +} From 7122fccf1e34a80d0a8ba8324ada317228f3efe7 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:08:25 +0900 Subject: [PATCH 13/18] =?UTF-8?q?[chore]=20#85=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcshareddata/swiftpm/Package.resolved | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/SniffMeet/SniffMeet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SniffMeet/SniffMeet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b0926453..97bc6ea6 100644 --- a/SniffMeet/SniffMeet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SniffMeet/SniffMeet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "307a2fb25fd0abaa1af27f17ac1b9e224b460bae11c901e0176fe0947adc37df", + "originHash" : "141690ec89c3fa5a4891e2b78d9fbaa7985b414d25aa60ee9048a7a9aa998ce4", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -19,6 +19,15 @@ "version" : "11.2.0" } }, + { + "identity" : "appauth-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/openid/AppAuth-iOS.git", + "state" : { + "revision" : "2781038865a80e2c425a1da12cc1327bcd56501f", + "version" : "1.7.6" + } + }, { "identity" : "collectionconcurrencykit", "kind" : "remoteSourceControl", @@ -64,6 +73,15 @@ "version" : "10.1.0" } }, + { + "identity" : "googlesignin-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleSignIn-iOS", + "state" : { + "revision" : "65fb3f1aa6ffbfdc79c4e22178a55cd91561f5e9", + "version" : "8.0.0" + } + }, { "identity" : "googleutilities", "kind" : "remoteSourceControl", @@ -87,8 +105,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { - "revision" : "3cdb78efb79b4a5383c3911488d8025bfc545b5e", - "version" : "4.3.0" + "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", + "version" : "3.5.0" + } + }, + { + "identity" : "gtmappauth", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GTMAppAuth.git", + "state" : { + "revision" : "5d7d66f647400952b1758b230e019b07c0b4b22a", + "version" : "4.1.1" } }, { From b0f06ebcfd602fe0f73b74949d01dd9dab35b6c3 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:08:55 +0900 Subject: [PATCH 14/18] =?UTF-8?q?[chore]=20#85=20=EA=B5=AC=EA=B8=80=20clie?= =?UTF-8?q?nt=5Fid,=20url=5Ftypes=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SniffMeet/SniffMeet/Resource/Info.plist | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/SniffMeet/SniffMeet/Resource/Info.plist b/SniffMeet/SniffMeet/Resource/Info.plist index 1778b58f..962ef50f 100644 --- a/SniffMeet/SniffMeet/Resource/Info.plist +++ b/SniffMeet/SniffMeet/Resource/Info.plist @@ -16,11 +16,30 @@ 'SniffMEET'은 Nearby Interaction과 ARKit을 위해 카메라 접근이 필요합니다. NSNearbyInteractionUsageDescription 'SniffMEET'은 연결된 기기와의 거리와 방향 측정을 위해 Nearby Interaction 접근이 필요합니다. + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + $(GOOGLE_REVERSED_CLIENT_ID) + + + + GOOGLE_CLIENT_ID + $(GOOGLE_CLIENT_ID) + NOTIFICATION_SERVER + $(NOTIFICATION_SERVER) NSBonjourServices _SniffMeet._tcp _SniffMeet._udp + PUBLIC_KEY + $(PUBLIC_KEY) + SERVER_URL + $(SERVER_URL) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -38,15 +57,9 @@ - SERVER_URL - $(SERVER_URL) - PUBLIC_KEY - $(PUBLIC_KEY) UIBackgroundModes remote-notification - NOTIFICATION_SERVER - $(NOTIFICATION_SERVER) From ea6129c47e850305508efd3d8b1d8378c08ba329 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:13:03 +0900 Subject: [PATCH 15/18] =?UTF-8?q?[chore]=20#85=20.pbxproj=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SniffMeet/SniffMeet.xcodeproj/project.pbxproj | 126 +++++++++++++++++- 1 file changed, 119 insertions(+), 7 deletions(-) diff --git a/SniffMeet/SniffMeet.xcodeproj/project.pbxproj b/SniffMeet/SniffMeet.xcodeproj/project.pbxproj index 4ca3eebf..a5710a3b 100644 --- a/SniffMeet/SniffMeet.xcodeproj/project.pbxproj +++ b/SniffMeet/SniffMeet.xcodeproj/project.pbxproj @@ -488,6 +488,31 @@ FD119F4E2CED87C200BE22BD /* SelectLocationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD119F4D2CED87BC00BE22BD /* SelectLocationPresenter.swift */; }; FD119F502CED87E500BE22BD /* SelectLocationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD119F4F2CED87DD00BE22BD /* SelectLocationRouter.swift */; }; FD119F522CED881300BE22BD /* SelectLocationModuleBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD119F512CED880A00BE22BD /* SelectLocationModuleBuildable.swift */; }; + FD14C2572DD4727D00AC9ABB /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = FD14C2562DD4727D00AC9ABB /* GoogleSignIn */; }; + FD14C2592DD4727D00AC9ABB /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD14C2582DD4727D00AC9ABB /* GoogleSignInSwift */; }; + FD14C25F2DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */; }; + FD14C2602DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */; }; + FD14C2612DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */; }; + FD14C26C2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */; }; + FD14C26D2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */; }; + FD14C26E2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */; }; + FD14C2702DD4737900AC9ABB /* AppleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */; }; + FD14C2712DD4737900AC9ABB /* AppleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */; }; + FD14C2722DD4737900AC9ABB /* AppleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */; }; + FD14C2742DD4739F00AC9ABB /* SignInEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2732DD4739F00AC9ABB /* SignInEmailUsecase.swift */; }; + FD14C2752DD4739F00AC9ABB /* SignInEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2732DD4739F00AC9ABB /* SignInEmailUsecase.swift */; }; + FD14C2772DD473AB00AC9ABB /* SignInAppleUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2762DD473AB00AC9ABB /* SignInAppleUsecase.swift */; }; + FD14C2782DD473AB00AC9ABB /* SignInAppleUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2762DD473AB00AC9ABB /* SignInAppleUsecase.swift */; }; + FD14C27A2DD473B400AC9ABB /* SignInGoogleUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2792DD473B400AC9ABB /* SignInGoogleUsecase.swift */; }; + FD14C27B2DD473B400AC9ABB /* SignInGoogleUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2792DD473B400AC9ABB /* SignInGoogleUsecase.swift */; }; + FD14C27D2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C27C2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift */; }; + FD14C27E2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C27C2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift */; }; + FD14C2802DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C27F2DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift */; }; + FD14C2812DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C27F2DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift */; }; + FD14C2832DD473D900AC9ABB /* SignUpEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2822DD473D900AC9ABB /* SignUpEmailUsecase.swift */; }; + FD14C2842DD473D900AC9ABB /* SignUpEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2822DD473D900AC9ABB /* SignUpEmailUsecase.swift */; }; + FD14C2872DD4741800AC9ABB /* OpenIDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2862DD4741800AC9ABB /* OpenIDProvider.swift */; }; + FD14C2882DD4741800AC9ABB /* OpenIDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2862DD4741800AC9ABB /* OpenIDProvider.swift */; }; FD159A7D2D35166D00B425D9 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD880B612CF433AC0093BEB9 /* Environment.swift */; }; FD243CFA2D621FC600971162 /* SNMProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD243CF92D621FC200971162 /* SNMProgressView.swift */; }; FD243CFB2D621FC600971162 /* SNMProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD243CF92D621FC200971162 /* SNMProgressView.swift */; }; @@ -867,6 +892,16 @@ FD119F4D2CED87BC00BE22BD /* SelectLocationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationPresenter.swift; sourceTree = ""; }; FD119F4F2CED87DD00BE22BD /* SelectLocationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationRouter.swift; sourceTree = ""; }; FD119F512CED880A00BE22BD /* SelectLocationModuleBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationModuleBuildable.swift; sourceTree = ""; }; + FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupabaseAuthTypes.swift; sourceTree = ""; }; + FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthManager.swift; sourceTree = ""; }; + FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAuthManager.swift; sourceTree = ""; }; + FD14C2732DD4739F00AC9ABB /* SignInEmailUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInEmailUsecase.swift; sourceTree = ""; }; + FD14C2762DD473AB00AC9ABB /* SignInAppleUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInAppleUsecase.swift; sourceTree = ""; }; + FD14C2792DD473B400AC9ABB /* SignInGoogleUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInGoogleUsecase.swift; sourceTree = ""; }; + FD14C27C2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInOpenIDUsecase.swift; sourceTree = ""; }; + FD14C27F2DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResendSignUpEmailUsecase.swift; sourceTree = ""; }; + FD14C2822DD473D900AC9ABB /* SignUpEmailUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpEmailUsecase.swift; sourceTree = ""; }; + FD14C2862DD4741800AC9ABB /* OpenIDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenIDProvider.swift; sourceTree = ""; }; FD159A7C2D35104500B425D9 /* SNMTestplan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SNMTestplan.xctestplan; sourceTree = ""; }; FD243CF92D621FC200971162 /* SNMProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SNMProgressView.swift; sourceTree = ""; }; FD243CFC2D62332000971162 /* SNMToastAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SNMToastAnimation.swift; sourceTree = ""; }; @@ -976,9 +1011,11 @@ FDC5D9BD2D5C7E0400380533 /* FirebaseAnalytics in Frameworks */, FDC5D9C32D5C7E0400380533 /* FirebaseCore in Frameworks */, FDC5D9BF2D5C7E0400380533 /* FirebaseAnalyticsOnDeviceConversion in Frameworks */, + FD14C2592DD4727D00AC9ABB /* GoogleSignInSwift in Frameworks */, FDC5D9C52D5C7E0400380533 /* FirebaseCrashlytics in Frameworks */, 99A9C0532D394AD100E669C8 /* OrderedCollections in Frameworks */, FDC5D9C12D5C7E0400380533 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, + FD14C2572DD4727D00AC9ABB /* GoogleSignIn in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1048,6 +1085,7 @@ 320044792CEC490600D08B6D /* Entity */ = { isa = PBXGroup; children = ( + FD14C2852DD4741100AC9ABB /* Auth */, A2BD49F02CF72A8800AC72A7 /* DTO */, 99E79FA72D556E1500E48552 /* Report.swift */, 320522BD2D0181A700F1677C /* ImageCacheable.swift */, @@ -1126,6 +1164,12 @@ 320047A42CF9E2A200D08B6D /* RequestUsecase */ = { isa = PBXGroup; children = ( + FD14C2732DD4739F00AC9ABB /* SignInEmailUsecase.swift */, + FD14C2762DD473AB00AC9ABB /* SignInAppleUsecase.swift */, + FD14C2792DD473B400AC9ABB /* SignInGoogleUsecase.swift */, + FD14C27C2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift */, + FD14C27F2DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift */, + FD14C2822DD473D900AC9ABB /* SignUpEmailUsecase.swift */, FD259F2E2D6F0AFA007B182D /* RequestWalkLogListUsecase.swift */, 99E79FA42D556C5600E48552 /* RequestReportUsecase.swift */, FD1152742CFB9F33008955C4 /* CheckFirstLaunchUsecase.swift */, @@ -1192,6 +1236,7 @@ 32081B1F2D5C7FFA00D19235 /* Core */ = { isa = PBXGroup; children = ( + FD14C26A2DD4734D00AC9ABB /* OAuth */, 320047B72CFD365700D08B6D /* PushNotification */, FDA4913A2CDA04BB00A36FB0 /* Repository */, FD0634242CE596A7003C9D6B /* SNMNetwork */, @@ -1611,6 +1656,7 @@ A24082652CEB2475009109A0 /* Auth */ = { isa = PBXGroup; children = ( + FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */, A24082632CEB2470009109A0 /* SupabaseAuthManager.swift */, A240826F2CEB27DE009109A0 /* SupabaseAuthRequest.swift */, ); @@ -1787,6 +1833,23 @@ path = SNMNetwork; sourceTree = ""; }; + FD14C26A2DD4734D00AC9ABB /* OAuth */ = { + isa = PBXGroup; + children = ( + FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */, + FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */, + ); + path = OAuth; + sourceTree = ""; + }; + FD14C2852DD4741100AC9ABB /* Auth */ = { + isa = PBXGroup; + children = ( + FD14C2862DD4741800AC9ABB /* OpenIDProvider.swift */, + ); + path = Auth; + sourceTree = ""; + }; FD243CF82D621FBA00971162 /* SNMToast */ = { isa = PBXGroup; children = ( @@ -2664,6 +2727,8 @@ FDC5D9C02D5C7E0400380533 /* FirebaseAnalyticsWithoutAdIdSupport */, FDC5D9C22D5C7E0400380533 /* FirebaseCore */, FDC5D9C42D5C7E0400380533 /* FirebaseCrashlytics */, + FD14C2562DD4727D00AC9ABB /* GoogleSignIn */, + FD14C2582DD4727D00AC9ABB /* GoogleSignInSwift */, ); productName = SniffMeet; productReference = FD3A03202CD8CDE50047B7ED /* SniffMeet.app */; @@ -2753,6 +2818,7 @@ FD3A03452CD8D2650047B7ED /* XCRemoteSwiftPackageReference "SwiftLint" */, 99A9C0512D394ABF00E669C8 /* XCRemoteSwiftPackageReference "swift-collections" */, FDC5D9BB2D5C7E0400380533 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, ); productRefGroup = FD3A03212CD8CDE50047B7ED /* Products */; projectDirPath = ""; @@ -2849,6 +2915,7 @@ FDC8A02E2D66FA190097F00A /* SNMErrorHandler.swift in Sources */, 32BD48EB2D40B4790078DA9C /* SNMColor.swift in Sources */, 99525F2D2D7466FE00B6E17E /* ResetPwViewController.swift in Sources */, + FD14C27D2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift in Sources */, 32BD48EA2D40AEBC0078DA9C /* RemoteDatabaseManagerMock.swift in Sources */, 32C539632D64165A00CD89DF /* TrackWalkRouter.swift in Sources */, 99E79F5C2D5392C100E48552 /* DeleteMateUseCase.swift in Sources */, @@ -2868,12 +2935,17 @@ 99525EBB2D6563E800B6E17E /* UpdateTimeUsecase.swift in Sources */, 32BD48412D3FF02C0078DA9C /* ImageCacheable.swift in Sources */, 32BD48422D3FF02C0078DA9C /* OnBoardingPage.swift in Sources */, + FD14C2812DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift in Sources */, + FD14C27B2DD473B400AC9ABB /* SignInGoogleUsecase.swift in Sources */, + FD14C2882DD4741800AC9ABB /* OpenIDProvider.swift in Sources */, 32BD48432D3FF02C0078DA9C /* WalkLog.swift in Sources */, + FD14C2832DD473D900AC9ABB /* SignUpEmailUsecase.swift in Sources */, 32BD48442D3FF02C0078DA9C /* WalkNoti.swift in Sources */, 32BD48452D3FF02C0078DA9C /* Mate.swift in Sources */, 32081A6A2D547F9F00D19235 /* MPCSessionConcurrencyTest.swift in Sources */, 32BD48462D3FF02C0078DA9C /* ProfileInfo.swift in Sources */, 32BD48472D3FF02C0078DA9C /* Keyword.swift in Sources */, + FD14C2782DD473AB00AC9ABB /* SignInAppleUsecase.swift in Sources */, 32BD48482D3FF02C0078DA9C /* Size.swift in Sources */, 99E795CA2D8A97D5001C9B18 /* PreferencesPresenter.swift in Sources */, 32BD48492D3FF02C0078DA9C /* Sex.swift in Sources */, @@ -2920,6 +2992,7 @@ 32BD48672D3FF02C0078DA9C /* SNMRequestConvertible.swift in Sources */, 32BD48682D3FF02C0078DA9C /* Endpoint.swift in Sources */, 32BD48692D3FF02C0078DA9C /* SNMNetworkResponse.swift in Sources */, + FD14C2742DD4739F00AC9ABB /* SignInEmailUsecase.swift in Sources */, 99525F332D74685D00B6E17E /* ResetPwPresenter.swift in Sources */, 32BD486A2D3FF02C0078DA9C /* Data + append.swift in Sources */, 32BD486B2D3FF02C0078DA9C /* URLRequest + append.swift in Sources */, @@ -2967,6 +3040,7 @@ A2FE77232D5211C700239F05 /* SupabaseSessionRequest.swift in Sources */, A2FE77722D54ACE800239F05 /* SupabaseDBRequestBuilder.swift in Sources */, 32BD48842D3FF02C0078DA9C /* InputTextField.swift in Sources */, + FD14C2722DD4737900AC9ABB /* AppleAuthManager.swift in Sources */, 32BD48852D3FF02C0078DA9C /* KeywordButton.swift in Sources */, 99E79F9C2D54D0AA00E48552 /* ReportPickerPresenter.swift in Sources */, 32BD48862D3FF02C0078DA9C /* KeywordView.swift in Sources */, @@ -3007,6 +3081,7 @@ 32BD48A02D3FF02C0078DA9C /* ProfileEditViewController.swift in Sources */, 32BD48A12D3FF02C0078DA9C /* HomeModuleBuilder.swift in Sources */, 32BD48A22D3FF02C0078DA9C /* HomeViewController.swift in Sources */, + FD14C26E2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */, 32BD48A32D3FF02C0078DA9C /* ProfileCardView.swift in Sources */, 32BD48A42D3FF02C0078DA9C /* HomeInteractor.swift in Sources */, 32BD48A52D3FF02C0078DA9C /* HomePresenter.swift in Sources */, @@ -3077,6 +3152,7 @@ 32BD48D12D3FF02C0078DA9C /* NIManager.swift in Sources */, 99E79FAB2D556E8600E48552 /* ReportDTO.swift in Sources */, 32BD48D22D3FF02C0078DA9C /* MPCManager.swift in Sources */, + FD14C2612DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */, 32BD48D32D3FF02C0078DA9C /* MPCAdvertiser.swift in Sources */, 32BD48D42D3FF02C0078DA9C /* MPCBroswer.swift in Sources */, 32BD48D52D3FF02C0078DA9C /* SupabaseConfig.swift in Sources */, @@ -3121,11 +3197,14 @@ FD159A7D2D35166D00B425D9 /* Environment.swift in Sources */, FD331A8B2CF33E7100F39426 /* UserDefaultsManager.swift in Sources */, FD331A8C2CF33E7100F39426 /* KeychainManager.swift in Sources */, + FD14C26D2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */, + FD14C2712DD4737900AC9ABB /* AppleAuthManager.swift in Sources */, A2FE77742D54ACE800239F05 /* SupabaseDBRequestBuilder.swift in Sources */, FD331A7D2CF33D0500F39426 /* SupabaseStorageRequest.swift in Sources */, FD331A7E2CF33D0500F39426 /* SupabaseConfig.swift in Sources */, FD331A802CF33D0500F39426 /* SupabaseAuthManager.swift in Sources */, FD331A8E2CF33F6700F39426 /* SupabaseStorageManager.swift in Sources */, + FD14C25F2DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */, FD331A822CF33D0500F39426 /* SupabaseAuthRequest.swift in Sources */, FD331A832CF33D0500F39426 /* SupabaseSession.swift in Sources */, FD331A842CF33D0500F39426 /* SupabaseUser.swift in Sources */, @@ -3166,6 +3245,7 @@ A24B1F322D3C33660012B860 /* CGImageCoder.swift in Sources */, A24082702CEB27DE009109A0 /* SupabaseAuthRequest.swift in Sources */, FDC8A02F2D66FA190097F00A /* SNMErrorHandler.swift in Sources */, + FD14C2802DD473CD00AC9ABB /* ResendSignUpEmailUsecase.swift in Sources */, 32C539622D64165A00CD89DF /* TrackWalkRouter.swift in Sources */, 99525F3B2D746AC000B6E17E /* ResetPwEmailInteractor.swift in Sources */, A2C844D12CDBE1E5007F2970 /* (null) in Sources */, @@ -3236,6 +3316,7 @@ A26BA0092D6DC84C000E5648 /* ProfileSetInteractor.swift in Sources */, FD01D8FA2D40ACAF00AD4940 /* Extension + Array.swift in Sources */, FD5134172CEB738D002E76F3 /* UIControl + Publisher.swift in Sources */, + FD14C2602DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */, FD259F432D6FD2D0007B182D /* WalkLogListPresenter.swift in Sources */, 320047BE2CFDE37300D08B6D /* WalkRequestDTO.swift in Sources */, 320044852CEC836400D08B6D /* PaddingLabel.swift in Sources */, @@ -3250,6 +3331,7 @@ FD9468D52CFD590700417DC1 /* ProfileEditPresenter.swift in Sources */, FD9468D62CFD590700417DC1 /* ProfileEditInteractor.swift in Sources */, FD9468D72CFD590700417DC1 /* ProfileEditRoutable.swift in Sources */, + FD14C27A2DD473B400AC9ABB /* SignInGoogleUsecase.swift in Sources */, 32C5395C2D64165800CD89DF /* TrackWalkViewPresenter.swift in Sources */, 993736422CFCACB100E3DD58 /* UpdateUserInfoUsecase.swift in Sources */, 320047B62CFCB8D000D08B6D /* EventConstant.swift in Sources */, @@ -3269,6 +3351,7 @@ FD0634282CE596BD003C9D6B /* SNMRequestConvertible.swift in Sources */, A2BA1B9A2CF260A50080575A /* MateListRouter.swift in Sources */, 99525F1C2D70736E00B6E17E /* SignUpViewController.swift in Sources */, + FD14C2842DD473D900AC9ABB /* SignUpEmailUsecase.swift in Sources */, 320043972CDF493C00D08B6D /* Extension + UIView.swift in Sources */, FD119F4A2CED875F00BE22BD /* UpdateUserLocationUsecase.swift in Sources */, FD1152772CFBA089008955C4 /* SaveFirstLaunchUsecase.swift in Sources */, @@ -3319,6 +3402,7 @@ 320043932CDF3D7F00D08B6D /* KeywordView.swift in Sources */, 320044772CEB4E1E00D08B6D /* RequestWalkUsecase.swift in Sources */, 320044982CED81F400D08B6D /* WalkNoti.swift in Sources */, + FD14C2702DD4737900AC9ABB /* AppleAuthManager.swift in Sources */, 99525F212D70747100B6E17E /* SignUpPresenter.swift in Sources */, FDE14A502CE8D2FB001F7A9D /* URLRequest + append.swift in Sources */, A2BA1B982CF260780080575A /* MateListPresenter.swift in Sources */, @@ -3327,6 +3411,7 @@ FD06342C2CE5984D003C9D6B /* SNMNetworkResponse.swift in Sources */, 3200445E2CE5CA1B00D08B6D /* SaveUserInfoUsecase.swift in Sources */, 99525F102D6E80C300B6E17E /* SigninPresenter.swift in Sources */, + FD14C2752DD4739F00AC9ABB /* SignInEmailUsecase.swift in Sources */, 3200445E2CE5CA1B00D08B6D /* SaveUserInfoUsecase.swift in Sources */, 995D94AA2CECF8DD005A47BF /* RequestMateInteractor.swift in Sources */, 320522BE2D0181A800F1677C /* ImageCacheable.swift in Sources */, @@ -3335,6 +3420,7 @@ 99E795D22D8D440F001C9B18 /* PreferencesOption.swift in Sources */, 99E79FA02D54D45600E48552 /* ReportPickerInteractor.swift in Sources */, FD51341B2CEB7AE8002E76F3 /* HomePresenter.swift in Sources */, + FD14C26C2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */, FD11527F2CFC9029008955C4 /* NotificationListInteractor.swift in Sources */, 320043942CDF3D7F00D08B6D /* PrimaryButton.swift in Sources */, FDE7689A2CF783EB00B5DE88 /* ConvertToWalkAPSUsecase.swift in Sources */, @@ -3343,6 +3429,7 @@ 320043952CDF3D7F00D08B6D /* Extension + Navigation.swift in Sources */, A2AFC9492D016BFF0062B34E /* SNMToast.swift in Sources */, 320047B92CFD367300D08B6D /* PushNotificationConfig.swift in Sources */, + FD14C27E2DD473BF00AC9ABB /* SignInOpenIDUsecase.swift in Sources */, FDBFA99F2CE1E50C00AA9220 /* SNMFont.swift in Sources */, 32C53A0C2D78624400CD89DF /* AlertContent.swift in Sources */, FD119F4E2CED87C200BE22BD /* SelectLocationPresenter.swift in Sources */, @@ -3376,6 +3463,7 @@ 320047BB2CFD368100D08B6D /* PushNotificationRequest.swift in Sources */, FD331A562CF23C5300F39426 /* WalkLogListViewController.swift in Sources */, 320047AA2CFB1C6200D08B6D /* RequestNotiListUsecase.swift in Sources */, + FD14C2872DD4741800AC9ABB /* OpenIDProvider.swift in Sources */, 320044672CE6125100D08B6D /* DataManager.swift in Sources */, FD2921342D632333009D0762 /* DimPresentable.swift in Sources */, 3200449C2CEDA87800D08B6D /* RespondWalkRequestUsecase.swift in Sources */, @@ -3414,6 +3502,7 @@ A23A3B732CF4683F00A58705 /* Mate.swift in Sources */, 99E79FA92D556E1900E48552 /* Report.swift in Sources */, 99525F0D2D6E806B00B6E17E /* SigninInteractor.swift in Sources */, + FD14C2772DD473AB00AC9ABB /* SignInAppleUsecase.swift in Sources */, FD1152752CFB9F44008955C4 /* CheckFirstLaunchUsecase.swift in Sources */, 3200441A2CE48A3D00D08B6D /* MPCBroswer.swift in Sources */, A25BE4562CEBB63A00886763 /* SupabaseUser.swift in Sources */, @@ -3567,7 +3656,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.hgd1004.SniffMEET; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = SniffMEET; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3592,7 +3680,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.hgd1004.SniffMEET; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = SniffMEET; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "SniffMEET pear2"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3645,7 +3733,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.hgd1004.SniffMEET; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = SniffMEET; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "SniffMEET pear2"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3662,7 +3750,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SniffMeet/SniffMeet.entitlements; + CODE_SIGN_ENTITLEMENTS = SniffMeet/SniffMeetDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -3677,6 +3765,9 @@ INFOPLIST_KEY_NSLocalNetworkUsageDescription = "'SniffMEET'은 P2P 연결을 지원하기 위해 로컬 네트워크 환경이 필요합니다."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "산책하기 기능에는 GPS 사용이 필요합니다. "; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "산책하기 기능에는 GPS 사용이 필요합니다. "; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "'SniffMEET'은 산책 기록을 위해 백그라운드에서의 사용자 위치 정보가 필요합니다."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "'SniffMEET'은 산책 기록을 위해 사용자 위치 정보 권한이 필요합니다."; + INFOPLIST_KEY_NSMotionUsageDescription = "'SniffMEET'은 걸음 수 측정을 위해 데이터 접근 권한이 필요합니다."; INFOPLIST_KEY_NSNearbyInteractionUsageDescription = "'SniffMEET'은 연결된 기기와의 거리와 방향 측정을 위해 Nearby Interaction 접근이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; @@ -3693,7 +3784,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.hgd1004.SniffMEET; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "SniffMEET pear2"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = SniffMEET; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3711,7 +3802,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = SniffMeet/SniffMeet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -3723,6 +3814,9 @@ INFOPLIST_KEY_NSLocalNetworkUsageDescription = "'SniffMEET'은 P2P 연결을 지원하기 위해 로컬 네트워크 환경이 필요합니다."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "산책하기 기능에는 GPS 사용이 필요합니다. "; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "산책하기 기능에는 GPS 사용이 필요합니다. "; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "'SniffMEET'은 산책 기록을 위해 백그라운드에서의 사용자 위치 정보가 필요합니다."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "'SniffMEET'은 산책 기록을 위해 사용자 위치 정보 권한이 필요합니다."; + INFOPLIST_KEY_NSMotionUsageDescription = "'SniffMEET'은 걸음 수 측정을 위해 데이터 접근 권한이 필요합니다."; INFOPLIST_KEY_NSNearbyInteractionUsageDescription = "'SniffMEET'은 연결된 기기와의 거리와 방향 측정을 위해 Nearby Interaction 접근이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; @@ -3739,7 +3833,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.hgd1004.SniffMEET; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "SniffMEET distribution pear2"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = SniffMEET; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -4049,6 +4143,14 @@ minimumVersion = 1.1.4; }; }; + FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; FD3A03452CD8D2650047B7ED /* XCRemoteSwiftPackageReference "SwiftLint" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/SwiftLint"; @@ -4083,6 +4185,16 @@ package = 99A9C0512D394ABF00E669C8 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; + FD14C2562DD4727D00AC9ABB /* GoogleSignIn */ = { + isa = XCSwiftPackageProductDependency; + package = FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignIn; + }; + FD14C2582DD4727D00AC9ABB /* GoogleSignInSwift */ = { + isa = XCSwiftPackageProductDependency; + package = FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignInSwift; + }; FDC5D9BC2D5C7E0400380533 /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; package = FDC5D9BB2D5C7E0400380533 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; From b0ad9e53bb2f44c697ceeb42c1fc0cc5adbf12e3 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:14:16 +0900 Subject: [PATCH 16/18] =?UTF-8?q?[chore]=20#85=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20print=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Core/Supabase/Auth/SupabaseAuthManager.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift index 2ca02f7b..e532dea6 100644 --- a/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift +++ b/SniffMeet/SniffMeet/Source/Core/Supabase/Auth/SupabaseAuthManager.swift @@ -125,7 +125,6 @@ final class SupabaseAuthManager: AuthManageable { throw SupabaseAuthError.signUpFailed } } catch { - print(error.localizedDescription) throw SupabaseAuthError.signUpFailed } } From db67f6c3c665ad723db9f224526d383c0b866a64 Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:28:22 +0900 Subject: [PATCH 17/18] =?UTF-8?q?[chore]=20#85=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 앱 프로젝트 extension(UIViewController+)와의 의존성 제거 --- SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift b/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift index 1ebdb4ce..b2bbb2ba 100644 --- a/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift +++ b/SniffMeet/SniffMeet/Source/Core/OAuth/GoogleAuthManager.swift @@ -17,7 +17,7 @@ final class GoogleAuthManager: GoogleAuthManageable { @MainActor func signIn() async throws -> String { - guard let rootViewController = UIViewController.topMostViewController else { + guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { throw GoogleAuthError.viewHierarchyNotFound } do { From 9c1f0854376d42172e40ca8cf0ca6db6230228ea Mon Sep 17 00:00:00 2001 From: Hyemin Heo Date: Wed, 14 May 2025 18:55:56 +0900 Subject: [PATCH 18/18] =?UTF-8?q?[chore]=20#85=20SceneTests=EC=97=90=20Goo?= =?UTF-8?q?gleSignIn=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SniffMeet/SniffMeet.xcodeproj/project.pbxproj | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/SniffMeet/SniffMeet.xcodeproj/project.pbxproj b/SniffMeet/SniffMeet.xcodeproj/project.pbxproj index a5710a3b..9ad519b3 100644 --- a/SniffMeet/SniffMeet.xcodeproj/project.pbxproj +++ b/SniffMeet/SniffMeet.xcodeproj/project.pbxproj @@ -494,10 +494,8 @@ FD14C2602DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */; }; FD14C2612DD472DE00AC9ABB /* SupabaseAuthTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C25E2DD472D700AC9ABB /* SupabaseAuthTypes.swift */; }; FD14C26C2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */; }; - FD14C26D2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */; }; FD14C26E2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26B2DD4736600AC9ABB /* GoogleAuthManager.swift */; }; FD14C2702DD4737900AC9ABB /* AppleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */; }; - FD14C2712DD4737900AC9ABB /* AppleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */; }; FD14C2722DD4737900AC9ABB /* AppleAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C26F2DD4737900AC9ABB /* AppleAuthManager.swift */; }; FD14C2742DD4739F00AC9ABB /* SignInEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2732DD4739F00AC9ABB /* SignInEmailUsecase.swift */; }; FD14C2752DD4739F00AC9ABB /* SignInEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2732DD4739F00AC9ABB /* SignInEmailUsecase.swift */; }; @@ -513,6 +511,8 @@ FD14C2842DD473D900AC9ABB /* SignUpEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2822DD473D900AC9ABB /* SignUpEmailUsecase.swift */; }; FD14C2872DD4741800AC9ABB /* OpenIDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2862DD4741800AC9ABB /* OpenIDProvider.swift */; }; FD14C2882DD4741800AC9ABB /* OpenIDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD14C2862DD4741800AC9ABB /* OpenIDProvider.swift */; }; + FD14C2F22DD49FD300AC9ABB /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = FD14C2F12DD49FD300AC9ABB /* GoogleSignIn */; }; + FD14C2F42DD49FD300AC9ABB /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD14C2F32DD49FD300AC9ABB /* GoogleSignInSwift */; }; FD159A7D2D35166D00B425D9 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD880B612CF433AC0093BEB9 /* Environment.swift */; }; FD243CFA2D621FC600971162 /* SNMProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD243CF92D621FC200971162 /* SNMProgressView.swift */; }; FD243CFB2D621FC600971162 /* SNMProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD243CF92D621FC200971162 /* SNMProgressView.swift */; }; @@ -993,7 +993,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FD14C2F42DD49FD300AC9ABB /* GoogleSignInSwift in Frameworks */, 32BD48FB2D40B5E50078DA9C /* OrderedCollections in Frameworks */, + FD14C2F22DD49FD300AC9ABB /* GoogleSignIn in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1850,6 +1852,13 @@ path = Auth; sourceTree = ""; }; + FD14C28A2DD497AD00AC9ABB /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; FD243CF82D621FBA00971162 /* SNMToast */ = { isa = PBXGroup; children = ( @@ -1912,6 +1921,7 @@ FD331A6F2CF33C7B00F39426 /* SupabaseTests */, 32BD48322D3FECAE0078DA9C /* SNMSceneTests */, A24B1F352D3C338B0012B860 /* SNMUtilityTests */, + FD14C28A2DD497AD00AC9ABB /* Frameworks */, FD3A03212CD8CDE50047B7ED /* Products */, ); sourceTree = ""; @@ -2663,6 +2673,8 @@ name = SNMSceneTests; packageProductDependencies = ( 32BD48FA2D40B5E50078DA9C /* OrderedCollections */, + FD14C2F12DD49FD300AC9ABB /* GoogleSignIn */, + FD14C2F32DD49FD300AC9ABB /* GoogleSignInSwift */, ); productName = SNMSceneTests; productReference = 32BD48282D3FEC9D0078DA9C /* SNMSceneTests.xctest */; @@ -3197,8 +3209,6 @@ FD159A7D2D35166D00B425D9 /* Environment.swift in Sources */, FD331A8B2CF33E7100F39426 /* UserDefaultsManager.swift in Sources */, FD331A8C2CF33E7100F39426 /* KeychainManager.swift in Sources */, - FD14C26D2DD4736600AC9ABB /* GoogleAuthManager.swift in Sources */, - FD14C2712DD4737900AC9ABB /* AppleAuthManager.swift in Sources */, A2FE77742D54ACE800239F05 /* SupabaseDBRequestBuilder.swift in Sources */, FD331A7D2CF33D0500F39426 /* SupabaseStorageRequest.swift in Sources */, FD331A7E2CF33D0500F39426 /* SupabaseConfig.swift in Sources */, @@ -4195,6 +4205,16 @@ package = FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; productName = GoogleSignInSwift; }; + FD14C2F12DD49FD300AC9ABB /* GoogleSignIn */ = { + isa = XCSwiftPackageProductDependency; + package = FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignIn; + }; + FD14C2F32DD49FD300AC9ABB /* GoogleSignInSwift */ = { + isa = XCSwiftPackageProductDependency; + package = FD14C2552DD4727D00AC9ABB /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignInSwift; + }; FDC5D9BC2D5C7E0400380533 /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; package = FDC5D9BB2D5C7E0400380533 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;