diff --git a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java index a8157601ffea..1f9a73d433ab 100755 --- a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java +++ b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java @@ -678,6 +678,25 @@ public void revokeTokenWithAuthorizationCode( result.success(); } + @Override + public void initializeRecaptchaConfig( + @NonNull GeneratedAndroidFirebaseAuth.AuthPigeonFirebaseApp app, + @NonNull GeneratedAndroidFirebaseAuth.VoidResult result) { + FirebaseAuth firebaseAuth = getAuthFromPigeon(app); + firebaseAuth + .initializeRecaptchaConfig() + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + result.success(); + } else { + result.error( + FlutterFirebaseAuthPluginException.parserExceptionToFlutter( + task.getException())); + } + }); + } + @Override public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); diff --git a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java index 863cba307451..f9666b78087a 100644 --- a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java +++ b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java @@ -2642,6 +2642,8 @@ void revokeTokenWithAuthorizationCode( @NonNull String authorizationCode, @NonNull VoidResult result); + void initializeRecaptchaConfig(@NonNull AuthPigeonFirebaseApp app, @NonNull VoidResult result); + /** The codec used by FirebaseAuthHostApi. */ static @NonNull MessageCodec getCodec() { return FirebaseAuthHostApiCodec.INSTANCE; @@ -3395,6 +3397,38 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.firebase_auth_platform_interface.FirebaseAuthHostApi.initializeRecaptchaConfig" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + AuthPigeonFirebaseApp appArg = (AuthPigeonFirebaseApp) args.get(0); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.initializeRecaptchaConfig(appArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m index 4dea617fa088..0f42c89c1f61 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m @@ -2172,4 +2172,22 @@ - (void)verifyBeforeUpdateEmailApp:(nonnull AuthPigeonFirebaseApp *)app }]; } +- (void)initializeRecaptchaConfigApp:(AuthPigeonFirebaseApp *)app + completion:(void (^)(FlutterError *_Nullable))completion { +#if TARGET_OS_OSX + NSLog(@"initializeRecaptchaConfigWithCompletion is not supported on the " + @"MacOS platform."); + completion(nil); +#else + FIRAuth *auth = [self getFIRAuthFromAppNameFromPigeon:app]; + [auth initializeRecaptchaConfigWithCompletion:^(NSError *_Nullable error) { + if (error != nil) { + completion([FLTFirebaseAuthPlugin convertToFlutterError:error]); + } else { + completion(nil); + } + }]; +#endif +} + @end diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m index c6a35e8e70d8..8a32f1224ae6 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m @@ -1553,6 +1553,32 @@ void SetUpFirebaseAuthHostApiWithSuffix(id binaryMesseng [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.firebase_auth_platform_interface." + @"FirebaseAuthHostApi.initializeRecaptchaConfig", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FirebaseAuthHostApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(initializeRecaptchaConfigApp:completion:)], + @"FirebaseAuthHostApi api (%@) doesn't respond to " + @"@selector(initializeRecaptchaConfigApp:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0); + [api initializeRecaptchaConfigApp:arg_app + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } } @interface FirebaseAuthUserHostApiCodecReader : FlutterStandardReader @end diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h index 89c67b26fb87..fe48f267f2af 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h @@ -385,6 +385,8 @@ NSObject *FirebaseAuthHostApiGetCodec(void); - (void)revokeTokenWithAuthorizationCodeApp:(AuthPigeonFirebaseApp *)app authorizationCode:(NSString *)authorizationCode completion:(void (^)(FlutterError *_Nullable))completion; +- (void)initializeRecaptchaConfigApp:(AuthPigeonFirebaseApp *)app + completion:(void (^)(FlutterError *_Nullable))completion; @end extern void SetUpFirebaseAuthHostApi(id binaryMessenger, diff --git a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart index 5c2867a75508..c0f72b4b27b2 100644 --- a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart @@ -819,6 +819,12 @@ class FirebaseAuth extends FirebasePluginPlatform { return _delegate.revokeTokenWithAuthorizationCode(authorizationCode); } + /// Initializes the reCAPTCHA Enterprise client proactively to enhance reCAPTCHA signal collection and + /// to complete reCAPTCHA-protected flows in a single attempt. + Future initializeRecaptchaConfig() { + return _delegate.initializeRecaptchaConfig(); + } + @override String toString() { return 'FirebaseAuth(app: ${app.name})'; diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart index 80c8f4de625c..3b5e945af3e6 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart @@ -667,6 +667,15 @@ class MethodChannelFirebaseAuth extends FirebaseAuthPlatform { ); } } + + @override + Future initializeRecaptchaConfig() async { + try { + await _api.initializeRecaptchaConfig(pigeonDefault); + } catch (e, stack) { + convertPlatformException(e, stack); + } + } } /// Simple helper class to make nullable values transferable through StreamControllers. diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart index 354506fba2a9..e66a71b7bc5a 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -1478,6 +1478,30 @@ class FirebaseAuthHostApi { return; } } + + Future initializeRecaptchaConfig(AuthPigeonFirebaseApp app) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.firebase_auth_platform_interface.FirebaseAuthHostApi.initializeRecaptchaConfig$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([app]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } } class _FirebaseAuthUserHostApiCodec extends StandardMessageCodec { diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart index bbf901e090ab..9a4fcb1c0308 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart @@ -706,4 +706,10 @@ abstract class FirebaseAuthPlatform extends PlatformInterface { throw UnimplementedError( 'revokeTokenWithAuthorizationCode() is not implemented'); } + + /// Initializes the reCAPTCHA Enterprise client proactively to enhance reCAPTCHA signal collection and + /// to complete reCAPTCHA-protected flows in a single attempt. + Future initializeRecaptchaConfig() { + throw UnimplementedError('initializeRecaptchaConfig() is not implemented'); + } } diff --git a/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart b/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart index 6165d4e75705..a923a5481bd8 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart @@ -419,6 +419,11 @@ abstract class FirebaseAuthHostApi { AuthPigeonFirebaseApp app, String authorizationCode, ); + + @async + void initializeRecaptchaConfig( + AuthPigeonFirebaseApp app, + ); } class PigeonIdTokenResult { diff --git a/packages/firebase_auth/firebase_auth_platform_interface/test/pigeon/test_api.dart b/packages/firebase_auth/firebase_auth_platform_interface/test/pigeon/test_api.dart index 03f1821876fe..91d5f6ef9641 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/test/pigeon/test_api.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/test/pigeon/test_api.dart @@ -187,6 +187,8 @@ abstract class TestFirebaseAuthHostApi { Future revokeTokenWithAuthorizationCode( AuthPigeonFirebaseApp app, String authorizationCode); + Future initializeRecaptchaConfig(AuthPigeonFirebaseApp app); + static void setUp( TestFirebaseAuthHostApi? api, { BinaryMessenger? binaryMessenger, @@ -993,6 +995,38 @@ abstract class TestFirebaseAuthHostApi { }); } } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_auth_platform_interface.FirebaseAuthHostApi.initializeRecaptchaConfig$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_auth_platform_interface.FirebaseAuthHostApi.initializeRecaptchaConfig was null.'); + final List args = (message as List?)!; + final AuthPigeonFirebaseApp? arg_app = + (args[0] as AuthPigeonFirebaseApp?); + assert(arg_app != null, + 'Argument for dev.flutter.pigeon.firebase_auth_platform_interface.FirebaseAuthHostApi.initializeRecaptchaConfig was null, expected non-null AuthPigeonFirebaseApp.'); + try { + await api.initializeRecaptchaConfig(arg_app!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } } diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index ec759f5d17ff..4e0572839d3d 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -633,6 +633,13 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { 'revokeTokenWithAuthorizationCode() is only available on apple platforms.', ); } + + @override + Future initializeRecaptchaConfig() async { + await guardAuthExceptions( + () => delegate.initializeRecaptchaConfig(), + ); + } } String getOriginName(String appName) { diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart index 4f21787f428a..6dce1cab9eee 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart @@ -804,6 +804,11 @@ class Auth extends JsObjectWrapper { .verifyPasswordResetCode(jsObject, code.toJS) .toDart .then((value) => (value! as JSString).toDart); + + /// Initializes the reCAPTCHA Enterprise client proactively to enhance reCAPTCHA signal collection and + /// to complete reCAPTCHA-protected flows in a single attempt. + Future initializeRecaptchaConfig() => + auth_interop.initializeRecaptchaConfig(jsObject).toDart; } /// Represents an auth provider. diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart index 2e0397f17313..0b9b0faef56b 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart @@ -984,3 +984,6 @@ class PhoneAuthCredentialJsImpl extends AuthCredential { extension PhoneAuthCredentialJsImplExtension on PhoneAuthCredentialJsImpl { external JSObject toJSON(); } + +@JS() +external JSPromise initializeRecaptchaConfig(AuthJsImpl auth); diff --git a/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart b/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart index 7ad2cbabb004..feef347f78e3 100644 --- a/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart +++ b/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart @@ -1045,6 +1045,23 @@ void main() { }, skip: true, ); + + group( + 'initializeRecaptchaConfig', + () { + test('initializeRecaptchaConfig completes without throwing', + () async { + // Skipping this test as initializeRecaptchaConfig is not supported + // by the Firebase emulator suite. + try { + await FirebaseAuth.instance.initializeRecaptchaConfig(); + } catch (e) { + fail('Should not have thrown: $e'); + } + }); + }, + skip: true, + ); }, // macOS skipped because it needs keychain sharing entitlement. See: https://github.com/firebase/flutterfire/issues/9538 skip: defaultTargetPlatform == TargetPlatform.macOS,