mirror of
https://github.com/firebase/firebase-ios-sdk.git
synced 2026-10-02 07:04:46 +08:00
[Auth] Add support for upcoming Recaptcha changes (#14201)
This commit is contained in:
@@ -63,7 +63,7 @@ supports email and password accounts, as well as several 3rd party authenticatio
|
||||
s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0'
|
||||
s.dependency 'GoogleUtilities/Environment', '~> 8.0'
|
||||
s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0'
|
||||
s.ios.dependency 'RecaptchaInterop', '~> 100.0'
|
||||
s.ios.dependency 'RecaptchaInterop', '~> 101.0'
|
||||
s.test_spec 'unit' do |unit_tests|
|
||||
unit_tests.scheme = { :code_coverage => true }
|
||||
# Unit tests can't run on watchOS.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Unreleased
|
||||
- [changed] Using reCAPTCHA Enterprise and Firebase Auth requires reCAPTCHA
|
||||
Enterprise 18.7.0 or later.
|
||||
|
||||
# 11.8.0
|
||||
- [added] Added `ActionCodeSettings.linkDomain` to customize the Firebase Hosting link domain
|
||||
that is used in out-of-band email action flows.
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2024 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 <TargetConditionals.h>
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRRecaptchaBridge.h"
|
||||
#import "RecaptchaInterop/RecaptchaInterop.h"
|
||||
|
||||
// This is thread safe since it is only called by the AuthRecaptchaVerifier singleton.
|
||||
static id<RCARecaptchaClientProtocol> recaptchaClient;
|
||||
|
||||
static void retrieveToken(NSString *actionString,
|
||||
NSString *fakeToken,
|
||||
FIRAuthRecaptchaTokenCallback callback) {
|
||||
Class RecaptchaActionClass = NSClassFromString(@"RecaptchaAction");
|
||||
SEL customActionSelector = NSSelectorFromString(@"initWithCustomAction:");
|
||||
if (RecaptchaActionClass &&
|
||||
[RecaptchaActionClass instancesRespondToSelector:customActionSelector]) {
|
||||
// Initialize with a custom action
|
||||
id (*funcWithCustomAction)(id, SEL, NSString *) = (id(*)(
|
||||
id, SEL, NSString *))[RecaptchaActionClass instanceMethodForSelector:customActionSelector];
|
||||
|
||||
id<RCAActionProtocol> customAction = funcWithCustomAction([[RecaptchaActionClass alloc] init],
|
||||
customActionSelector, actionString);
|
||||
if (customAction) {
|
||||
[recaptchaClient execute:customAction
|
||||
completion:^(NSString *_Nullable token, NSError *_Nullable error) {
|
||||
if (!error) {
|
||||
callback(token, nil, YES, YES);
|
||||
return;
|
||||
} else {
|
||||
callback(fakeToken, nil, YES, YES);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
// RecaptchaAction class creation failed.
|
||||
callback(@"", nil, YES, NO);
|
||||
}
|
||||
|
||||
} else {
|
||||
// RecaptchaEnterprise not linked.
|
||||
callback(@"", nil, NO, NO);
|
||||
}
|
||||
}
|
||||
|
||||
void FIRRecaptchaGetToken(NSString *siteKey,
|
||||
NSString *actionString,
|
||||
NSString *fakeToken,
|
||||
FIRAuthRecaptchaTokenCallback callback) {
|
||||
if (recaptchaClient != nil) {
|
||||
retrieveToken(actionString, fakeToken, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
Class RecaptchaClass = NSClassFromString(@"Recaptcha");
|
||||
SEL selector = NSSelectorFromString(@"getClientWithSiteKey:completion:");
|
||||
if (RecaptchaClass && [RecaptchaClass respondsToSelector:selector]) {
|
||||
void (*funcWithoutTimeout)(id, SEL, NSString *,
|
||||
void (^)(id<RCARecaptchaClientProtocol> _Nullable recaptchaClient,
|
||||
NSError *_Nullable error)) =
|
||||
(void *)[RecaptchaClass methodForSelector:selector];
|
||||
funcWithoutTimeout(RecaptchaClass, selector, siteKey,
|
||||
^(id<RCARecaptchaClientProtocol> _Nonnull client, NSError *_Nullable error) {
|
||||
if (error) {
|
||||
callback(@"", error, YES, YES);
|
||||
} else {
|
||||
recaptchaClient = client;
|
||||
retrieveToken(actionString, fakeToken, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// RecaptchaEnterprise not linked.
|
||||
callback(@"", nil, NO, NO);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2024 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 <Foundation/Foundation.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
typedef void (^FIRAuthRecaptchaTokenCallback)(NSString *_Nonnull token,
|
||||
NSError *_Nullable error,
|
||||
BOOL linked,
|
||||
BOOL recaptchaActionCreated);
|
||||
|
||||
// Provide a bridge to the Objective-C protocol provided by the optional Recaptcha Enterprise
|
||||
// dependency. Once the Recaptcha Enterprise provides a Swift interop protocol, this C and
|
||||
// Objective-C code can be converted to Swift. Casting to a Objective-C protocol does not seem
|
||||
// possible in Swift. The C API is a workaround for linkage problems with an Objective-C API.
|
||||
void FIRRecaptchaGetToken(NSString *_Nonnull siteKey,
|
||||
NSString *_Nonnull actionString,
|
||||
NSString *_Nonnull fakeToken,
|
||||
_Nonnull FIRAuthRecaptchaTokenCallback callback);
|
||||
#endif
|
||||
@@ -26,5 +26,4 @@
|
||||
#import "FIRGoogleAuthProvider.h"
|
||||
#import "FIRMultiFactor.h"
|
||||
#import "FIRPhoneAuthProvider.h"
|
||||
#import "FIRRecaptchaBridge.h"
|
||||
#import "FIRTwitterAuthProvider.h"
|
||||
|
||||
@@ -125,38 +125,85 @@
|
||||
// No recaptcha on internal build system.
|
||||
return actionString
|
||||
#else
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
FIRRecaptchaGetToken(siteKey, actionString,
|
||||
"NO_RECAPTCHA") { (token: String, error: Error?,
|
||||
linked: Bool, actionCreated: Bool) in
|
||||
guard linked else {
|
||||
continuation.resume(throwing: AuthErrorUtils.recaptchaSDKNotLinkedError())
|
||||
return
|
||||
}
|
||||
guard actionCreated else {
|
||||
continuation.resume(throwing: AuthErrorUtils.recaptchaActionCreationFailed())
|
||||
return
|
||||
}
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
return
|
||||
} else {
|
||||
if token == "NO_RECAPTCHA" {
|
||||
AuthLog.logInfo(code: "I-AUT000031",
|
||||
message: "reCAPTCHA token retrieval failed. NO_RECAPTCHA sent as the fake code.")
|
||||
} else {
|
||||
AuthLog.logInfo(
|
||||
code: "I-AUT000030",
|
||||
message: "reCAPTCHA token retrieval succeeded."
|
||||
)
|
||||
}
|
||||
continuation.resume(returning: token)
|
||||
}
|
||||
}
|
||||
|
||||
let (token, error, linked, actionCreated) = await recaptchaToken(
|
||||
siteKey: siteKey,
|
||||
actionString: actionString,
|
||||
fakeToken: "NO_RECAPTCHA"
|
||||
)
|
||||
|
||||
guard linked else {
|
||||
throw AuthErrorUtils.recaptchaSDKNotLinkedError()
|
||||
}
|
||||
guard actionCreated else {
|
||||
throw AuthErrorUtils.recaptchaActionCreationFailed()
|
||||
}
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
if token == "NO_RECAPTCHA" {
|
||||
AuthLog.logInfo(code: "I-AUT000031",
|
||||
message: "reCAPTCHA token retrieval failed. NO_RECAPTCHA sent as the fake code.")
|
||||
} else {
|
||||
AuthLog.logInfo(
|
||||
code: "I-AUT000030",
|
||||
message: "reCAPTCHA token retrieval succeeded."
|
||||
)
|
||||
}
|
||||
return token
|
||||
#endif // !(COCOAPODS || SWIFT_PACKAGE)
|
||||
}
|
||||
|
||||
private static var recaptchaClient: (any RCARecaptchaClientProtocol)?
|
||||
|
||||
private func recaptchaToken(siteKey: String,
|
||||
actionString: String,
|
||||
fakeToken: String) async -> (token: String, error: Error?,
|
||||
linked: Bool, actionCreated: Bool) {
|
||||
if let recaptchaClient {
|
||||
return await retrieveToken(
|
||||
actionString: actionString,
|
||||
fakeToken: fakeToken,
|
||||
recaptchaClient: recaptchaClient
|
||||
)
|
||||
}
|
||||
|
||||
if let recaptcha =
|
||||
NSClassFromString("RecaptchaEnterprise.RCARecaptcha") as? RCARecaptchaProtocol.Type {
|
||||
do {
|
||||
let client = try await recaptcha.fetchClient(withSiteKey: siteKey)
|
||||
recaptchaClient = client
|
||||
return await retrieveToken(
|
||||
actionString: actionString,
|
||||
fakeToken: fakeToken,
|
||||
recaptchaClient: client
|
||||
)
|
||||
} catch {
|
||||
return ("", error, true, true)
|
||||
}
|
||||
} else {
|
||||
// RecaptchaEnterprise not linked.
|
||||
return ("", nil, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
private func retrieveToken(actionString: String,
|
||||
fakeToken: String,
|
||||
recaptchaClient: RCARecaptchaClientProtocol) async -> (token: String,
|
||||
error: Error?,
|
||||
linked: Bool,
|
||||
actionCreated: Bool) {
|
||||
if let recaptchaAction =
|
||||
NSClassFromString("RecaptchaEnterprise.RCAAction") as? RCAActionProtocol.Type {
|
||||
let action = recaptchaAction.init(customAction: actionString)
|
||||
let token = try? await recaptchaClient.execute(withAction: action)
|
||||
return (token ?? "NO_RECAPTCHA", nil, true, true)
|
||||
} else {
|
||||
// RecaptchaEnterprise not linked.
|
||||
return ("", nil, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveRecaptchaConfig(forceRefresh: Bool) async throws {
|
||||
if !forceRefresh {
|
||||
if let tenantID = auth?.tenantID {
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ let package = Package(
|
||||
),
|
||||
.package(
|
||||
url: "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||
"100.0.0" ..< "101.0.0"
|
||||
"101.0.0" ..< "102.0.0"
|
||||
),
|
||||
.package(url: "https://github.com/google/app-check.git",
|
||||
"11.0.1" ..< "12.0.0"),
|
||||
|
||||
Reference in New Issue
Block a user