diff --git a/DevLog/Data/Repository/AuthDataRepositoryImpl.swift b/DevLog/Data/Repository/AuthDataRepositoryImpl.swift index e580a044..47eb308d 100644 --- a/DevLog/Data/Repository/AuthDataRepositoryImpl.swift +++ b/DevLog/Data/Repository/AuthDataRepositoryImpl.swift @@ -33,7 +33,7 @@ final class AuthDataRepositoryImpl: AuthDataRepository { } func fetchAllProviders() async throws -> [AuthProvider] { - let providerStrings = authService.providerIDs ?? [] + let providerStrings = authService.providerIDs return providerStrings.compactMap { AuthProvider(rawValue: $0) } } diff --git a/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift b/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift index 54fc5681..ebb1b651 100644 --- a/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift +++ b/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift @@ -63,20 +63,25 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { } func delete() async throws { - guard let uid = authService.uid, - let providerID = try await authService.getProviderID(), - let provider = AuthProvider(rawValue: providerID) - else { + guard let uid = authService.uid else { throw AuthError.notAuthenticated } - switch provider { - case .apple: - try await appleAuthService.deleteAuth(uid) - case .github: - try await githubAuthService.deleteAuth(uid) - case .google: - try await googleAuthService.deleteAuth(uid) + let providers = authService.providerIDs.compactMap { AuthProvider(rawValue: $0) } + + for provider in providers { + switch provider { + case .apple: + try await appleAuthService.deleteAuth(uid) + case .github: + try await githubAuthService.deleteAuth(uid) + case .google: + try await googleAuthService.deleteAuth(uid) + } } + + try await authService.deleteFirestoreUserData() + try await authService.deleteCurrentUser() + try await authService.clearCurrentSession() } } diff --git a/DevLog/Infra/Service/AuthService.swift b/DevLog/Infra/Service/AuthService.swift index c47e5645..1368e8df 100644 --- a/DevLog/Infra/Service/AuthService.swift +++ b/DevLog/Infra/Service/AuthService.swift @@ -7,17 +7,21 @@ import FirebaseAuth import FirebaseFirestore +import FirebaseFunctions +import FirebaseMessaging final class AuthService { private let store = Firestore.firestore() + private let functions = Functions.functions(region: "asia-northeast3") + private let messaging = Messaging.messaging() private let logger = Logger(category: "AuthService") var uid: String? { Auth.auth().currentUser?.uid } - var providerIDs: [String]? { - Auth.auth().currentUser?.providerData.map { $0.providerID } + var providerIDs: [String] { + Auth.auth().currentUser?.providerData.map { $0.providerID } ?? [] } func getProviderID() async throws -> String? { @@ -42,4 +46,33 @@ final class AuthService { throw error } } + + func deleteFirestoreUserData() async throws { + logger.info("Deleting Firestore user data") + + let deleteFunction = functions.httpsCallable("deleteUserFirestoreData") + _ = try await deleteFunction.call() + } + + func deleteCurrentUser() async throws { + logger.info("Deleting FirebaseAuth current user") + + guard let currentUser = Auth.auth().currentUser else { + logger.warning("No current user to delete") + throw AuthError.notAuthenticated + } + + try await currentUser.delete() + } + + func clearCurrentSession() async throws { + logger.info("Clearing current auth session") + + do { + try await messaging.deleteToken() + } catch { + logger.error("Failed to delete FCM token while clearing session", error: error) + } + try Auth.auth().signOut() + } } diff --git a/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift index 635131df..19b9f5cd 100644 --- a/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift @@ -105,14 +105,6 @@ final class AppleAuthenticationService: AuthenticationService { let token = try await refreshAppleAccessToken() try await revokeAppleAccessToken(token: token) - - let deleteFunction = functions.httpsCallable("deleteUserFirestoreData") - - _ = try await deleteFunction.call(["uid": uid]) - - try await user?.delete() - try await messaging.deleteToken() - try Auth.auth().signOut() } func link(uid: String, email: String) async throws { diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index d9aeaa20..77265ac1 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -82,14 +82,6 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { func deleteAuth(_ uid: String) async throws { try await revokeAccessToken() - - let deleteFunction = functions.httpsCallable("deleteUserFirestoreData") - - _ = try await deleteFunction.call(["uid": uid]) - - try await user?.delete() - try await messaging.deleteToken() - try Auth.auth().signOut() } func link(uid: String, email: String) async throws { diff --git a/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift index 3fb15f44..0d094154 100644 --- a/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift @@ -78,15 +78,8 @@ final class GoogleAuthenticationService: AuthenticationService { } func deleteAuth(_ uid: String) async throws { - let deleteFunction = functions.httpsCallable("deleteUserFirestoreData") - - _ = try await deleteFunction.call(["uid": uid]) - - try await user?.delete() GIDSignIn.sharedInstance.signOut() try await GIDSignIn.sharedInstance.disconnect() - try await messaging.deleteToken() - try Auth.auth().signOut() } func link(uid: String, email: String) async throws { diff --git a/Firebase/functions/src/user/delete.ts b/Firebase/functions/src/user/delete.ts index 0ca9dfd2..80ee4d98 100644 --- a/Firebase/functions/src/user/delete.ts +++ b/Firebase/functions/src/user/delete.ts @@ -1,5 +1,6 @@ import { onCall, HttpsError } from "firebase-functions/v2/https"; import * as admin from "firebase-admin"; +import * as logger from "firebase-functions/logger"; export const deleteUserFirestoreData = onCall({ cors: true, @@ -7,9 +8,14 @@ export const deleteUserFirestoreData = onCall({ region: "asia-northeast3" }, async (request) => { - if (!request.auth) throw new HttpsError("unauthenticated", "로그인 필요"); - const uid = request.data.uid; - if (!uid) throw new HttpsError("invalid-argument", "uid 필요"); + if (!request.auth?.uid) { + logger.error("deleteUserFirestoreData called without authenticated uid", { + auth: request.auth ?? null + }); + throw new HttpsError("unauthenticated", "로그인 필요"); + } + + const uid = request.auth.uid; try { const userDocRef = admin.firestore().doc(`users/${uid}`);