Skip to content

Commit 1a591ed

Browse files
committed
[Auth] Alternative recaptcha approach
1 parent d672490 commit 1a591ed

File tree

3 files changed

+63
-90
lines changed

3 files changed

+63
-90
lines changed

FirebaseAuth/Sources/ObjC/FIRRecaptchaBridge.m

Lines changed: 28 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -18,83 +18,43 @@
1818
#import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRRecaptchaBridge.h"
1919
#import "RecaptchaInterop/RecaptchaInterop.h"
2020

21-
// This is thread safe since it is only called by the AuthRecaptchaVerifier singleton.
22-
static id<RCARecaptchaClientProtocol> recaptchaClient;
23-
24-
static void retrieveToken(NSString *actionString,
25-
NSString *fakeToken,
26-
FIRAuthRecaptchaTokenCallback callback) {
27-
Class RecaptchaActionClass = NSClassFromString(@"RecaptchaEnterprise.RCAAction");
28-
SEL customActionSelector = NSSelectorFromString(@"initWithCustomAction:");
29-
if (!RecaptchaActionClass) {
30-
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
31-
RecaptchaActionClass = NSClassFromString(@"RecaptchaAction");
32-
}
33-
34-
if (RecaptchaActionClass &&
35-
[RecaptchaActionClass instancesRespondToSelector:customActionSelector]) {
36-
// Initialize with a custom action
37-
id (*funcWithCustomAction)(id, SEL, NSString *) = (id(*)(
38-
id, SEL, NSString *))[RecaptchaActionClass instanceMethodForSelector:customActionSelector];
39-
40-
id<RCAActionProtocol> customAction =
41-
funcWithCustomAction([RecaptchaActionClass alloc], customActionSelector, actionString);
42-
if (customAction) {
43-
[recaptchaClient execute:customAction
44-
completion:^(NSString *_Nullable token, NSError *_Nullable error) {
45-
if (!error) {
46-
callback(token, nil, YES, YES);
47-
return;
48-
} else {
49-
callback(fakeToken, nil, YES, YES);
50-
}
51-
}];
52-
} else {
53-
// RecaptchaAction class creation failed.
54-
callback(@"", nil, YES, NO);
55-
}
56-
57-
} else {
58-
// RecaptchaEnterprise not linked.
59-
callback(@"", nil, NO, NO);
60-
}
61-
}
62-
63-
void FIRRecaptchaGetToken(NSString *siteKey,
64-
NSString *actionString,
65-
NSString *fakeToken,
66-
FIRAuthRecaptchaTokenCallback callback) {
67-
if (recaptchaClient != nil) {
68-
retrieveToken(actionString, fakeToken, callback);
69-
return;
70-
}
71-
72-
// Why not use `conformsToProtocol`?
73-
Class RecaptchaClass = NSClassFromString(@"RecaptchaEnterprise.RCARecaptcha");
74-
SEL selector = NSSelectorFromString(@"fetchClientWithSiteKey:completion:");
75-
if (!RecaptchaClass) {
76-
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
77-
RecaptchaClass = NSClassFromString(@"Recaptcha");
78-
selector = NSSelectorFromString(@"getClientWithSiteKey:completion:");
79-
}
80-
81-
if (RecaptchaClass && [RecaptchaClass respondsToSelector:selector]) {
21+
void __objc_getClientWithSiteKey(
22+
NSString *siteKey,
23+
Class recaptchaClass,
24+
void (^completionHandler)(id<RCARecaptchaClientProtocol> _Nullable result,
25+
NSError *_Nullable error)) {
26+
SEL selector = NSSelectorFromString(@"getClientWithSiteKey:completion:");
27+
if (recaptchaClass && [recaptchaClass respondsToSelector:selector]) {
8228
void (*funcWithoutTimeout)(id, SEL, NSString *,
8329
void (^)(id<RCARecaptchaClientProtocol> _Nullable recaptchaClient,
8430
NSError *_Nullable error)) =
85-
(void *)[RecaptchaClass methodForSelector:selector];
86-
funcWithoutTimeout(RecaptchaClass, selector, siteKey,
31+
(void *)[recaptchaClass methodForSelector:selector];
32+
funcWithoutTimeout(recaptchaClass, selector, siteKey,
8733
^(id<RCARecaptchaClientProtocol> _Nonnull client, NSError *_Nullable error) {
8834
if (error) {
89-
callback(@"", error, YES, YES);
35+
completionHandler(nil, error);
9036
} else {
91-
recaptchaClient = client;
92-
retrieveToken(actionString, fakeToken, callback);
37+
completionHandler(client, nil);
9338
}
9439
});
9540
} else {
96-
// RecaptchaEnterprise not linked.
97-
callback(@"", nil, NO, NO);
41+
completionHandler(nil, nil); // TODO(ncooke3): Add error just in case.
9842
}
9943
}
44+
45+
id<RCAActionProtocol> _Nullable __fir_initActionFromClass(Class _Nonnull klass,
46+
NSString *_Nonnull actionString) {
47+
SEL customActionSelector = NSSelectorFromString(@"initWithCustomAction:");
48+
if (klass && [klass instancesRespondToSelector:customActionSelector]) {
49+
id (*funcWithCustomAction)(id, SEL, NSString *) =
50+
(id(*)(id, SEL, NSString *))[klass instanceMethodForSelector:customActionSelector];
51+
52+
id<RCAActionProtocol> customAction =
53+
funcWithCustomAction([klass alloc], customActionSelector, actionString);
54+
return customAction;
55+
} else {
56+
return nil;
57+
}
58+
}
59+
10060
#endif

FirebaseAuth/Sources/Public/FirebaseAuth/FIRRecaptchaBridge.h

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717

1818
#if TARGET_OS_IOS
1919

20-
typedef void (^FIRAuthRecaptchaTokenCallback)(NSString *_Nonnull token,
21-
NSError *_Nullable error,
22-
BOOL linked,
23-
BOOL recaptchaActionCreated);
20+
@protocol RCARecaptchaClientProtocol;
21+
@protocol RCAActionProtocol;
22+
23+
void __objc_getClientWithSiteKey(
24+
NSString *_Nonnull siteKey,
25+
Class _Nonnull recaptchaClass,
26+
void (^_Nonnull completionHandler)(id<RCARecaptchaClientProtocol> _Nullable result,
27+
NSError *_Nullable error));
28+
29+
id<RCAActionProtocol> _Nullable __fir_initActionFromClass(Class _Nonnull klass,
30+
NSString *_Nonnull actionString);
2431

25-
// Provide a bridge to the Objective-C protocol provided by the optional Recaptcha Enterprise
26-
// dependency. Once the Recaptcha Enterprise provides a Swift interop protocol, this C and
27-
// Objective-C code can be converted to Swift. Casting to a Objective-C protocol does not seem
28-
// possible in Swift. The C API is a workaround for linkage problems with an Objective-C API.
29-
void FIRRecaptchaGetToken(NSString *_Nonnull siteKey,
30-
NSString *_Nonnull actionString,
31-
NSString *_Nonnull fakeToken,
32-
_Nonnull FIRAuthRecaptchaTokenCallback callback);
3332
#endif

FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,18 @@
177177
} else if let recaptcha = NSClassFromString("Recaptcha") {
178178
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
179179
do {
180-
let client = try await recaptcha.getClient(withSiteKey: siteKey)
180+
let client: any RCARecaptchaClientProtocol =
181+
try await withCheckedThrowingContinuation { continuation in
182+
__objc_getClientWithSiteKey(siteKey, recaptcha) { client, error in
183+
if let error {
184+
continuation.resume(throwing: error)
185+
}
186+
if let client {
187+
continuation.resume(returning: client)
188+
}
189+
// TODO(ncooke3): Handle other case.
190+
}
191+
}
181192
recaptchaClient = client
182193
return await retrieveToken(actionString: actionString, fakeToken: fakeToken)
183194
} catch {
@@ -192,18 +203,21 @@
192203
private func retrieveToken(actionString: String,
193204
fakeToken: String) async -> (token: String, error: Error?,
194205
linked: Bool, actionCreated: Bool) {
195-
let recaptchaAction = (
196-
NSClassFromString("RecaptchaEnterprise.RCAAction") ?? NSClassFromString("RecaptchaAction")
197-
) as? RCAActionProtocol.Type
198-
199-
guard let recaptchaAction else {
206+
if let recaptchaAction =
207+
NSClassFromString("RecaptchaEnterprise.RCAAction") as? RCAActionProtocol.Type {
208+
let action = recaptchaAction.init(customAction: actionString)
209+
let token = try? await recaptchaClient!.execute(withAction: action)
210+
return (token ?? "NO_RECAPTCHA", nil, true, true)
211+
} else if
212+
let recaptchaAction = NSClassFromString("RecaptchaAction"),
213+
let action = __fir_initActionFromClass(recaptchaAction, actionString) {
214+
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
215+
let token = try? await recaptchaClient!.execute(withAction: action)
216+
return (token ?? "NO_RECAPTCHA", nil, true, true)
217+
} else {
200218
// RecaptchaEnterprise not linked.
201219
return ("", nil, false, false)
202220
}
203-
204-
let action = recaptchaAction.init(customAction: actionString)
205-
let token = try? await recaptchaClient!.execute(withAction: action)
206-
return (token ?? "NO_RECAPTCHA", nil, true, true)
207221
}
208222

209223
func retrieveRecaptchaConfig(forceRefresh: Bool) async throws {

0 commit comments

Comments
 (0)