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 @@ -8,6 +8,7 @@ public protocol ExternalAuthProvider {

public protocol GoogleProviderAuthUIProtocol: ExternalAuthProvider {
@MainActor func signInWithGoogle(clientID: String) async throws -> AuthCredential
@MainActor func deleteUser(user: User) async throws
}

public protocol FacebookProviderAuthUIProtocol: ExternalAuthProvider {
Expand Down Expand Up @@ -268,7 +269,7 @@ public final class AuthService {
try await handleAutoUpgradeAnonymousUser(credentials: credentials)
} else {
let result = try await auth.signIn(with: credentials)
signedInCredential = result.credential
signedInCredential = result.credential ?? credentials
}
updateAuthenticationState()
} catch {
Expand Down Expand Up @@ -301,11 +302,13 @@ public extension AuthService {
func deleteUser() async throws {
do {
if let user = auth.currentUser, let providerId = signedInCredential?.provider {
if providerId == "password" {
if providerId == EmailAuthProviderID {
let operation = EmailPasswordDeleteUserOperation(passwordPrompt: passwordPrompt)
try await operation(on: user)
} else if providerId == "facebook.com" {
} else if providerId == FacebookAuthProviderID {
try await facebookProvider.deleteUser(user: user)
} else if providerId == GoogleAuthProviderID {
try await googleProvider.deleteUser(user: user)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// AccountService+Google.swift
// FirebaseUI
//
// Created by Russell Wheatley on 22/05/2025.
//

//
// AccountService+Facebook.swift
// FirebaseUI
//
// Created by Russell Wheatley on 14/05/2025.
//

@preconcurrency import FirebaseAuth
import FirebaseAuthSwiftUI
import Observation

protocol GoogleOperationReauthentication {
var googleProvider: GoogleProviderAuthUI { get }
}

extension GoogleOperationReauthentication {
@MainActor func reauthenticate() async throws -> AuthenticationToken {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follows near identical logic to Facebook for reauthentication.

guard let user = Auth.auth().currentUser else {
throw AuthServiceError.reauthenticationRequired("No user currently signed-in")
}

do {
let credential = try await googleProvider
.signInWithGoogle(clientID: googleProvider.clientID)
try await user.reauthenticate(with: credential)

return .firebase("")
} catch {
throw AuthServiceError.signInFailed(underlying: error)
}
}
}

@MainActor
class GoogleDeleteUserOperation: AuthenticatedOperation,
@preconcurrency GoogleOperationReauthentication {
let googleProvider: GoogleProviderAuthUI
init(googleProvider: GoogleProviderAuthUI) {
self.googleProvider = googleProvider
}

func callAsFunction(on user: User) async throws {
try await callAsFunction(on: user) {
try await user.delete()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,20 @@ public class GoogleProviderAuthUI: @preconcurrency GoogleProviderAuthUIProtocol
let scopes: [String]
let shortName = "Google"
let providerId = "google.com"
let clientID: String
public let clientID: String
public init(scopes: [String]? = nil, clientID: String = FirebaseApp.app()!.options.clientID!) {
self.scopes = scopes ?? kDefaultScopes
self.clientID = clientID
}

@MainActor public func authButton() -> AnyView {
let customViewModel = GoogleSignInButtonViewModel(
scheme: .light,
style: .wide,
state: .normal
)
return AnyView(GoogleSignInButton(viewModel: customViewModel) {
Task {
try await self.signInWithGoogle(clientID: self.clientID)
}
})
// Moved to SignInWithGoogleButton so we could sign in via AuthService
AnyView(SignInWithGoogleButton())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this into separate View, it was directly calling signInWithGoogle which meant it wasn't actually signing in with Firebase and not using AuthService to update currentUser and other aspects of state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider documenting this in a comment since it's unintuitive looking at the code.

}

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

@MainActor public func signInWithGoogle(clientID: String) async throws -> AuthCredential {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// SignInWithGoogleButton.swift
// FirebaseUI
//
// Created by Russell Wheatley on 22/05/2025.
//
import FirebaseAuthSwiftUI
import FirebaseCore
import GoogleSignInSwift
import SwiftUI

@MainActor
public struct SignInWithGoogleButton {
@Environment(AuthService.self) private var authService

let customViewModel = GoogleSignInButtonViewModel(
scheme: .light,
style: .wide,
state: .normal
)
}

extension SignInWithGoogleButton: View {
public var body: some View {
GoogleSignInButton(viewModel: customViewModel) {
Task {
try await authService.signInWithGoogle()
}
}
}
}

#Preview {
FirebaseOptions.dummyConfigurationForPreview()
return SignInWithGoogleButton()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ struct ContentView: View {
actionCodeSettings.linkDomain = "flutterfire-e2e-tests.firebaseapp.com"
actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!)
let configuration = AuthConfiguration(
shouldAutoUpgradeAnonymousUsers: true,
tosUrl: URL(string: "https://example.com/tos"),
privacyPolicyUrl: URL(string: "https://example.com/privacy"),
emailLinkSignInActionCodeSettings: actionCodeSettings
Expand Down