Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,6 @@ public class AppleProviderSwift: AuthProviderSwift {

return credential
}

public func deleteUser(user: User) async throws {
let operation = ProviderDeleteUserOperation(provider: self)
try await operation(on: user)
}
}

public class AppleProviderAuthUI: AuthProviderUI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

@preconcurrency import FirebaseAuth
import Observation

@MainActor
protocol EmailPasswordOperationReauthentication {
var passwordPrompt: PasswordPromptCoordinator { get }
}

extension EmailPasswordOperationReauthentication {
func reauthenticate() async throws {
guard let user = Auth.auth().currentUser else {
throw AuthServiceError.reauthenticationRequired("No user currently signed-in")
}

guard let email = user.email else {
throw AuthServiceError.invalidCredentials("User does not have an email address")
}

do {
let password = try await passwordPrompt.confirmPassword()

let credential = EmailAuthProvider.credential(withEmail: email, password: password)
_ = try await Auth.auth().currentUser?.reauthenticate(with: credential)
} catch {
throw AuthServiceError.signInFailed(underlying: error)
}
}
}

@MainActor
class EmailPasswordDeleteUserOperation: AuthenticatedOperation,
EmailPasswordOperationReauthentication {
let passwordPrompt: PasswordPromptCoordinator

init(passwordPrompt: PasswordPromptCoordinator) {
self.passwordPrompt = passwordPrompt
}

func callAsFunction(on user: User) async throws {
try await callAsFunction(on: user) {
try await user.delete()
}
}
}

class EmailPasswordUpdatePasswordOperation: AuthenticatedOperation,
EmailPasswordOperationReauthentication {
let passwordPrompt: PasswordPromptCoordinator
let newPassword: String

init(passwordPrompt: PasswordPromptCoordinator, newPassword: String) {
self.passwordPrompt = passwordPrompt
self.newPassword = newPassword
}

func callAsFunction(on user: User) async throws {
try await callAsFunction(on: user) {
try await user.updatePassword(to: newPassword)
}
}
}

