Skip to content

Commit 9ef04f9

Browse files
refactor: reauth helpers
1 parent 13b93d2 commit 9ef04f9

File tree

2 files changed

+93
-68
lines changed

2 files changed

+93
-68
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,20 @@ public final class AuthService {
217217

218218
try await user.link(with: credentials)
219219
updateAuthenticationState()
220-
} catch {
220+
} catch let error as NSError {
221+
authenticationState = .unauthenticated
222+
223+
// Check if reauthentication is needed
224+
if error.domain == AuthErrorDomain,
225+
error.code == AuthErrorCode.requiresRecentLogin.rawValue ||
226+
error.code == AuthErrorCode.userTokenExpired.rawValue {
227+
try await requireReauthentication()
228+
}
229+
221230
// Possible conflicts from user.link():
222231
// - credentialAlreadyInUse: credential is already linked to another account
223232
// - emailAlreadyInUse: email from credential is already used by another account
224233
// - accountExistsWithDifferentCredential: account exists with different sign-in method
225-
authenticationState = .unauthenticated
226234
try handleErrorWithConflictCheck(error: error, credential: credentials)
227235
}
228236
}
@@ -293,15 +301,37 @@ public extension AuthService {
293301
throw AuthServiceError.noCurrentUser
294302
}
295303

296-
try await user.delete()
304+
do {
305+
try await user.delete()
306+
} catch let error as NSError {
307+
// Check if reauthentication is needed
308+
if error.domain == AuthErrorDomain,
309+
error.code == AuthErrorCode.requiresRecentLogin.rawValue ||
310+
error.code == AuthErrorCode.userTokenExpired.rawValue {
311+
try await requireReauthentication()
312+
} else {
313+
throw error
314+
}
315+
}
297316
}
298317

299318
func updatePassword(to password: String) async throws {
300319
guard let user = auth.currentUser else {
301320
throw AuthServiceError.noCurrentUser
302321
}
303322

304-
try await user.updatePassword(to: password)
323+
do {
324+
try await user.updatePassword(to: password)
325+
} catch let error as NSError {
326+
// Check if reauthentication is needed
327+
if error.domain == AuthErrorDomain,
328+
error.code == AuthErrorCode.requiresRecentLogin.rawValue ||
329+
error.code == AuthErrorCode.userTokenExpired.rawValue {
330+
try await requireReauthentication()
331+
} else {
332+
throw error
333+
}
334+
}
305335
}
306336
}
307337

@@ -694,8 +724,19 @@ public extension AuthService {
694724
}
695725

696726
// Complete the enrollment
697-
try await user.multiFactor.enroll(with: assertion, displayName: displayName)
698-
currentUser = auth.currentUser
727+
do {
728+
try await user.multiFactor.enroll(with: assertion, displayName: displayName)
729+
currentUser = auth.currentUser
730+
} catch let error as NSError {
731+
// Check if reauthentication is needed
732+
if error.domain == AuthErrorDomain,
733+
error.code == AuthErrorCode.requiresRecentLogin.rawValue ||
734+
error.code == AuthErrorCode.userTokenExpired.rawValue {
735+
try await requireReauthentication()
736+
} else {
737+
throw error
738+
}
739+
}
699740
}
700741

701742
/// Reauthenticates with a simple provider (Google, Apple, Facebook, Twitter, etc.)
@@ -812,13 +853,24 @@ public extension AuthService {
812853

813854
let multiFactorUser = user.multiFactor
814855

815-
try await multiFactorUser.unenroll(withFactorUID: factorUid)
856+
do {
857+
try await multiFactorUser.unenroll(withFactorUID: factorUid)
816858

817-
// This is the only we to get the actual latest enrolledFactors
818-
currentUser = Auth.auth().currentUser
819-
let freshFactors = currentUser?.multiFactor.enrolledFactors ?? []
859+
// This is the only we to get the actual latest enrolledFactors
860+
currentUser = Auth.auth().currentUser
861+
let freshFactors = currentUser?.multiFactor.enrolledFactors ?? []
820862

821-
return freshFactors
863+
return freshFactors
864+
} catch let error as NSError {
865+
// Check if reauthentication is needed
866+
if error.domain == AuthErrorDomain,
867+
error.code == AuthErrorCode.requiresRecentLogin.rawValue ||
868+
error.code == AuthErrorCode.userTokenExpired.rawValue {
869+
try await requireReauthentication()
870+
} else {
871+
throw error
872+
}
873+
}
822874
}
823875

