Skip to content

Commit 4c528b7

Browse files
committed
[Auth] Remove AuthBackendRPCImplementation type
1 parent 948c460 commit 4c528b7

File tree

6 files changed

+178
-175
lines changed

6 files changed

+178
-175
lines changed

FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift

Lines changed: 81 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -22,82 +22,53 @@ import Foundation
2222
#endif
2323

2424
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
25-
protocol AuthBackendRPCIssuer {
26-
/// Asynchronously send a HTTP request.
27-
/// - Parameter request: The request to be made.
28-
/// - Parameter body: Request body.
29-
/// - Parameter contentType: Content type of the body.
30-
/// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously
31-
/// on the auth global work queue in the future.
32-
func asyncCallToURL<T: AuthRPCRequest>(with request: T,
33-
body: Data?,
34-
contentType: String) async -> (Data?, Error?)
35-
}
36-
37-
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
38-
class AuthBackendRPCIssuerImplementation: AuthBackendRPCIssuer {
39-
let fetcherService: GTMSessionFetcherService
40-
41-
init() {
42-
fetcherService = GTMSessionFetcherService()
43-
fetcherService.userAgent = AuthBackend.authUserAgent()
44-
fetcherService.callbackQueue = kAuthGlobalWorkQueue
45-
46-
// Avoid reusing the session to prevent
47-
// https://github.com/firebase/firebase-ios-sdk/issues/1261
48-
fetcherService.reuseSession = false
49-
}
50-
51-
func asyncCallToURL<T: AuthRPCRequest>(with request: T,
52-
body: Data?,
53-
contentType: String) async -> (Data?, Error?) {
54-
let requestConfiguration = request.requestConfiguration()
55-
let request = await AuthBackend.request(withURL: request.requestURL(),
56-
contentType: contentType,
57-
requestConfiguration: requestConfiguration)
58-
let fetcher = fetcherService.fetcher(with: request)
59-
if let _ = requestConfiguration.emulatorHostAndPort {
60-
fetcher.allowLocalhostRequest = true
61-
fetcher.allowedInsecureSchemes = ["http"]
62-
}
63-
fetcher.bodyData = body
64-
65-
return await withUnsafeContinuation { continuation in
66-
fetcher.beginFetch { data, error in
67-
continuation.resume(returning: (data, error))
68-
}
69-
}
70-
}
25+
protocol AuthBackendProtocol {
26+
func call<T: AuthRPCRequest>(with request: T) async throws -> T.Response
7127
}
7228

7329
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
74-
class AuthBackend {
30+
class AuthBackend: AuthBackendProtocol {
7531
static func authUserAgent() -> String {
7632
return "FirebaseAuth.iOS/\(FirebaseVersion()) \(GTMFetcherStandardUserAgentString(nil))"
7733
}
7834

79-
private static var realRPCBackend = AuthBackendRPCImplementation()
80-
private static var gBackendImplementation = realRPCBackend
81-
82-
class func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) {
83-
gBackendImplementation.rpcIssuer = issuer
35+
static func call<T: AuthRPCRequest>(with request: T) async throws -> T.Response {
36+
return try await shared.call(with: request)
8437
}
8538

86-
class func resetRPCIssuer() {
87-
gBackendImplementation.rpcIssuer = realRPCBackend.rpcIssuer
88-
}
39+
private static let shared: AuthBackend = .init(rpcIssuer: AuthBackendRPCIssuer())
8940

90-
class func implementation() -> AuthBackendImplementation {
91-
return gBackendImplementation
41+
private let rpcIssuer: any AuthBackendRPCIssuerProtocol
42+
43+
init(rpcIssuer: any AuthBackendRPCIssuerProtocol) {
44+
self.rpcIssuer = rpcIssuer
9245
}
9346

94-
class func call<T: AuthRPCRequest>(with request: T) async throws -> T.Response {
95-
return try await implementation().call(with: request)
47+
/// Calls the RPC using HTTP request.
48+
/// Possible error responses:
49+
/// * See FIRAuthInternalErrorCodeRPCRequestEncodingError
50+
/// * See FIRAuthInternalErrorCodeJSONSerializationError
51+
/// * See FIRAuthInternalErrorCodeNetworkError
52+
/// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse
53+
/// * See FIRAuthInternalErrorCodeUnexpectedResponse
54+
/// * See FIRAuthInternalErrorCodeRPCResponseDecodingError
55+
/// - Parameter request: The request.
56+
/// - Returns: The response.
57+
func call<T: AuthRPCRequest>(with request: T) async throws -> T.Response {
58+
let response = try await callInternal(with: request)
59+
if let auth = request.requestConfiguration().auth,
60+
let mfaError = Self.generateMFAError(response: response, auth: auth) {
61+
throw mfaError
62+
} else if let error = Self.phoneCredentialInUse(response: response) {
63+
throw error
64+
} else {
65+
return response
66+
}
9667
}
9768

98-
class func request(withURL url: URL,
99-
contentType: String,
100-
requestConfiguration: AuthRequestConfiguration) async -> URLRequest {
69+
static func request(withURL url: URL,
70+
contentType: String,
71+
requestConfiguration: AuthRequestConfiguration) async -> URLRequest {
10172
// Kick off tasks for the async header values.
10273
async let heartbeatsHeaderValue = requestConfiguration.heartbeatLogger?.asyncHeaderValue()
10374
async let appCheckTokenHeaderValue = requestConfiguration.appCheck?
@@ -132,94 +103,56 @@ class AuthBackend {
132103
}
133104
return request
134105
}
135-
}
136106

137-
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
138-
protocol AuthBackendImplementation {
139-
func call<T: AuthRPCRequest>(with request: T) async throws -> T.Response
140-
}
141-
142-
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
143-
private class AuthBackendRPCImplementation: AuthBackendImplementation {
144-
var rpcIssuer: AuthBackendRPCIssuer = AuthBackendRPCIssuerImplementation()
145-
146-
/// Calls the RPC using HTTP request.
147-
/// Possible error responses:
148-
/// * See FIRAuthInternalErrorCodeRPCRequestEncodingError
149-
/// * See FIRAuthInternalErrorCodeJSONSerializationError
150-
/// * See FIRAuthInternalErrorCodeNetworkError
151-
/// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse
152-
/// * See FIRAuthInternalErrorCodeUnexpectedResponse
153-
/// * See FIRAuthInternalErrorCodeRPCResponseDecodingError
154-
/// - Parameter request: The request.
155-
/// - Returns: The response.
156-
fileprivate func call<T: AuthRPCRequest>(with request: T) async throws -> T.Response {
157-
let response = try await callInternal(with: request)
158-
if let auth = request.requestConfiguration().auth,
159-
let mfaError = Self.generateMFAError(response: response, auth: auth) {
160-
throw mfaError
161-
} else if let error = Self.phoneCredentialInUse(response: response) {
162-
throw error
163-
} else {
164-
return response
165-
}
166-
}
167-
168-
#if os(iOS)
169-
private class func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? {
170-
if let mfaResponse = response as? AuthMFAResponse,
171-
mfaResponse.idToken == nil,
172-
let enrollments = mfaResponse.mfaInfo {
173-
var info: [MultiFactorInfo] = []
174-
for enrollment in enrollments {
175-
// check which MFA factors are enabled.
176-
if let _ = enrollment.phoneInfo {
177-
info.append(PhoneMultiFactorInfo(proto: enrollment))
178-
} else if let _ = enrollment.totpInfo {
179-
info.append(TOTPMultiFactorInfo(proto: enrollment))
180-
} else {
181-
AuthLog.logError(code: "I-AUT000021", message: "Multifactor type is not supported")
182-
}
107+
private static func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? {
108+
#if !os(iOS)
109+
return nil
110+
#endif // !os(iOS)
111+
if let mfaResponse = response as? AuthMFAResponse,
112+
mfaResponse.idToken == nil,
113+
let enrollments = mfaResponse.mfaInfo {
114+
var info: [MultiFactorInfo] = []
115+
for enrollment in enrollments {
116+
// check which MFA factors are enabled.
117+
if let _ = enrollment.phoneInfo {
118+
info.append(PhoneMultiFactorInfo(proto: enrollment))
119+
} else if let _ = enrollment.totpInfo {
120+
info.append(TOTPMultiFactorInfo(proto: enrollment))
121+
} else {
122+
AuthLog.logError(code: "I-AUT000021", message: "Multifactor type is not supported")
183123
}
184-
return AuthErrorUtils.secondFactorRequiredError(
185-
pendingCredential: mfaResponse.mfaPendingCredential,
186-
hints: info,
187-
auth: auth
188-
)
189-
} else {
190-
return nil
191124
}
192-
}
193-
#else
194-
private class func generateMFAError(response: AuthRPCResponse, auth: Auth?) -> Error? {
125+
return AuthErrorUtils.secondFactorRequiredError(
126+
pendingCredential: mfaResponse.mfaPendingCredential,
127+
hints: info,
128+
auth: auth
129+
)
130+
} else {
195131
return nil
196132
}
197-
#endif
133+
}
198134

199-
#if os(iOS)
200-
// Check whether or not the successful response is actually the special case phone
201-
// auth flow that returns a temporary proof and phone number.
202-
private class func phoneCredentialInUse(response: AuthRPCResponse) -> Error? {
203-
if let phoneAuthResponse = response as? VerifyPhoneNumberResponse,
204-
let phoneNumber = phoneAuthResponse.phoneNumber,
205-
phoneNumber.count > 0,
206-
let temporaryProof = phoneAuthResponse.temporaryProof,
207-
temporaryProof.count > 0 {
208-
let credential = PhoneAuthCredential(withTemporaryProof: temporaryProof,
209-
phoneNumber: phoneNumber,
210-
providerID: PhoneAuthProvider.id)
211-
return AuthErrorUtils.credentialAlreadyInUseError(message: nil,
212-
credential: credential,
213-
email: nil)
214-
} else {
215-
return nil
216-
}
217-
}
218-
#else
219-
private class func phoneCredentialInUse(response: AuthRPCResponse) -> Error? {
135+
// Check whether or not the successful response is actually the special case phone
136+
// auth flow that returns a temporary proof and phone number.
137+
private static func phoneCredentialInUse(response: AuthRPCResponse) -> Error? {
138+
#if !os(iOS)
139+
return nil
140+
#endif // !os(iOS)
141+
if let phoneAuthResponse = response as? VerifyPhoneNumberResponse,
142+
let phoneNumber = phoneAuthResponse.phoneNumber,
143+
phoneNumber.count > 0,
144+
let temporaryProof = phoneAuthResponse.temporaryProof,
145+
temporaryProof.count > 0 {
146+
let credential = PhoneAuthCredential(withTemporaryProof: temporaryProof,
147+
phoneNumber: phoneNumber,
148+
providerID: PhoneAuthProvider.id)
149+
return AuthErrorUtils.credentialAlreadyInUseError(message: nil,
150+
credential: credential,
151+
email: nil)
152+
} else {
220153
return nil
221154
}
222-
#endif
155+
}
223156

224157
/// Calls the RPC using HTTP request.
225158
///
@@ -318,7 +251,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation {
318251
if error != nil {
319252
if let errorDictionary = dictionary["error"] as? [String: AnyHashable] {
320253
if let errorMessage = errorDictionary["message"] as? String {
321-
if let clientError = AuthBackendRPCImplementation.clientError(
254+
if let clientError = Self.clientError(
322255
withServerErrorMessage: errorMessage,
323256
errorDictionary: errorDictionary,
324257
response: response,
@@ -351,7 +284,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation {
351284
if let verifyAssertionRequest = request as? VerifyAssertionRequest {
352285
if verifyAssertionRequest.returnIDPCredential {
353286
if let errorMessage = dictionary["errorMessage"] as? String {
354-
if let clientError = AuthBackendRPCImplementation.clientError(
287+
if let clientError = Self.clientError(
355288
withServerErrorMessage: errorMessage,
356289
errorDictionary: dictionary,
357290
response: response,
@@ -365,10 +298,10 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation {
365298
return response
366299
}
367300

368-
private class func clientError(withServerErrorMessage serverErrorMessage: String,
369-
errorDictionary: [String: Any],
370-
response: AuthRPCResponse,
371-
error: Error?) -> Error? {
301+
private static func clientError(withServerErrorMessage serverErrorMessage: String,
302+
errorDictionary: [String: Any],
303+
response: AuthRPCResponse,
304+
error: Error?) -> Error? {
372305
let split = serverErrorMessage.split(separator: ":")
373306
let shortErrorMessage = split.first?.trimmingCharacters(in: .whitespacesAndNewlines)
374307
let serverDetailErrorMessage = String(split.count > 1 ? split[1] : "")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2024 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 FirebaseCore
16+
import FirebaseCoreExtension
17+
import Foundation
18+
#if COCOAPODS
19+
import GTMSessionFetcher
20+
#else
21+
import GTMSessionFetcherCore
22+
#endif
23+
24+
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
25+
protocol AuthBackendRPCIssuerProtocol {
26+
/// Asynchronously send a HTTP request.
27+
/// - Parameter request: The request to be made.
28+
/// - Parameter body: Request body.
29+
/// - Parameter contentType: Content type of the body.
30+
/// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously
31+
/// on the auth global work queue in the future.
32+
func asyncCallToURL<T: AuthRPCRequest>(with request: T,
33+
body: Data?,
34+
contentType: String) async -> (Data?, Error?)
35+
}
36+
37+
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
38+
class AuthBackendRPCIssuer: AuthBackendRPCIssuerProtocol {
39+
let fetcherService: GTMSessionFetcherService
40+
41+
init() {
42+
fetcherService = GTMSessionFetcherService()
43+
fetcherService.userAgent = AuthBackend.authUserAgent()
44+
fetcherService.callbackQueue = kAuthGlobalWorkQueue
45+
46+
// Avoid reusing the session to prevent
47+
// https://github.com/firebase/firebase-ios-sdk/issues/1261
48+
fetcherService.reuseSession = false
49+
}
50+
51+
func asyncCallToURL<T: AuthRPCRequest>(with request: T,
52+
body: Data?,
53+
contentType: String) async -> (Data?, Error?) {
54+
let requestConfiguration = request.requestConfiguration()
55+
let request = await AuthBackend.request(withURL: request.requestURL(),
56+
contentType: contentType,
57+
requestConfiguration: requestConfiguration)
58+
let fetcher = fetcherService.fetcher(with: request)
59+
if let _ = requestConfiguration.emulatorHostAndPort {
60+
fetcher.allowLocalhostRequest = true
61+
fetcher.allowedInsecureSchemes = ["http"]
62+
}
63+
fetcher.bodyData = body
64+
65+
return await withUnsafeContinuation { continuation in
66+
fetcher.beginFetch { data, error in
67+
continuation.resume(returning: (data, error))
68+
}
69+
}
70+
}
71+
}

FirebaseAuth/Sources/Swift/User/User.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,7 @@ extension User: NSSecureCoding {}
13961396
action: AuthRecaptchaAction
13971397
.signUpPassword)
13981398
#else
1399-
let response = try await AuthBackend.call(with: request)
1399+
let response = try await backend.call(with: request)
14001400
#endif
14011401
guard let refreshToken = response.refreshToken,
14021402
let idToken = response.idToken else {

0 commit comments

Comments
 (0)