From 08cfd07c46efc59af80e94454bdbcfc894b0da5c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 10 Oct 2025 17:37:28 +0100 Subject: [PATCH 01/16] refactor: phone auth provider --- .../Sources/Services/PhoneAuthProviderAuthUI.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift index be555bdc92..f39d6f1615 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift @@ -77,3 +77,16 @@ public class PhoneAuthProviderAuthUI: AuthProviderUI { AnyView(PhoneAuthButtonView(phoneProvider: provider as! PhoneAuthProviderSwift)) } } + +public class PhoneAuthProviderAuthUI: AuthProviderUI { + public var provider: AuthProviderSwift + public let id: String = "phone.com" + + public init(provider: PhoneAuthProviderSwift? = nil) { + self.provider = provider ?? PhoneProviderSwift() + } + + @MainActor public func authButton() -> AnyView { + AnyView(PhoneAuthButtonView(phoneProvider: provider as! PhoneAuthProviderSwift)) + } +} From c81680b4558e516363809408f5238fc7d56945c0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 11:29:12 +0100 Subject: [PATCH 02/16] feat: initial OAuth setup --- .../Services/AccountService+OAuth.swift | 47 ++++++ .../Sources/Services/AuthService+OAuth.swift | 31 ++++ .../Services/OAuthProviderSwift+Presets.swift | 80 ++++++++++ .../Sources/Services/OAuthProviderSwift.swift | 144 ++++++++++++++++++ .../Sources/Views/GenericOAuthButton.swift | 65 ++++++++ .../FirebaseOAuthSwiftUITests.swift | 21 +++ Package.swift | 16 ++ 7 files changed, 404 insertions(+) create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift new file mode 100644 index 0000000000..aacf856457 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift @@ -0,0 +1,47 @@ +// +// AccountService+OAuth.swift +// FirebaseUI +// +// Created by Russell Wheatley on 21/10/2025. +// + +@preconcurrency import FirebaseAuth +import FirebaseAuthSwiftUI +import Observation + +protocol OAuthOperationReauthentication { + var oauthProvider: OAuthProviderSwift { get } +} + +extension OAuthOperationReauthentication { + @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 oauthProvider.createAuthCredential() + try await user.reauthenticate(with: credential) + + return .firebase("") + } catch { + throw AuthServiceError.signInFailed(underlying: error) + } + } +} + +@MainActor +class OAuthDeleteUserOperation: AuthenticatedOperation, + @preconcurrency OAuthOperationReauthentication { + let oauthProvider: OAuthProviderSwift + init(oauthProvider: OAuthProviderSwift) { + self.oauthProvider = oauthProvider + } + + func callAsFunction(on user: User) async throws { + try await callAsFunction(on: user) { + try await user.delete() + } + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift new file mode 100644 index 0000000000..4189addbba --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift @@ -0,0 +1,31 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// AuthService+OAuth.swift +// FirebaseUI +// +// Created by Russell Wheatley on 21/10/2025. +// + +import FirebaseAuthSwiftUI + +public extension AuthService { + @discardableResult + func withOAuthSignIn(_ provider: OAuthProviderSwift) -> AuthService { + registerProvider(providerWithButton: OAuthProviderAuthUI(provider: provider)) + return self + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift new file mode 100644 index 0000000000..7cdc8be25d --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift @@ -0,0 +1,80 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI + +/// Preset configurations for common OAuth providers +public extension OAuthProviderSwift { + + /// GitHub OAuth provider + /// - Parameters: + /// - scopes: GitHub scopes (default: ["user"]) + /// - Returns: Configured GitHub provider + static func github(scopes: [String] = ["user"]) -> OAuthProviderSwift { + return OAuthProviderSwift( + providerId: "github.com", + scopes: scopes, + displayName: "Sign in with GitHub", + iconSystemName: "chevron.left.forwardslash.chevron.right", + buttonBackgroundColor: .black, + buttonForegroundColor: .white + ) + } + + /// Microsoft OAuth provider + /// - Parameters: + /// - scopes: Microsoft scopes (default: ["openid", "profile", "email"]) + /// - Returns: Configured Microsoft provider + static func microsoft(scopes: [String] = ["openid", "profile", "email"]) -> OAuthProviderSwift { + return OAuthProviderSwift( + providerId: "microsoft.com", + scopes: scopes, + displayName: "Sign in with Microsoft", + iconSystemName: "building.2", + buttonBackgroundColor: Color(red: 0/255, green: 120/255, blue: 212/255), + buttonForegroundColor: .white + ) + } + + /// Yahoo OAuth provider + /// - Parameters: + /// - scopes: Yahoo scopes (default: []) + /// - Returns: Configured Yahoo provider + static func yahoo(scopes: [String] = []) -> OAuthProviderSwift { + return OAuthProviderSwift( + providerId: "yahoo.com", + scopes: scopes, + displayName: "Sign in with Yahoo", + iconSystemName: "y.circle.fill", + buttonBackgroundColor: Color(red: 80/255, green: 0/255, blue: 155/255), + buttonForegroundColor: .white + ) + } + + /// LinkedIn OAuth provider + /// - Parameters: + /// - scopes: LinkedIn scopes (default: ["r_liteprofile", "r_emailaddress"]) + /// - Returns: Configured LinkedIn provider + static func linkedIn(scopes: [String] = ["r_liteprofile", "r_emailaddress"]) -> OAuthProviderSwift { + return OAuthProviderSwift( + providerId: "linkedin.com", + scopes: scopes, + displayName: "Sign in with LinkedIn", + iconSystemName: "link", + buttonBackgroundColor: Color(red: 0/255, green: 119/255, blue: 181/255), + buttonForegroundColor: .white + ) + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift new file mode 100644 index 0000000000..11df97c2c2 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift @@ -0,0 +1,144 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseAuth +import FirebaseAuthSwiftUI +import FirebaseCore +import SwiftUI + +/// Configuration for a generic OAuth provider +public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { + public let providerId: String + public let scopes: [String] + public let customParameters: [String: String] + + // Button appearance + public let displayName: String + public let buttonIcon: Image + public let buttonBackgroundColor: Color + public let buttonForegroundColor: Color + + /// Initialize a generic OAuth provider + /// - Parameters: + /// - providerId: The OAuth provider ID (e.g., "github.com", "microsoft.com") + /// - scopes: OAuth scopes to request + /// - customParameters: Additional OAuth parameters + /// - displayName: Button label (e.g., "Sign in with GitHub") + /// - buttonIcon: Button icon image + /// - buttonBackgroundColor: Button background color + /// - buttonForegroundColor: Button text/icon color + public init( + providerId: String, + scopes: [String] = [], + customParameters: [String: String] = [:], + displayName: String, + buttonIcon: Image, + buttonBackgroundColor: Color = .black, + buttonForegroundColor: Color = .white + ) { + self.providerId = providerId + self.scopes = scopes + self.customParameters = customParameters + self.displayName = displayName + self.buttonIcon = buttonIcon + self.buttonBackgroundColor = buttonBackgroundColor + self.buttonForegroundColor = buttonForegroundColor + } + + /// Convenience initializer using SF Symbol + /// - Parameters: + /// - providerId: The OAuth provider ID (e.g., "github.com", "microsoft.com") + /// - scopes: OAuth scopes to request + /// - customParameters: Additional OAuth parameters + /// - displayName: Button label (e.g., "Sign in with GitHub") + /// - iconSystemName: SF Symbol name + /// - buttonBackgroundColor: Button background color + /// - buttonForegroundColor: Button text/icon color + public convenience init( + providerId: String, + scopes: [String] = [], + customParameters: [String: String] = [:], + displayName: String, + iconSystemName: String, + buttonBackgroundColor: Color = .black, + buttonForegroundColor: Color = .white + ) { + self.init( + providerId: providerId, + scopes: scopes, + customParameters: customParameters, + displayName: displayName, + buttonIcon: Image(systemName: iconSystemName), + buttonBackgroundColor: buttonBackgroundColor, + buttonForegroundColor: buttonForegroundColor + ) + } + + @MainActor public func createAuthCredential() async throws -> AuthCredential { + let provider = OAuthProvider(providerID: providerId) + + // Set scopes if provided + if !scopes.isEmpty { + provider.scopes = scopes + } + + // Set custom parameters if provided + if !customParameters.isEmpty { + provider.customParameters = customParameters + } + + return try await withCheckedThrowingContinuation { continuation in + provider.getCredentialWith(nil) { credential, error in + if let error { + continuation.resume( + throwing: AuthServiceError.signInFailed(underlying: error) + ) + } else if let credential { + continuation.resume(returning: credential) + } else { + continuation.resume( + throwing: AuthServiceError.invalidCredentials( + "\(self.providerId) did not provide a valid AuthCredential" + ) + ) + } + } + } + } + + public func deleteUser(user: User) async throws { + let operation = OAuthDeleteUserOperation(oauthProvider: self) + try await operation(on: user) + } +} + +public class OAuthProviderAuthUI: AuthProviderUI { + public var provider: AuthProviderSwift + + public init(provider: AuthProviderSwift) { + self.provider = provider + } + + public var id: String { + guard let oauthProvider = provider as? OAuthProviderSwift else { + return "oauth.unknown" + } + return oauthProvider.providerId + } + + @MainActor public func authButton() -> AnyView { + AnyView(GenericOAuthButton(provider: provider)) + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift new file mode 100644 index 0000000000..2c8297c7f2 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift @@ -0,0 +1,65 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseAuthSwiftUI +import SwiftUI + +/// A generic OAuth sign-in button that adapts to any provider's configuration +@MainActor +public struct GenericOAuthButton { + @Environment(AuthService.self) private var authService + let provider: AuthProviderSwift + + public init(provider: AuthProviderSwift) { + self.provider = provider + } +} + +extension GenericOAuthButton: View { + public var body: some View { + guard let oauthProvider = provider as? OAuthProviderSwift else { + return AnyView( + Text("Invalid OAuth Provider") + .foregroundColor(.red) + ) + } + + return AnyView( + Button(action: { + Task { + try await authService.signIn(provider) + } + }) { + HStack { + oauthProvider.buttonIcon + .resizable() + .renderingMode(.template) + .scaledToFit() + .frame(width: 24, height: 24) + .foregroundColor(oauthProvider.buttonForegroundColor) + + Text(oauthProvider.displayName) + .fontWeight(.semibold) + .foregroundColor(oauthProvider.buttonForegroundColor) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .background(oauthProvider.buttonBackgroundColor) + .cornerRadius(8) + } + .accessibilityIdentifier("sign-in-with-\(oauthProvider.providerId)-button") + ) + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift new file mode 100644 index 0000000000..1a96992970 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift @@ -0,0 +1,21 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@testable import FirebaseOAuthSwiftUI +import Testing + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} + diff --git a/Package.swift b/Package.swift index c26c766008..37e1c2c149 100644 --- a/Package.swift +++ b/Package.swift @@ -86,6 +86,10 @@ let package = Package( name: "FirebaseAppleSwiftUI", targets: ["FirebaseAppleSwiftUI"] ), + .library( + name: "FirebaseOAuthSwiftUI", + targets: ["FirebaseOAuthSwiftUI"] + ), ], dependencies: [ .package( @@ -342,5 +346,17 @@ let package = Package( dependencies: ["FirebaseAppleSwiftUI"], path: "FirebaseSwiftUI/FirebaseAppleSwiftUI/Tests/" ), + .target( + name: "FirebaseOAuthSwiftUI", + dependencies: [ + "FirebaseAuthSwiftUI", + ], + path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources" + ), + .testTarget( + name: "FirebaseOAuthSwiftUITests", + dependencies: ["FirebaseOAuthSwiftUI"], + path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/" + ), ] ) From 94935721d4b7f9bf1788389a6b2a5cc2f082ec6b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 11:29:26 +0100 Subject: [PATCH 03/16] chore: setup test app with OAuth --- .../FirebaseSwiftUIExample.xcodeproj/project.pbxproj | 8 ++++++++ .../FirebaseSwiftUIExample/ContentView.swift | 2 ++ .../FirebaseSwiftUIExample/TestView.swift | 1 + 3 files changed, 11 insertions(+) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj index d593bafac1..e0c6a0533d 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 4607CC9C2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4607CC9B2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI */; }; 4607CC9E2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4607CC9D2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI */; }; 4610DD2A2EA796360084B32B /* FirebaseAppleSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4610DD292EA796360084B32B /* FirebaseAppleSwiftUI */; }; + 464938E92EA8E6BD0013A9E3 /* FirebaseOAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 464938E82EA8E6BD0013A9E3 /* FirebaseOAuthSwiftUI */; }; 4681E0002E97F22B00387C88 /* FirebaseTwitterSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4681DFFF2E97F22B00387C88 /* FirebaseTwitterSwiftUI */; }; 46CB7B252D773F2100F1FD0A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 46CB7B242D773F2100F1FD0A /* GoogleService-Info.plist */; }; 46F89C392D64B04E000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 46F89C382D64B04E000F8BC0 /* FirebaseAuthSwiftUI */; }; @@ -88,6 +89,7 @@ 8D808CB92DB081F900D2293F /* FirebasePhoneAuthSwiftUI in Frameworks */, 4681E0002E97F22B00387C88 /* FirebaseTwitterSwiftUI in Frameworks */, 4607CC9C2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI in Frameworks */, + 464938E92EA8E6BD0013A9E3 /* FirebaseOAuthSwiftUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -167,6 +169,7 @@ 8D808CB82DB081F900D2293F /* FirebasePhoneAuthSwiftUI */, 4681DFFF2E97F22B00387C88 /* FirebaseTwitterSwiftUI */, 4610DD292EA796360084B32B /* FirebaseAppleSwiftUI */, + 464938E82EA8E6BD0013A9E3 /* FirebaseOAuthSwiftUI */, ); productName = FirebaseSwiftUIExample; productReference = 46F89C082D64A86C000F8BC0 /* FirebaseSwiftUIExample.app */; @@ -671,6 +674,11 @@ package = 8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */; productName = FirebaseAppleSwiftUI; }; + 464938E82EA8E6BD0013A9E3 /* FirebaseOAuthSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = 8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */; + productName = FirebaseOAuthSwiftUI; + }; 4681DFFF2E97F22B00387C88 /* FirebaseTwitterSwiftUI */ = { isa = XCSwiftPackageProductDependency; package = 8D808CB52DB07EBD00D2293F /* XCLocalSwiftPackageReference "../../../../FirebaseUI-iOS" */; diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift index 1101a55e36..08f1fd9423 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift @@ -26,6 +26,7 @@ import FirebaseGoogleSwiftUI import FirebasePhoneAuthSwiftUI import FirebaseTwitterSwiftUI import FirebaseAppleSwiftUI +import FirebaseOAuthSwiftUI import SwiftUI struct ContentView: View { @@ -52,6 +53,7 @@ struct ContentView: View { .withPhoneSignIn() .withAppleSignIn() .withTwitterSignIn() + .withOAuthSignIn(OAuthProviderSwift.github()) .withFacebookSignIn() .withEmailSignIn() diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift index 04aef85482..53076884bc 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift @@ -26,6 +26,7 @@ import FirebaseGoogleSwiftUI import FirebasePhoneAuthSwiftUI import FirebaseAppleSwiftUI import FirebaseTwitterSwiftUI +import FirebaseOAuthSwiftUI import SwiftUI struct TestView: View { From 6a43b9671b99d0518d660d54be95c29333fa7ccb Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 12:29:04 +0100 Subject: [PATCH 04/16] chore: needed custom params for microsoft and yahoo --- .../Sources/Services/OAuthProviderSwift+Presets.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift index 7cdc8be25d..34dd0a5440 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift @@ -40,6 +40,7 @@ public extension OAuthProviderSwift { return OAuthProviderSwift( providerId: "microsoft.com", scopes: scopes, + customParameters: ["prompt" : "consent"], displayName: "Sign in with Microsoft", iconSystemName: "building.2", buttonBackgroundColor: Color(red: 0/255, green: 120/255, blue: 212/255), @@ -49,12 +50,13 @@ public extension OAuthProviderSwift { /// Yahoo OAuth provider /// - Parameters: - /// - scopes: Yahoo scopes (default: []) + /// - scopes: Yahoo scopes (default: ["user.readwrite"]) /// - Returns: Configured Yahoo provider - static func yahoo(scopes: [String] = []) -> OAuthProviderSwift { + static func yahoo(scopes: [String] = ["user.readwrite"]) -> OAuthProviderSwift { return OAuthProviderSwift( providerId: "yahoo.com", scopes: scopes, + customParameters: ["prompt" : "consent"], displayName: "Sign in with Yahoo", iconSystemName: "y.circle.fill", buttonBackgroundColor: Color(red: 80/255, green: 0/255, blue: 155/255), From a2c8aa1d87873d10d9cd136d89458d187ce6c61f Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 12:29:14 +0100 Subject: [PATCH 05/16] chore: remove linkedIn --- .../Services/OAuthProviderSwift+Presets.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift index 34dd0a5440..e382f0eba1 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift @@ -63,20 +63,5 @@ public extension OAuthProviderSwift { buttonForegroundColor: .white ) } - - /// LinkedIn OAuth provider - /// - Parameters: - /// - scopes: LinkedIn scopes (default: ["r_liteprofile", "r_emailaddress"]) - /// - Returns: Configured LinkedIn provider - static func linkedIn(scopes: [String] = ["r_liteprofile", "r_emailaddress"]) -> OAuthProviderSwift { - return OAuthProviderSwift( - providerId: "linkedin.com", - scopes: scopes, - displayName: "Sign in with LinkedIn", - iconSystemName: "link", - buttonBackgroundColor: Color(red: 0/255, green: 119/255, blue: 181/255), - buttonForegroundColor: .white - ) - } } From 44d07f841353adb330b048076fc6678fd0107eeb Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 12:29:36 +0100 Subject: [PATCH 06/16] format --- .../Services/AccountService+OAuth.swift | 1 - .../Sources/Services/AuthService+OAuth.swift | 1 - .../Services/OAuthProviderSwift+Presets.swift | 14 ++--- .../Sources/Services/OAuthProviderSwift.swift | 55 +++++++++---------- .../Sources/Views/GenericOAuthButton.swift | 7 +-- .../FirebaseOAuthSwiftUITests.swift | 1 - 6 files changed, 34 insertions(+), 45 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift index aacf856457..54215c913d 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AccountService+OAuth.swift @@ -44,4 +44,3 @@ class OAuthDeleteUserOperation: AuthenticatedOperation, } } } - diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift index 4189addbba..b4b6034af6 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/AuthService+OAuth.swift @@ -28,4 +28,3 @@ public extension AuthService { return self } } - diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift index e382f0eba1..ee795fd6d6 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift @@ -16,7 +16,6 @@ import SwiftUI /// Preset configurations for common OAuth providers public extension OAuthProviderSwift { - /// GitHub OAuth provider /// - Parameters: /// - scopes: GitHub scopes (default: ["user"]) @@ -31,7 +30,7 @@ public extension OAuthProviderSwift { buttonForegroundColor: .white ) } - + /// Microsoft OAuth provider /// - Parameters: /// - scopes: Microsoft scopes (default: ["openid", "profile", "email"]) @@ -40,14 +39,14 @@ public extension OAuthProviderSwift { return OAuthProviderSwift( providerId: "microsoft.com", scopes: scopes, - customParameters: ["prompt" : "consent"], + customParameters: ["prompt": "consent"], displayName: "Sign in with Microsoft", iconSystemName: "building.2", - buttonBackgroundColor: Color(red: 0/255, green: 120/255, blue: 212/255), + buttonBackgroundColor: Color(red: 0 / 255, green: 120 / 255, blue: 212 / 255), buttonForegroundColor: .white ) } - + /// Yahoo OAuth provider /// - Parameters: /// - scopes: Yahoo scopes (default: ["user.readwrite"]) @@ -56,12 +55,11 @@ public extension OAuthProviderSwift { return OAuthProviderSwift( providerId: "yahoo.com", scopes: scopes, - customParameters: ["prompt" : "consent"], + customParameters: ["prompt": "consent"], displayName: "Sign in with Yahoo", iconSystemName: "y.circle.fill", - buttonBackgroundColor: Color(red: 80/255, green: 0/255, blue: 155/255), + buttonBackgroundColor: Color(red: 80 / 255, green: 0 / 255, blue: 155 / 255), buttonForegroundColor: .white ) } } - diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift index 11df97c2c2..08eae1733a 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift @@ -22,13 +22,13 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { public let providerId: String public let scopes: [String] public let customParameters: [String: String] - + // Button appearance public let displayName: String public let buttonIcon: Image public let buttonBackgroundColor: Color public let buttonForegroundColor: Color - + /// Initialize a generic OAuth provider /// - Parameters: /// - providerId: The OAuth provider ID (e.g., "github.com", "microsoft.com") @@ -38,15 +38,13 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { /// - buttonIcon: Button icon image /// - buttonBackgroundColor: Button background color /// - buttonForegroundColor: Button text/icon color - public init( - providerId: String, - scopes: [String] = [], - customParameters: [String: String] = [:], - displayName: String, - buttonIcon: Image, - buttonBackgroundColor: Color = .black, - buttonForegroundColor: Color = .white - ) { + public init(providerId: String, + scopes: [String] = [], + customParameters: [String: String] = [:], + displayName: String, + buttonIcon: Image, + buttonBackgroundColor: Color = .black, + buttonForegroundColor: Color = .white) { self.providerId = providerId self.scopes = scopes self.customParameters = customParameters @@ -55,7 +53,7 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { self.buttonBackgroundColor = buttonBackgroundColor self.buttonForegroundColor = buttonForegroundColor } - + /// Convenience initializer using SF Symbol /// - Parameters: /// - providerId: The OAuth provider ID (e.g., "github.com", "microsoft.com") @@ -65,15 +63,13 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { /// - iconSystemName: SF Symbol name /// - buttonBackgroundColor: Button background color /// - buttonForegroundColor: Button text/icon color - public convenience init( - providerId: String, - scopes: [String] = [], - customParameters: [String: String] = [:], - displayName: String, - iconSystemName: String, - buttonBackgroundColor: Color = .black, - buttonForegroundColor: Color = .white - ) { + public convenience init(providerId: String, + scopes: [String] = [], + customParameters: [String: String] = [:], + displayName: String, + iconSystemName: String, + buttonBackgroundColor: Color = .black, + buttonForegroundColor: Color = .white) { self.init( providerId: providerId, scopes: scopes, @@ -84,20 +80,20 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { buttonForegroundColor: buttonForegroundColor ) } - + @MainActor public func createAuthCredential() async throws -> AuthCredential { let provider = OAuthProvider(providerID: providerId) - + // Set scopes if provided if !scopes.isEmpty { provider.scopes = scopes } - + // Set custom parameters if provided if !customParameters.isEmpty { provider.customParameters = customParameters } - + return try await withCheckedThrowingContinuation { continuation in provider.getCredentialWith(nil) { credential, error in if let error { @@ -116,7 +112,7 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { } } } - + public func deleteUser(user: User) async throws { let operation = OAuthDeleteUserOperation(oauthProvider: self) try await operation(on: user) @@ -125,20 +121,19 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { public class OAuthProviderAuthUI: AuthProviderUI { public var provider: AuthProviderSwift - + public init(provider: AuthProviderSwift) { self.provider = provider } - + public var id: String { guard let oauthProvider = provider as? OAuthProviderSwift else { return "oauth.unknown" } return oauthProvider.providerId } - + @MainActor public func authButton() -> AnyView { AnyView(GenericOAuthButton(provider: provider)) } } - diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift index 2c8297c7f2..0884dc8c8d 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Views/GenericOAuthButton.swift @@ -20,7 +20,7 @@ import SwiftUI public struct GenericOAuthButton { @Environment(AuthService.self) private var authService let provider: AuthProviderSwift - + public init(provider: AuthProviderSwift) { self.provider = provider } @@ -34,7 +34,7 @@ extension GenericOAuthButton: View { .foregroundColor(.red) ) } - + return AnyView( Button(action: { Task { @@ -48,7 +48,7 @@ extension GenericOAuthButton: View { .scaledToFit() .frame(width: 24, height: 24) .foregroundColor(oauthProvider.buttonForegroundColor) - + Text(oauthProvider.displayName) .fontWeight(.semibold) .foregroundColor(oauthProvider.buttonForegroundColor) @@ -62,4 +62,3 @@ extension GenericOAuthButton: View { ) } } - diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift index 1a96992970..31b3bfd5bf 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Tests/FirebaseOAuthSwiftUITests/FirebaseOAuthSwiftUITests.swift @@ -18,4 +18,3 @@ import Testing @Test func example() async throws { // Write your test here and use APIs like `#expect(...)` to check expected conditions. } - From 10f89aa74b0ca3b8d10cfc6bf9b7e291edeb47b4 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 12:32:32 +0100 Subject: [PATCH 07/16] chore: setup microsoft and yahoo sign in --- .../FirebaseSwiftUIExample/ContentView.swift | 2 ++ .../FirebaseSwiftUIExample/TestView.swift | 3 +++ 2 files changed, 5 insertions(+) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift index 08f1fd9423..3b1022a430 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/ContentView.swift @@ -54,6 +54,8 @@ struct ContentView: View { .withAppleSignIn() .withTwitterSignIn() .withOAuthSignIn(OAuthProviderSwift.github()) + .withOAuthSignIn(OAuthProviderSwift.microsoft()) + .withOAuthSignIn(OAuthProviderSwift.yahoo()) .withFacebookSignIn() .withEmailSignIn() diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift index 53076884bc..ea1399cd02 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift @@ -61,6 +61,9 @@ struct TestView: View { .withPhoneSignIn() .withAppleSignIn() .withTwitterSignIn() + .withOAuthSignIn(OAuthProviderSwift.github()) + .withOAuthSignIn(OAuthProviderSwift.microsoft()) + .withOAuthSignIn(OAuthProviderSwift.yahoo()) .withFacebookSignIn() .withEmailSignIn() } From 058fc3e67b50a76d3c2be06137e4539a8dc8623f Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 13:00:09 +0100 Subject: [PATCH 08/16] test: test buttons are present in auth picker --- .../FirebaseSwiftUIExampleUITests.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift index 7dbc0dc2f4..7f85874c52 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift @@ -61,6 +61,27 @@ final class FirebaseSwiftUIExampleUITests: XCTestCase { appleButton.waitForExistence(timeout: 5), "Apple sign-in button should exist" ) + + // Check for Github sign-in button + let githubButton = app.buttons["sign-in-with-github.com-button"] + XCTAssertTrue( + githubButton.waitForExistence(timeout: 5), + "Github sign-in button should exist" + ) + + // Check for Microsoft sign-in button + let microsoftButton = app.buttons["sign-in-with-microsoft.com-button"] + XCTAssertTrue( + microsoftButton.waitForExistence(timeout: 5), + "Microsoft sign-in button should exist" + ) + + // Check for Yahoo sign-in button + let yahooButton = app.buttons["sign-in-with-yahoo.com-button"] + XCTAssertTrue( + yahooButton.waitForExistence(timeout: 5), + "Yahoo sign-in button should exist" + ) // Check for Google sign-in button let googleButton = app.buttons["sign-in-with-google-button"] From 08a47639f89713274d53420e58714b8002ede9b2 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 22 Oct 2025 13:10:17 +0100 Subject: [PATCH 09/16] feat: use logos for microsoft, yahoo and github --- .../Resources/Media.xcassets/Contents.json | 7 +++++ .../github_logo.imageset/Contents.json | 28 ++++++++++++++++++ .../github_logo.imageset/ic_github.png | Bin 0 -> 800 bytes .../github_logo.imageset/ic_github@2x.png | Bin 0 -> 1789 bytes .../github_logo.imageset/ic_github@3x.png | Bin 0 -> 3106 bytes .../microsoft_logo.imageset/Contents.json | 28 ++++++++++++++++++ .../microsoft_logo.imageset/ic_microsoft.png | Bin 0 -> 508 bytes .../ic_microsoft@2x.png | Bin 0 -> 619 bytes .../ic_microsoft@3x.png | Bin 0 -> 626 bytes .../yahoo_logo.imageset/Contents.json | 28 ++++++++++++++++++ .../yahoo_logo.imageset/ic_yahoo.png | Bin 0 -> 1895 bytes .../yahoo_logo.imageset/ic_yahoo@2x.png | Bin 0 -> 2917 bytes .../yahoo_logo.imageset/ic_yahoo@3x.png | Bin 0 -> 3781 bytes .../Services/OAuthProviderSwift+Presets.swift | 10 +++---- Package.swift | 5 +++- 15 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/Contents.json create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/Contents.json create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github@2x.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github@3x.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/Contents.json create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft@2x.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft@3x.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/yahoo_logo.imageset/Contents.json create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/yahoo_logo.imageset/ic_yahoo.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/yahoo_logo.imageset/ic_yahoo@2x.png create mode 100644 FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/yahoo_logo.imageset/ic_yahoo@3x.png diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/Contents.json b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/Contents.json new file mode 100644 index 0000000000..6cc12269b3 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/Contents.json @@ -0,0 +1,7 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/Contents.json b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/Contents.json new file mode 100644 index 0000000000..ac6bbbb149 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "filename" : "ic_github.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_github@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_github@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github.png b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github.png new file mode 100644 index 0000000000000000000000000000000000000000..cae7a5ddfc2f3c567e82550e95121b994e1ab032 GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^> z;_2(k{+N@8L5Qs@^z>q&JhP{ZV~EA++R47&A&C-g>wivgNRud;AkdPtxS3nrP))Fo z?O%ffM@!f)4)I_7F&(?NeP;H_czjAeps{I|OGi`K44!QPQ#Srtz$3k0Y~Q?N>)&mz ze1Euh{`0!|&n*p^SXMk_^nbBrrcS^kD&JQ;rrP1Tdv3|`)i(++IG!#$TeUdujFoH%cj2rxH3_TyHFJ-gqjp{O>dG`8Vb3M2;LgvElKC(6BFyPSx30yy}d;w>wE=cS?P$ zO;&uj@Vcliibf_8C09A4|82D{%XBF}G4Y}8{$%kzbD8btelGsQYA7tGzMu8?d|(Pu zEpd$~Nl7e8wMs5Z1yT$~28Ncp29~;pMj-|UR)!W<24>nohJnHEdq@7DXvob^$xN%n zt>IL2jRR1FB*=!~{Irtt#G+IN$CUh}R0Yr6#Prml)Wnp^!jq|>WXs^`>gTe~DWM4f DsC!Je literal 0 HcmV?d00001 diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github@2x.png b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..acd642bc19f040760b082ae7662d473af4f77931 GIT binary patch literal 1789 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt00(qQO+^Re2@C)d5|ED{-T(jv;7LS5R9M5U*V%7X*A)iv-#I?U z6UJtU&145~7z`%FDnW=qk>MpsfK;iG@*4Hus#K|!(yF2AL#q}Z0tCV<R8fU>k!= zhzBq>9-Sf!A(r%&>Y9iGd?R3OZ zd)0a~#@+Ru%N`i?N+_l*br7j^$SFrOsa7R^i-W8wuRL+t-&{2<6jCNd(U)s-T!;Od zRd^S~YSdVzR)cN6^(3V8<4*GEBNevl@Ef~Tz7zQ(Ds0zer*aoOk_rpwi1Hj%`pB2Q zuvwL8nGSLkQ-1P|Kj|?&Pmts#XPYmbw&Ojh=Yy@b!)bi2dp=RgLgh3&=8HM{j9Igi zcf18sW+f73*sH>TlwKi-=I$U;<+!hGp4%{?-;f4dRJ=7=LCRx88f;JpHv7tqepABy zC9iZ)hdm1s_d4&6a@*{%&o*Vqnv^n&M1>kLGJ5@}ORuz#9n%h~?a|?p>&8g}G&t=* zUP5}EbKSUl`+e@PdKoVb8#jX$YOPhL%&703^@CBh`fZtWK!pRI>op!ESf$lrTl0=R zW}1i8G$HkJdEp8 zWmamjrb8~u#8ggy0>O-QK}BtF#8bCpu~tWm6sJPDf==IymHG~FAJ=V+F~u% z6c{I5*Hzj$-h?RUqRVv?1%Is365C`|fpJ`q;blcXk6}H=3oNT_N;DLdSK5fl_o9bM zBhq>GS0hP^DkyDQW;y9uOi~~tWj4=2xg;mCi-_OagWrs}AhuWuU8UM0m&-w!mBr0A zkro$F)~GV4Y447?nyyk)V9vh)DeX*{hXI0~Xb{{BvcdZDN`9Qk^MRzc)D|W{~fw9~M9ZsoV zR`m5w>9CXpeDGbyOI z&S7=d`PQI`kdl@*%fgGz_2m`HRjASExRdsll<26(9wer8yXiBv#$9y7j1xYw*2kKi z_HUgYdg>>`vU%HM>onP@#cq3TR$nsBzH(EysRW(Q+pE?JO*-B5d(WJ(%0}y~_lYMu zo%Ot6d)9WJYt>}E#wFY9nDaVwgV$qMoU~50{jx@#F{8n!>a5VL*-o(=MGYvZvs*_| zA6`VtLsvY`X;E6Ab2>r0U)$@h-|1c$L&jWmuY}^B?~2qvADzzWlO~A-UN~!`57jBR z)iFaZsCGb;N-3SL=%2r0qTFEybeUd~JmaOS&U#^C_?>dwdD|Sc+A1Bo-S@Q{cBwU@ zSC6R@nkg@3mUJ-YhVyRc;z;Hq3NE2uyGlEC=yTaUPsB1(OO7yE*(C>qY4`osrTMPD z_>K15HGlBRXDS>(i~EL5tC01msF}}Sbg3ofiodwFSaZpIZUaO9EU2{0W)0eO>M^Ot zs6Q_0pfvZR?mOcfqf5O1AP8llF`Nu%LVw7H=`a!o!p~tOTnWDjLSDeRU*TZ57xK{` z&V-YpF_h)6T}V-sYubapm(}Bt_650HW5rveAM>NDF1eZ$|0Oj8E$aGS}*kIF|hnu>i>lQ0k!WM*#_aNJ^%m!C3HntbYx+4WjbSW zWnpw>05UK!IV~_bEip1wFfckXI65&nD=;uRFfb=ezdrx~03~!qSaf7zbY(hiZ)9m^ zc>ppnF*z+TIV~|VR4_0)F*rIgI4dwPIxsL+76*d>000?uMObuGZ)S9NVRB^vL1b@Y fWgtmyVP|DhWnpA_ami&o00000NkvXXu0mjfbKgm& literal 0 HcmV?d00001 diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github@3x.png b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/github_logo.imageset/ic_github@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ffcfd198a44b1c402573b295e7fb65c04a828bff GIT binary patch literal 3106 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 literal 0 HcmV?d00001 diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/Contents.json b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/Contents.json new file mode 100644 index 0000000000..60fe21a8d5 --- /dev/null +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "filename" : "ic_microsoft.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_microsoft@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_microsoft@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} + diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft.png b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft.png new file mode 100644 index 0000000000000000000000000000000000000000..3a57a3e967a05b234b74d4450d9e0db1f8ad26bd GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkphQ%FPl)TcM8(ci4iNHB zk|YHEkCSLV<=k=15rP;t{(zzl|NkFm`hSQAf*98R{C}7QPJT*N>OE%9uO>_%)p?b0>X^G zdoDBr1=&kHeO=ifbMi2V3f(`~DG3yE@N{tu(Kw$x!G(d%O_L=-!c)lS%n2bKg9E3I zojZ8)C@(XQjlqG_$MqF7RCt(qE^#O*X*DyP5K4Mvp`y~Zn1w;P(aitf-Jl$xS*j(j z5hW>!C8<`)MX5lF!N|bSQrEyz*U%`$z`)AL(8|z48^|y)aN6_f3yOx^{FKbJO57TZ zA9{HJHAsSN2+mI{DNig)WpGT%PfAtr%uP&B4N6T+sVqF13W^d2Pgg&ebxsLQ0B}C1 AuK)l5 literal 0 HcmV?d00001 diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft@2x.png b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/microsoft_logo.imageset/ic_microsoft@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..baf89791056fffe52994212f8936738e92d9675c GIT binary patch literal 619 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 literal 0 HcmV?d00001 diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/yahoo_logo.imageset/ic_yahoo@3x.png b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Resources/Media.xcassets/yahoo_logo.imageset/ic_yahoo@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f3bf49ed073c6bd4c4528fe19b5fb15dd0675dc4 GIT binary patch literal 3781 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 NgNvrRPqiIy{vS)Xhpqqs literal 0 HcmV?d00001 diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift index ee795fd6d6..080a575b50 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift+Presets.swift @@ -25,7 +25,7 @@ public extension OAuthProviderSwift { providerId: "github.com", scopes: scopes, displayName: "Sign in with GitHub", - iconSystemName: "chevron.left.forwardslash.chevron.right", + buttonIcon: Image("github_logo", bundle: .module), buttonBackgroundColor: .black, buttonForegroundColor: .white ) @@ -33,15 +33,15 @@ public extension OAuthProviderSwift { /// Microsoft OAuth provider /// - Parameters: - /// - scopes: Microsoft scopes (default: ["openid", "profile", "email"]) + /// - scopes: Microsoft scopes (default: ["user.readwrite"]) /// - Returns: Configured Microsoft provider - static func microsoft(scopes: [String] = ["openid", "profile", "email"]) -> OAuthProviderSwift { + static func microsoft(scopes: [String] = ["user.readwrite"]) -> OAuthProviderSwift { return OAuthProviderSwift( providerId: "microsoft.com", scopes: scopes, customParameters: ["prompt": "consent"], displayName: "Sign in with Microsoft", - iconSystemName: "building.2", + buttonIcon: Image("microsoft_logo", bundle: .module), buttonBackgroundColor: Color(red: 0 / 255, green: 120 / 255, blue: 212 / 255), buttonForegroundColor: .white ) @@ -57,7 +57,7 @@ public extension OAuthProviderSwift { scopes: scopes, customParameters: ["prompt": "consent"], displayName: "Sign in with Yahoo", - iconSystemName: "y.circle.fill", + buttonIcon: Image("yahoo_logo", bundle: .module), buttonBackgroundColor: Color(red: 80 / 255, green: 0 / 255, blue: 155 / 255), buttonForegroundColor: .white ) diff --git a/Package.swift b/Package.swift index 37e1c2c149..3ab09585ea 100644 --- a/Package.swift +++ b/Package.swift @@ -351,7 +351,10 @@ let package = Package( dependencies: [ "FirebaseAuthSwiftUI", ], - path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources" + path: "FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources", + resources: [ + .process("Resources") + ] ), .testTarget( name: "FirebaseOAuthSwiftUITests", From 5a725361c73b7fa59d09ce8cd75fb86ad23f703e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 13:26:57 +0100 Subject: [PATCH 10/16] fix: remove duplicate provider --- .../Sources/Services/PhoneAuthProviderAuthUI.swift | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift index f39d6f1615..be555bdc92 100644 --- a/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift +++ b/FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Sources/Services/PhoneAuthProviderAuthUI.swift @@ -77,16 +77,3 @@ public class PhoneAuthProviderAuthUI: AuthProviderUI { AnyView(PhoneAuthButtonView(phoneProvider: provider as! PhoneAuthProviderSwift)) } } - -public class PhoneAuthProviderAuthUI: AuthProviderUI { - public var provider: AuthProviderSwift - public let id: String = "phone.com" - - public init(provider: PhoneAuthProviderSwift? = nil) { - self.provider = provider ?? PhoneProviderSwift() - } - - @MainActor public func authButton() -> AnyView { - AnyView(PhoneAuthButtonView(phoneProvider: provider as! PhoneAuthProviderSwift)) - } -} From dd9e119c11231ddb7a2ede051a47c10687d02233 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 13:35:17 +0100 Subject: [PATCH 11/16] chore: update to use guards --- .../Sources/Services/OAuthProviderSwift.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift index 08eae1733a..9e952b79f1 100644 --- a/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift +++ b/FirebaseSwiftUI/FirebaseOAuthSwiftUI/Sources/Services/OAuthProviderSwift.swift @@ -96,19 +96,23 @@ public class OAuthProviderSwift: AuthProviderSwift, DeleteUserSwift { return try await withCheckedThrowingContinuation { continuation in provider.getCredentialWith(nil) { credential, error in - if let error { + if let error = error { continuation.resume( throwing: AuthServiceError.signInFailed(underlying: error) ) - } else if let credential { - continuation.resume(returning: credential) - } else { + return + } + + guard let credential = credential else { continuation.resume( throwing: AuthServiceError.invalidCredentials( "\(self.providerId) did not provide a valid AuthCredential" ) ) + return } + + continuation.resume(returning: credential) } } } From a2041610c2bba784414136bc95e4035c7bbece1b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 13:35:53 +0100 Subject: [PATCH 12/16] ci: update timeouts --- .github/workflows/swiftui-auth.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index 4b65be8fad..f854daf2fd 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -26,7 +26,7 @@ jobs: unit-tests: name: Package Unit Tests runs-on: macos-15 - timeout-minutes: 20 + timeout-minutes: 15 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 @@ -129,7 +129,7 @@ jobs: ui-tests: name: UI Tests runs-on: macos-15 - timeout-minutes: 30 + timeout-minutes: 40 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 From d09ff11be9957e2f3d29e77dc8e0b3a9196ebd39 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 14:53:04 +0100 Subject: [PATCH 13/16] ci: update name of test results --- .github/workflows/swiftui-auth.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index f854daf2fd..a17ca3a411 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -190,5 +190,5 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ui-tests-results + name: FirebaseSwiftUIExampleUITests.xcresult path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.xcresult \ No newline at end of file From f5d86be1cef495a8e01271a2824cd66d2948c498 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 15:16:49 +0100 Subject: [PATCH 14/16] ci: remove obsolete log for firebase logs --- .github/workflows/swiftui-auth.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index a17ca3a411..7faaacd95c 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -179,12 +179,12 @@ jobs: -enableCodeCoverage YES \ -resultBundlePath FirebaseSwiftUIExampleUITests.xcresult | tee FirebaseSwiftUIExampleUITests.log | xcpretty --test --color --simple - - name: Upload test logs + - name: Upload Firebase Emulator logs if: failure() uses: actions/upload-artifact@v4 with: - name: ui-tests-logs - path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.log + name: firebase-emulator-logs + path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/firebase-debug.log - name: Upload test results if: failure() From dbcf36ddee8340767e0562a668c97ef2754adcb2 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 15:46:29 +0100 Subject: [PATCH 15/16] ci: debug mode for logs --- .../FirebaseSwiftUIExample/start-firebase-emulator.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/start-firebase-emulator.sh b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/start-firebase-emulator.sh index fb1280e438..d5ee3dec67 100755 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/start-firebase-emulator.sh +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/start-firebase-emulator.sh @@ -14,7 +14,7 @@ if ! [ -x "$(command -v npm)" ]; then exit 1 fi -EMU_START_COMMAND="firebase emulators:start --only auth --project flutterfire-e2e-tests" +EMU_START_COMMAND="firebase emulators:start --only auth --project flutterfire-e2e-tests --debug" MAX_RETRIES=3 MAX_CHECKATTEMPTS=60 From 4f45eaf977bd7812079ee5e6aa7db5942ddb7f94 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 24 Oct 2025 15:47:13 +0100 Subject: [PATCH 16/16] test: test sometimes fails waiting for signed in screen so might be a fix --- .../FirebaseSwiftUIExampleUITests.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift index 7f85874c52..7d010d8777 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift @@ -236,6 +236,13 @@ final class FirebaseSwiftUIExampleUITests: XCTestCase { XCTAssertTrue(signUpButton.exists, "Sign-Up button should exist") signUpButton.tap() + // Wait for the auth screen to disappear (email field should no longer exist) + let emailFieldDisappeared = NSPredicate(format: "exists == false") + let expectation = XCTNSPredicateExpectation(predicate: emailFieldDisappeared, object: emailField) + let result = XCTWaiter().wait(for: [expectation], timeout: 10.0) + XCTAssertEqual(result, .completed, "Email field should disappear after sign-up") + + // Wait for user creation and signed-in view to appear let signedInText = app.staticTexts["signed-in-text"] XCTAssertTrue(