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 @@ -114,7 +114,7 @@ extension AuthenticateWithAppleDialog: ASAuthorizationControllerDelegate {

// MARK: - Apple Provider Swift

public class AppleProviderSwift: AuthProviderSwift {
public class AppleProviderSwift: CredentialAuthProviderSwift {
public let scopes: [ASAuthorization.Scope]
let providerId = "apple.com"

Expand All @@ -140,15 +140,15 @@ public class AppleProviderSwift: AuthProviderSwift {
}

public class AppleProviderAuthUI: AuthProviderUI {
public var provider: AuthProviderSwift
private let typedProvider: AppleProviderSwift
public var provider: AuthProviderSwift { typedProvider }
public let id: String = "apple.com"

public init(provider: AuthProviderSwift) {
self.provider = provider
public init(provider: AppleProviderSwift) {
typedProvider = provider
}

public let id: String = "apple.com"

@MainActor public func authButton() -> AnyView {
AnyView(SignInWithAppleButton(provider: provider))
AnyView(SignInWithAppleButton(provider: typedProvider))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import SwiftUI
@MainActor
public struct SignInWithAppleButton {
@Environment(AuthService.self) private var authService
let provider: AuthProviderSwift
public init(provider: AuthProviderSwift) {
let provider: AppleProviderSwift
public init(provider: AppleProviderSwift) {
self.provider = provider
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import FirebaseAuthUIComponents
import FirebaseCore
import SwiftUI

public protocol AuthProviderSwift {
/// Base protocol for all authentication providers
public protocol AuthProviderSwift {}

/// Protocol for providers that can directly create an AuthCredential
/// Used by Google, Apple, Twitter, Facebook, and OAuth providers
public protocol CredentialAuthProviderSwift: AuthProviderSwift {
@MainActor func createAuthCredential() async throws -> AuthCredential
}

Expand All @@ -27,12 +32,6 @@ public protocol AuthProviderUI {
var provider: AuthProviderSwift { get }
}

public protocol PhoneAuthProviderSwift: AuthProviderSwift {
@MainActor func verifyPhoneNumber(phoneNumber: String) async throws -> String
@MainActor func createAuthCredential(verificationId: String,
verificationCode: String) async throws -> AuthCredential
}

public enum AuthenticationState {
case unauthenticated
case authenticating
Expand Down Expand Up @@ -164,10 +163,6 @@ public final class AuthService {

private var providers: [AuthProviderUI] = []

public var currentPhoneProvider: PhoneAuthProviderSwift? {
providers.compactMap { $0.provider as? PhoneAuthProviderSwift }.first
}

public func registerProvider(providerWithButton: AuthProviderUI) {
providers.append(providerWithButton)
}
Expand All @@ -189,7 +184,7 @@ public final class AuthService {
)
}

public func signIn(_ provider: AuthProviderSwift) async throws -> SignInOutcome {
public func signIn(_ provider: CredentialAuthProviderSwift) async throws -> SignInOutcome {
do {
let credential = try await provider.createAuthCredential()
let result = try await signIn(credentials: credential)
Expand Down Expand Up @@ -829,8 +824,15 @@ public extension AuthService {
let password = try await passwordPrompt.confirmPassword()
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
_ = try await user.reauthenticate(with: credential)
} else if let matchingProvider = providers.first(where: { $0.id == providerId }) {
let credential = try await matchingProvider.provider.createAuthCredential()
} else if providerId == PhoneAuthProviderID {
// Phone auth requires manual reauthentication via sign out and sign in otherwise it will take
// the user out of the existing flow
throw AuthServiceError.reauthenticationRequired(
Copy link
Member Author

Choose a reason for hiding this comment

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

@demolaf - one of the reasons why I liked phone auth being a separate flow is because now when the user needs to reauthenticate for sensitive operations (e.g. changing password), we have to throw error asking the user to sign-out and sign back in again because pushing user to enter phone number will clear the View they're in already and state would be lost.

"Phone authentication requires you to sign out and sign in again to continue"
)
} else if let matchingProvider = providers.first(where: { $0.id == providerId }),
let credentialProvider = matchingProvider.provider as? CredentialAuthProviderSwift {
let credential = try await credentialProvider.createAuthCredential()
_ = try await user.reauthenticate(with: credential)
} else {
throw AuthServiceError.providerNotFound("No provider found for \(providerId)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,8 @@ struct EnterPhoneNumberView: View {
Button(action: {
Task {
do {
guard let provider = authService.currentPhoneProvider else {
fatalError("No phone provider found")
}
let fullPhoneNumber = selectedCountry.dialCode + phoneNumber
let id = try await provider.verifyPhoneNumber(phoneNumber: fullPhoneNumber)
let id = try await authService.verifyPhoneNumber(phoneNumber: fullPhoneNumber)
authService.navigator.push(.enterVerificationCode(
verificationID: id,
fullPhoneNumber: fullPhoneNumber
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,10 @@ struct EnterVerificationCodeView: View {
Button(action: {
Task {
do {
guard let provider = authService.currentPhoneProvider else {
fatalError("No phone provider found")
}
let credential = try await provider.createAuthCredential(
verificationId: verificationID,
try await authService.signInWithPhoneNumber(
verificationID: verificationID,
verificationCode: verificationCode
)

_ = try await authService.signIn(credentials: credential)
authService.navigator.clear()
} catch {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import FirebaseAuth
import FirebaseAuthSwiftUI
import SwiftUI

public class FacebookProviderSwift: AuthProviderSwift {
public class FacebookProviderSwift: CredentialAuthProviderSwift {
let scopes: [String]
let providerId = "facebook.com"
private let loginManager = LoginManager()
Expand Down Expand Up @@ -119,14 +119,15 @@ public class FacebookProviderSwift: AuthProviderSwift {
}

public class FacebookProviderAuthUI: AuthProviderUI {
public var provider: AuthProviderSwift
private let typedProvider: FacebookProviderSwift
public var provider: AuthProviderSwift { typedProvider }
public let id: String = "facebook.com"

public init(provider: AuthProviderSwift) {
self.provider = provider
public init(provider: FacebookProviderSwift) {
typedProvider = provider
}

@MainActor public func authButton() -> AnyView {
AnyView(SignInWithFacebookButton(facebookProvider: provider as! FacebookProviderSwift))
AnyView(SignInWithFacebookButton(facebookProvider: typedProvider))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

public class GoogleProviderSwift: AuthProviderSwift {
public class GoogleProviderSwift: CredentialAuthProviderSwift {
let scopes: [String]
let clientID: String
let providerId = "google.com"
Expand Down Expand Up @@ -71,14 +71,15 @@ public class GoogleProviderSwift: AuthProviderSwift {
}

public class GoogleProviderAuthUI: AuthProviderUI {
public var provider: AuthProviderSwift
private let typedProvider: GoogleProviderSwift
public var provider: AuthProviderSwift { typedProvider }
public let id: String = "google.com"

public init(provider: AuthProviderSwift) {
self.provider = provider
public init(provider: GoogleProviderSwift) {
typedProvider = provider
}

@MainActor public func authButton() -> AnyView {
AnyView(SignInWithGoogleButton(googleProvider: provider))
AnyView(SignInWithGoogleButton(googleProvider: typedProvider))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import SwiftUI
@MainActor
public struct SignInWithGoogleButton {
@Environment(AuthService.self) private var authService
let googleProvider: AuthProviderSwift
let googleProvider: GoogleProviderSwift

public init(googleProvider: AuthProviderSwift) {
public init(googleProvider: GoogleProviderSwift) {
self.googleProvider = googleProvider
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import FirebaseCore
import SwiftUI

/// Configuration for a generic OAuth provider
public class OAuthProviderSwift: AuthProviderSwift {
public class OAuthProviderSwift: CredentialAuthProviderSwift {
public let providerId: String
public let scopes: [String]
public let customParameters: [String: String]
Expand Down Expand Up @@ -116,20 +116,18 @@ public class OAuthProviderSwift: AuthProviderSwift {
}

public class OAuthProviderAuthUI: AuthProviderUI {
public var provider: AuthProviderSwift
private let typedProvider: OAuthProviderSwift
public var provider: AuthProviderSwift { typedProvider }

public init(provider: AuthProviderSwift) {
self.provider = provider
public init(provider: OAuthProviderSwift) {
typedProvider = provider
}

public var id: String {
guard let oauthProvider = provider as? OAuthProviderSwift else {
return "oauth.unknown"
}
return oauthProvider.providerId
return typedProvider.providerId
}

@MainActor public func authButton() -> AnyView {
AnyView(GenericOAuthButton(provider: provider))
AnyView(GenericOAuthButton(provider: typedProvider))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,28 @@ import SwiftUI
@MainActor
public struct GenericOAuthButton {
@Environment(AuthService.self) private var authService
let provider: AuthProviderSwift
public init(provider: AuthProviderSwift) {
let provider: OAuthProviderSwift
public init(provider: OAuthProviderSwift) {
self.provider = provider
}
}

extension GenericOAuthButton: View {
public var body: some View {
guard let oauthProvider = provider as? OAuthProviderSwift else {
return AnyView(
Text(authService.string.invalidOAuthProviderError)
.foregroundColor(.red)
)
}

// Create custom style from provider configuration
var resolvedStyle: ProviderStyle {
ProviderStyle(
icon: oauthProvider.buttonIcon,
backgroundColor: oauthProvider.buttonBackgroundColor,
contentColor: oauthProvider.buttonForegroundColor
icon: provider.buttonIcon,
backgroundColor: provider.buttonBackgroundColor,
contentColor: provider.buttonForegroundColor
)
}

return AnyView(
AuthProviderButton(
label: oauthProvider.displayName,
label: provider.displayName,
style: resolvedStyle,
accessibilityId: "sign-in-with-\(oauthProvider.providerId)-button"
accessibilityId: "sign-in-with-\(provider.providerId)-button"
) {
Task {
try? await authService.signIn(provider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.

@preconcurrency import FirebaseAuth
import FirebaseAuthSwiftUI
import SwiftUI

public typealias VerificationID = String

public class PhoneProviderSwift: PhoneAuthProviderSwift {
/// Simple provider marker for phone authentication
public class PhoneProviderSwift: AuthProviderSwift {
public init() {}

@MainActor public func verifyPhoneNumber(phoneNumber: String) async throws -> VerificationID {
return try await withCheckedThrowingContinuation { continuation in
PhoneAuthProvider.provider()
.verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
if let error = error {
continuation.resume(throwing: error)
return
}
continuation.resume(returning: verificationID!)
}
}
}

@MainActor public func createAuthCredential() async throws -> AuthCredential {
fatalError("Not implemented")
}

@MainActor public func createAuthCredential(verificationId: String,
verificationCode: String) async throws
-> AuthCredential {
return PhoneAuthProvider.provider()
.credential(withVerificationID: verificationId, verificationCode: verificationCode)
}
}

public class PhoneAuthProviderAuthUI: AuthProviderUI {
public var provider: AuthProviderSwift
private let typedProvider: PhoneProviderSwift
public var provider: AuthProviderSwift { typedProvider }
public let id: String = "phone"

public init(provider: PhoneAuthProviderSwift? = nil) {
self.provider = provider ?? PhoneProviderSwift()
public init() {
typedProvider = PhoneProviderSwift()
}

@MainActor public func authButton() -> AnyView {
AnyView(PhoneAuthButtonView(phoneProvider: provider as! PhoneAuthProviderSwift))
AnyView(PhoneAuthButtonView())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ import SwiftUI
@MainActor
public struct PhoneAuthButtonView {
@Environment(AuthService.self) private var authService
let phoneProvider: PhoneAuthProviderSwift

public init(phoneProvider: PhoneAuthProviderSwift) {
self.phoneProvider = phoneProvider
}
public init() {}
}

extension PhoneAuthButtonView: View {
Expand All @@ -41,7 +38,6 @@ extension PhoneAuthButtonView: View {

#Preview {
FirebaseOptions.dummyConfigurationForPreview()
let phoneProvider = PhoneProviderSwift()
return PhoneAuthButtonView(phoneProvider: phoneProvider)
return PhoneAuthButtonView()
.environment(AuthService())
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import FirebaseAuthSwiftUI
import FirebaseCore
import SwiftUI

public class TwitterProviderSwift: AuthProviderSwift {
public class TwitterProviderSwift: CredentialAuthProviderSwift {
public let scopes: [String]
let providerId = "twitter.com"

Expand Down Expand Up @@ -45,15 +45,15 @@ public class TwitterProviderSwift: AuthProviderSwift {
}

public class TwitterProviderAuthUI: AuthProviderUI {
public var provider: AuthProviderSwift
private let typedProvider: TwitterProviderSwift
public var provider: AuthProviderSwift { typedProvider }
public let id: String = "twitter.com"

public init(provider: AuthProviderSwift) {
self.provider = provider
public init(provider: TwitterProviderSwift) {
typedProvider = provider
}

public let id: String = "twitter.com"

@MainActor public func authButton() -> AnyView {
AnyView(SignInWithTwitterButton(provider: provider))
AnyView(SignInWithTwitterButton(provider: typedProvider))
}
}
Loading
Loading