Skip to content

Commit 380a299

Browse files
refactor: remove navigation push inside AuthService. move to View layer
1 parent e2797a3 commit 380a299

File tree

10 files changed

+126
-9
lines changed

10 files changed

+126
-9
lines changed

FirebaseSwiftUI/FirebaseAppleSwiftUI/Sources/Views/SignInWithAppleButton.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SwiftUI
2121
public struct SignInWithAppleButton {
2222
@Environment(AuthService.self) private var authService
2323
@Environment(\.accountConflictHandler) private var accountConflictHandler
24+
@Environment(\.mfaHandler) private var mfaHandler
2425
@Environment(\.reportError) private var reportError
2526
let provider: AppleProviderSwift
2627
public init(provider: AppleProviderSwift) {
@@ -37,7 +38,14 @@ extension SignInWithAppleButton: View {
3738
) {
3839
Task {
3940
do {
40-
_ = try await authService.signIn(provider)
41+
let outcome = try await authService.signIn(provider)
42+
43+
// Handle MFA at view level
44+
if case let .mfaRequired(mfaInfo) = outcome,
45+
let onMFA = mfaHandler {
46+
onMFA(mfaInfo)
47+
return
48+
}
4149
} catch {
4250
reportError?(error)
4351

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,6 @@ public extension AuthService {
881881
let hints = extractMFAHints(from: resolver)
882882
currentMFARequired = MFARequired(hints: hints)
883883
currentMFAResolver = resolver
884-
navigator.push(.mfaResolution)
885884
return .mfaRequired(MFARequired(hints: hints))
886885
}
887886

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AccountConflictModifier.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public extension EnvironmentValues {
3232
@MainActor
3333
struct AccountConflictModifier: ViewModifier {
3434
@Environment(AuthService.self) private var authService
35+
@Environment(\.mfaHandler) private var mfaHandler
3536
@Environment(\.reportError) private var reportError
3637
@State private var pendingCredentialForLinking: AuthCredential?
3738

@@ -56,7 +57,13 @@ struct AccountConflictModifier: ViewModifier {
5657
try await authService.signOut()
5758

5859
// Sign in with the new credential
59-
_ = try await authService.signIn(credentials: conflict.credential)
60+
let outcome = try await authService.signIn(credentials: conflict.credential)
61+
62+
// Handle MFA at view level
63+
if case let .mfaRequired(mfaInfo) = outcome,
64+
let onMFA = mfaHandler {
65+
onMFA(mfaInfo)
66+
}
6067
} catch {
6168
// Report error to parent view for display
6269
reportError?(error)

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ extension AuthPickerView: View {
7676
.interactiveDismissDisabled(authService.configuration.interactiveDismissEnabled)
7777
// Apply account conflict handling at NavigationStack level
7878
.accountConflictHandler()
79+
// Apply MFA handling at NavigationStack level
80+
.mfaHandler()
7981
}
8082
}
8183

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ private enum FocusableField: Hashable {
3333
public struct EmailAuthView {
3434
@Environment(AuthService.self) private var authService
3535
@Environment(\.accountConflictHandler) private var accountConflictHandler
36+
@Environment(\.mfaHandler) private var mfaHandler
3637
@Environment(\.reportError) private var reportError
3738

3839
@State private var email = ""
@@ -53,7 +54,14 @@ public struct EmailAuthView {
5354

5455
private func signInWithEmailPassword() async throws {
5556
do {
56-
_ = try await authService.signIn(email: email, password: password)
57+
let outcome = try await authService.signIn(email: email, password: password)
58+
59+
// Handle MFA at view level
60+
if case let .mfaRequired(mfaInfo) = outcome,
61+
let onMFA = mfaHandler {
62+
onMFA(mfaInfo)
63+
return
64+
}
5765
} catch {
5866
reportError?(error)
5967

@@ -69,7 +77,14 @@ public struct EmailAuthView {
6977

7078
private func createUserWithEmailPassword() async throws {
7179
do {
72-
_ = try await authService.createUser(email: email, password: password)
80+
let outcome = try await authService.createUser(email: email, password: password)
81+
82+
// Handle MFA at view level
83+
if case let .mfaRequired(mfaInfo) = outcome,
84+
let onMFA = mfaHandler {
85+
onMFA(mfaInfo)
86+
return
87+
}
7388
} catch {
7489
reportError?(error)
7590

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAuth
16+
import SwiftUI
17+
18+
/// Environment key for accessing the MFA handler
19+
public struct MFAHandlerKey: @preconcurrency EnvironmentKey {
20+
@MainActor public static let defaultValue: ((MFARequired) -> Void)? = nil
21+
}
22+
23+
public extension EnvironmentValues {
24+
var mfaHandler: ((MFARequired) -> Void)? {
25+
get { self[MFAHandlerKey.self] }
26+
set { self[MFAHandlerKey.self] = newValue }
27+
}
28+
}
29+
30+
/// View modifier that handles MFA requirements at the view layer
31+
/// Automatically navigates to MFA resolution when MFA is required
32+
@MainActor
33+
struct MFAHandlerModifier: ViewModifier {
34+
@Environment(AuthService.self) private var authService
35+
36+
func body(content: Content) -> some View {
37+
content
38+
.environment(\.mfaHandler, handleMFARequired)
39+
}
40+
41+
/// Handle MFA required - navigate to MFA resolution view
42+
func handleMFARequired(_: MFARequired) {
43+
authService.navigator.push(.mfaResolution)
44+
}
45+
}
46+
47+
extension View {
48+
/// Adds MFA handling to the view hierarchy
49+
/// Should be applied at the NavigationStack level to handle MFA requirements throughout the auth
50+
/// flow
51+
func mfaHandler() -> some View {
52+
modifier(MFAHandlerModifier())
53+
}
54+
}

FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Views/SignInWithFacebookButton.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import SwiftUI
2323
public struct SignInWithFacebookButton {
2424
@Environment(AuthService.self) private var authService
2525
@Environment(\.accountConflictHandler) private var accountConflictHandler
26+
@Environment(\.mfaHandler) private var mfaHandler
2627
@Environment(\.reportError) private var reportError
2728
let facebookProvider: FacebookProviderSwift
2829

@@ -40,7 +41,14 @@ extension SignInWithFacebookButton: View {
4041
) {
4142
Task {
4243
do {
43-
_ = try await authService.signIn(facebookProvider)
44+
let outcome = try await authService.signIn(facebookProvider)
45+
46+
// Handle MFA at view level
47+
if case let .mfaRequired(mfaInfo) = outcome,
48+
let onMFA = mfaHandler {
49+
onMFA(mfaInfo)
50+
return
51+
}
4452
} catch {
4553
reportError?(error)
4654

FirebaseSwiftUI/FirebaseGoogleSwiftUI/Sources/Views/SignInWithGoogleButton.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import SwiftUI
2727
public struct SignInWithGoogleButton {
2828
@Environment(AuthService.self) private var authService
2929
@Environment(\.accountConflictHandler) private var accountConflictHandler
30+
@Environment(\.mfaHandler) private var mfaHandler
3031
@Environment(\.reportError) private var reportError
3132
let googleProvider: GoogleProviderSwift
3233

@@ -44,7 +45,14 @@ extension SignInWithGoogleButton: View {
4445
) {
4546
Task {
4647
do {
47-
_ = try await authService.signIn(googleProvider)
48+
let outcome = try await authService.signIn(googleProvider)
49+
50+
// Handle MFA at view level
51+
if case let .mfaRequired(mfaInfo) = outcome,
52+
let onMFA = mfaHandler {
53+
onMFA(mfaInfo)
54+
return
55+
}
4856
} catch {
4957
reportError?(error)
5058

FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SwiftUI
2121
public struct GenericOAuthButton {
2222
@Environment(AuthService.self) private var authService
2323
@Environment(\.accountConflictHandler) private var accountConflictHandler
24+
@Environment(\.mfaHandler) private var mfaHandler
2425
@Environment(\.reportError) private var reportError
2526
let provider: OAuthProviderSwift
2627
public init(provider: OAuthProviderSwift) {
@@ -47,7 +48,14 @@ extension GenericOAuthButton: View {
4748
) {
4849
Task {
4950
do {
50-
_ = try await authService.signIn(provider)
51+
let outcome = try await authService.signIn(provider)
52+
53+
// Handle MFA at view level
54+
if case let .mfaRequired(mfaInfo) = outcome,
55+
let onMFA = mfaHandler {
56+
onMFA(mfaInfo)
57+
return
58+
}
5159
} catch {
5260
reportError?(error)
5361

FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources/Views/SignInWithTwitterButton.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SwiftUI
2121
public struct SignInWithTwitterButton {
2222
@Environment(AuthService.self) private var authService
2323
@Environment(\.accountConflictHandler) private var accountConflictHandler
24+
@Environment(\.mfaHandler) private var mfaHandler
2425
@Environment(\.reportError) private var reportError
2526
let provider: TwitterProviderSwift
2627
public init(provider: TwitterProviderSwift) {
@@ -37,7 +38,14 @@ extension SignInWithTwitterButton: View {
3738
) {
3839
Task {
3940
do {
40-
_ = try await authService.signIn(provider)
41+
let outcome = try await authService.signIn(provider)
42+
43+
// Handle MFA at view level
44+
if case let .mfaRequired(mfaInfo) = outcome,
45+
let onMFA = mfaHandler {
46+
onMFA(mfaInfo)
47+
return
48+
}
4149
} catch {
4250
reportError?(error)
4351

0 commit comments

Comments
 (0)