/// Coordinator for prompting users to enter their password during reauthentication flows
@MainActor
@Observable
public final class PasswordPromptCoordinator {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import SwiftUI

public protocol AuthProviderSwift {
@MainActor func createAuthCredential() async throws -> AuthCredential
@MainActor func deleteUser(user: User) async throws
}

public protocol AuthProviderUI {
Expand Down Expand Up @@ -188,7 +187,13 @@ public final class AuthService {
public func linkAccounts(credentials credentials: AuthCredential) async throws {
authenticationState = .authenticating
do {
try await currentUser?.link(with: credentials)
guard let user = currentUser else {
throw AuthServiceError.noCurrentUser
}

try await withReauthenticationIfNeeded(on: user) {
try await user.link(with: credentials)
}
updateAuthenticationState()
} catch {
authenticationState = .unauthenticated
Expand Down Expand Up @@ -277,18 +282,12 @@ public final class AuthService {
public extension AuthService {
func deleteUser() async throws {
do {
if let user = auth.currentUser, let providerId = signedInCredential?.provider {
if providerId == EmailAuthProviderID {
let operation = EmailPasswordDeleteUserOperation(passwordPrompt: passwordPrompt)
try await operation(on: user)
} else {
// Find provider by matching ID
guard let matchingProvider = providers.first(where: { $0.id == providerId }) else {
throw AuthServiceError.providerNotFound("No provider found for \(providerId)")
}
guard let user = auth.currentUser else {
throw AuthServiceError.noCurrentUser
}

try await matchingProvider.provider.deleteUser(user: user)
}
try await withReauthenticationIfNeeded(on: user) {
try await user.delete()
}
} catch {
updateError(message: string.localizedErrorMessage(for: error))
Expand All @@ -298,14 +297,13 @@ public extension AuthService {

func updatePassword(to password: String) async throws {
do {
if let user = auth.currentUser {
let operation = EmailPasswordUpdatePasswordOperation(
passwordPrompt: passwordPrompt,
newPassword: password
)
try await operation(on: user)
guard let user = auth.currentUser else {
throw AuthServiceError.noCurrentUser
}

try await withReauthenticationIfNeeded(on: user) {
try await user.updatePassword(to: password)
}
} catch {
updateError(message: string.localizedErrorMessage(for: error))
throw error
Expand Down Expand Up @@ -702,7 +700,9 @@ public extension AuthService {
}

// Complete the enrollment
try await user.multiFactor.enroll(with: assertion, displayName: displayName)
try await withReauthenticationIfNeeded(on: user) {
try await user.multiFactor.enroll(with: assertion, displayName: displayName)
}
currentUser = auth.currentUser
} catch {
updateError(message: string.localizedErrorMessage(for: error))
Expand Down Expand Up @@ -731,6 +731,22 @@ public extension AuthService {
}
}

private func withReauthenticationIfNeeded(on user: User,
operation: () async throws -> Void) async throws {
do {
try await operation()
} catch let error as NSError {
if error.domain == AuthErrorDomain,
error.code == AuthErrorCode.requiresRecentLogin.rawValue || error.code == AuthErrorCode
.userTokenExpired.rawValue {
try await reauthenticateCurrentUser(on: user)
try await operation()
} else {
throw error
}
}
}

func unenrollMFA(_ factorUid: String) async throws -> [MultiFactorInfo] {
do {
guard let user = auth.currentUser else {
Expand All @@ -739,20 +755,8 @@ public extension AuthService {

let multiFactorUser = user.multiFactor

do {
try await withReauthenticationIfNeeded(on: user) {
try await multiFactorUser.unenroll(withFactorUID: factorUid)
} catch let error as NSError {
if error.domain == AuthErrorDomain,
error.code == AuthErrorCode.requiresRecentLogin.rawValue || error.code == AuthErrorCode
.userTokenExpired.rawValue {
try await reauthenticateCurrentUser(on: user)
try await multiFactorUser.unenroll(withFactorUID: factorUid)
} else {
throw AuthServiceError
.multiFactorAuth(
"Invalid second factor: \(error.localizedDescription)"
)
}
}

// This is the only we to get the actual latest enrolledFactors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ public class FacebookProviderSwift: AuthProviderSwift {
)
}
}

public func deleteUser(user: User) async throws {
let operation = ProviderDeleteUserOperation(provider: self)
try await operation(on: user)
}
}

public class FacebookProviderAuthUI: AuthProviderUI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,6 @@ public class GoogleProviderSwift: AuthProviderSwift {
}
}
}

public func deleteUser(user: User) async throws {
let operation = ProviderDeleteUserOperation(provider: self)
try await operation(on: user)
}
}

public class GoogleProviderAuthUI: AuthProviderUI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,6 @@ public class OAuthProviderSwift: AuthProviderSwift {
}
}
}

public func deleteUser(user: User) async throws {
let operation = ProviderDeleteUserOperation(provider: self)
try await operation(on: user)
}
}

public class OAuthProviderAuthUI: AuthProviderUI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ public class PhoneProviderSwift: PhoneAuthProviderSwift {
presentingViewController.present(hostingController, animated: true)
}
}

public func deleteUser(user: User) async throws {
let operation = ProviderDeleteUserOperation(provider: self)
try await operation(on: user)
}
}

public class PhoneAuthProviderAuthUI: AuthProviderUI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ public class TwitterProviderSwift: AuthProviderSwift {
}
}
}

public func deleteUser(user: User) async throws {
let operation = ProviderDeleteUserOperation(provider: self)
try await operation(on: user)
}
}

public class TwitterProviderAuthUI: AuthProviderUI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ final class MFAEnrollmentUITests: XCTestCase {
}

// MARK: - Helper Methods

@MainActor
private func signInToApp(app: XCUIApplication, email: String) throws {
let password = "123456"

Expand Down Expand Up @@ -436,7 +436,7 @@ final class MFAEnrollmentUITests: XCTestCase {
XCTAssertTrue(signedInText.waitForExistence(timeout: 30), "SignedInView should be visible after login")
XCTAssertTrue(signedInText.exists, "SignedInView should be visible after login")
}

@MainActor
private func navigateToMFAEnrollment(app: XCUIApplication) throws {
// Navigate to MFA management
app.buttons["mfa-management-button"].tap()
Expand Down
Loading