-
Notifications
You must be signed in to change notification settings - Fork 485
feat: google reauthenticate logic for sensitive operations such as delete user #1258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2d71e74
58a85ab
f991f96
a2ea416
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 { | ||
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 |
---|---|---|
|
@@ -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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
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()) | ||
} |
There was a problem hiding this comment.
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.