Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Auth] Alternative recaptcha approach #14217

Merged
merged 2 commits into from
Dec 6, 2024
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
84 changes: 12 additions & 72 deletions FirebaseAuth/Sources/ObjC/FIRRecaptchaBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,23 @@
#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(@"RecaptchaEnterprise.RCAAction");
SEL customActionSelector = NSSelectorFromString(@"initWithCustomAction:");
if (!RecaptchaActionClass) {
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
RecaptchaActionClass = NSClassFromString(@"RecaptchaAction");
}

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], 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);
}

Class<RCARecaptchaProtocol> _Nonnull __fir_castToRecaptchaProtocolFromClass(Class _Nonnull klass) {
if ([klass conformsToProtocol:@protocol(RCARecaptchaProtocol)]) {
NSLog(@"RCARecaptchaProtocol - true");
} else {
// RecaptchaEnterprise not linked.
callback(@"", nil, NO, NO);
NSLog(@"RCARecaptchaProtocol - false");
}
return (Class<RCARecaptchaProtocol>)klass;
}

void FIRRecaptchaGetToken(NSString *siteKey,
NSString *actionString,
NSString *fakeToken,
FIRAuthRecaptchaTokenCallback callback) {
if (recaptchaClient != nil) {
retrieveToken(actionString, fakeToken, callback);
return;
}

// Why not use `conformsToProtocol`?
Class RecaptchaClass = NSClassFromString(@"RecaptchaEnterprise.RCARecaptcha");
SEL selector = NSSelectorFromString(@"fetchClientWithSiteKey:completion:");
if (!RecaptchaClass) {
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
RecaptchaClass = NSClassFromString(@"Recaptcha");
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);
}
});
Class<RCAActionProtocol> _Nonnull __fir_castToRecaptchaActionProtocolFromClass(
Class _Nonnull klass) {
if ([klass conformsToProtocol:@protocol(RCAActionProtocol)]) {
NSLog(@"RCAActionProtocol - true");
} else {
// RecaptchaEnterprise not linked.
callback(@"", nil, NO, NO);
NSLog(@"RCAActionProtocol - false");
}
return (Class<RCAActionProtocol>)klass;
}

#endif
19 changes: 7 additions & 12 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIRRecaptchaBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,12 @@

#if TARGET_OS_IOS

typedef void (^FIRAuthRecaptchaTokenCallback)(NSString *_Nonnull token,
NSError *_Nullable error,
BOOL linked,
BOOL recaptchaActionCreated);
@protocol RCARecaptchaProtocol;
@protocol RCAActionProtocol;

Class<RCARecaptchaProtocol> _Nonnull __fir_castToRecaptchaProtocolFromClass(Class _Nonnull klass);

Class<RCAActionProtocol> _Nonnull __fir_castToRecaptchaActionProtocolFromClass(
Class _Nonnull klass);

// 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
22 changes: 13 additions & 9 deletions FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
} else if let recaptcha = NSClassFromString("Recaptcha") {
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
do {
let recaptcha = __fir_castToRecaptchaProtocolFromClass(recaptcha)
let client = try await recaptcha.getClient(withSiteKey: siteKey)
recaptchaClient = client
return await retrieveToken(actionString: actionString, fakeToken: fakeToken)
Expand All @@ -192,18 +193,21 @@
private func retrieveToken(actionString: String,
fakeToken: String) async -> (token: String, error: Error?,
linked: Bool, actionCreated: Bool) {
let recaptchaAction = (
NSClassFromString("RecaptchaEnterprise.RCAAction") ?? NSClassFromString("RecaptchaAction")
) as? RCAActionProtocol.Type

guard let recaptchaAction else {
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 if let recaptchaAction = NSClassFromString("RecaptchaAction") {
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
let recaptchaAction = __fir_castToRecaptchaActionProtocolFromClass(recaptchaAction)
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)
}

let action = recaptchaAction.init(customAction: actionString)
let token = try? await recaptchaClient!.execute(withAction: action)
return (token ?? "NO_RECAPTCHA", nil, true, true)
}

func retrieveRecaptchaConfig(forceRefresh: Bool) async throws {
Expand Down
Loading