824876
// MARK: - Account Conflict Helper Methods

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/ReauthenticationHelpers.swift

Lines changed: 30 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,71 +15,44 @@
1515
import FirebaseAuth
1616

1717
/// Execute an operation that may require reauthentication
18-
/// Automatically handles simple providers (Google, Apple, etc.)
19-
/// For email/phone, the coordinator must be provided to handle the UI flow
18+
/// Automatically handles reauth errors by presenting UI and retrying
2019
/// - Parameters:
2120
/// - authService: The auth service managing authentication
22-
/// - coordinator: The coordinator managing reauthentication UI (for email/phone)
21+
/// - coordinator: The coordinator managing reauthentication UI
2322
/// - operation: The operation to execute
2423
/// - Throws: Rethrows errors from the operation or reauthentication process
2524
@MainActor
26-
public func withReauthenticationIfNeeded(authService: AuthService,
27-
coordinator: ReauthenticationCoordinator,
28-
operation: @escaping () async throws
29-
-> Void) async throws {
25+
public func withReauthenticationIfNeeded(
26+
authService: AuthService,
27+
coordinator: ReauthenticationCoordinator,
28+
operation: @escaping () async throws -> Void
29+
) async throws {
3030
do {
3131
try await operation()
32-
} catch let error as NSError {
33-
// Check if reauthentication is needed
34-
if error.domain == AuthErrorDomain,
35-
error.code == AuthErrorCode.requiresRecentLogin.rawValue ||
36-
error.code == AuthErrorCode.userTokenExpired.rawValue {
37-
// Determine the provider context
38-
let providerId = try await authService.getCurrentSignInProvider()
39-
let context = ReauthContext(
40-
providerId: providerId,
41-
providerName: getProviderDisplayName(providerId),
42-
phoneNumber: authService.currentUser?.phoneNumber,
43-
email: authService.currentUser?.email
44-
)
45-
46-
// Handle based on provider type
47-
switch providerId {
48-
case PhoneAuthProviderID, EmailAuthProviderID:
49-
// For email/phone, use coordinator to handle UI flow
50-
try await coordinator.requestReauth(context: context)
51-
52-
default:
53-
// For simple providers (Google, Apple, etc.), AuthService can handle it directly
54-
try await authService.reauthenticate(context: context)
55-
}
56-
57-
// Retry the operation after successful reauth
58-
try await operation()
59-
} else {
32+
} catch let error as AuthServiceError {
33+
// Check if this is a reauthentication error
34+
let context: ReauthContext?
35+
36+
switch error {
37+
case .emailReauthenticationRequired(let ctx):
38+
context = ctx
39+
case .phoneReauthenticationRequired(let ctx):
40+
context = ctx
41+
case .simpleReauthenticationRequired(let ctx):
42+
context = ctx
43+
default:
44+
// Not a reauth error, rethrow
6045
throw error
6146
}
62-
}
63-
}
64-
65-
/// Get a user-friendly display name for a provider ID
66-
/// - Parameter providerId: The provider ID from Firebase Auth
67-
/// - Returns: A user-friendly name for the provider
68-
public func getProviderDisplayName(_ providerId: String) -> String {
69-
switch providerId {
70-
case EmailAuthProviderID:
71-
return "Email"
72-
case PhoneAuthProviderID:
73-
return "Phone"
74-
case "google.com":
75-
return "Google"
76-
case "apple.com":
77-
return "Apple"
78-
case "facebook.com":
79-
return "Facebook"
80-
case "twitter.com":
81-
return "Twitter"
82-
default:
83-
return providerId
47+
48+
guard let reauthContext = context else {
49+
throw error
50+
}
51+
52+
// Request reauthentication through coordinator (shows UI)
53+
try await coordinator.requestReauth(context: reauthContext)
54+
55+
// After successful reauth, retry the operation
56+
try await operation()
8457
}
8558
}

0 commit comments

Comments
 (0)