Skip to content

[Auth] Add support for upcoming Recaptcha changes #14201

New issue

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

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

Already on GitHub? # to your account

Merged
merged 14 commits into from
Feb 3, 2025
Merged
22 changes: 17 additions & 5 deletions FirebaseAuth/Sources/ObjC/FIRRecaptchaBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,21 @@
static void retrieveToken(NSString *actionString,
NSString *fakeToken,
FIRAuthRecaptchaTokenCallback callback) {
Class RecaptchaActionClass = NSClassFromString(@"RecaptchaAction");
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] init],
customActionSelector, actionString);
id<RCAActionProtocol> customAction =
funcWithCustomAction([RecaptchaActionClass alloc], customActionSelector, actionString);
if (customAction) {
[recaptchaClient execute:customAction
completion:^(NSString *_Nullable token, NSError *_Nullable error) {
Expand Down Expand Up @@ -64,8 +69,15 @@ void FIRRecaptchaGetToken(NSString *siteKey,
return;
}

Class RecaptchaClass = NSClassFromString(@"Recaptcha");
SEL selector = NSSelectorFromString(@"getClientWithSiteKey:completion:");
// 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,
Expand Down
103 changes: 74 additions & 29 deletions FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,38 +125,83 @@
// 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 recaptchaClient != nil {
return await retrieveToken(actionString: actionString, fakeToken: fakeToken)
}

if let recaptcha = NSClassFromString("RecaptchaEnterprise.RCARecaptcha") as? RCARecaptchaProtocol.Type {
do {
// TODO(ncooke3): This should be `fetchClient(withSiteKey:)`.
let client = try await recaptcha.getClient(withSiteKey: siteKey)
recaptchaClient = client
return await retrieveToken(actionString: actionString, fakeToken: fakeToken)
} catch {
return ("", error, true, true);
}
} else if let recaptcha = NSClassFromString("Recaptcha") {
// Fall back to attempting to connect with pre-18.7.0 RecaptchaEnterprise.
let client = try await recaptcha.getClient(withSiteKey: siteKey)
recaptchaClient = client
return await retrieveToken(actionString: actionString, fakeToken: fakeToken)
} catch {
return ("", error, true, true);
}
} else {
// RecaptchaEnterprise not linked.
return ("", nil, false, false)
}
}

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 {
// 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 {
if !forceRefresh {
if let tenantID = auth?.tenantID {
Expand Down
Loading