From a1f471029352b7597d7e83a8c1ea06145768cf89 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 22 Dec 2023 00:07:19 -0500 Subject: [PATCH] feat(auth, authDomain): implement FirebaseOptions.authDomain on Auth JS SDK has FirebaseOptions.authDomain, and while the native SDKs do not directly have this FirebaseOptions key, they do have Auth.customAuthDomain - so take the key if it is set during initializeApp, store it, then as Auth instances are initialized, set it in if it exists --- jest.setup.ts | 1 + .../app/ReactNativeFirebaseAppModule.java | 14 ++++++ .../firebase/common/RCTConvertFirebase.java | 5 ++ packages/app/ios/RNFBApp/RNFBAppModule.h | 2 + packages/app/ios/RNFBApp/RNFBAppModule.m | 47 ++++++++++++++++++- packages/app/ios/RNFBApp/RNFBSharedUtils.m | 5 ++ .../auth/ReactNativeFirebaseAuthModule.java | 21 +++++++++ packages/auth/e2e/auth.e2e.js | 17 +++++++ packages/auth/ios/RNFBAuth/RNFBAuthModule.m | 16 +++++++ packages/auth/lib/index.js | 11 +++++ packages/auth/lib/modular/index.js | 5 ++ 11 files changed, 143 insertions(+), 1 deletion(-) diff --git a/jest.setup.ts b/jest.setup.ts index 6f2688d906..155bdbf347 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -46,6 +46,7 @@ jest.doMock('react-native', () => { addIdTokenListener: jest.fn(), setTenantId: jest.fn(), useEmulator: jest.fn(), + configureAuthDomain: jest.fn(), }, RNFBCrashlyticsModule: {}, RNFBDatabaseModule: { diff --git a/packages/app/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppModule.java b/packages/app/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppModule.java index 301bea1360..bc9918f8dc 100644 --- a/packages/app/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppModule.java +++ b/packages/app/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppModule.java @@ -17,6 +17,7 @@ * */ +import android.util.Log; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMethod; @@ -38,6 +39,8 @@ public class ReactNativeFirebaseAppModule extends ReactNativeFirebaseModule { private static final String TAG = "App"; + public static Map authDomains = new HashMap<>(); + ReactNativeFirebaseAppModule(ReactApplicationContext reactContext) { super(reactContext, TAG); } @@ -52,11 +55,22 @@ public void initialize() { public void initializeApp(ReadableMap options, ReadableMap appConfig, Promise promise) { FirebaseApp firebaseApp = RCTConvertFirebase.readableMapToFirebaseApp(options, appConfig, getContext()); + ReactNativeFirebaseAppModule.configureAuthDomain( + appConfig.getString("name"), options.getString("authDomain")); WritableMap firebaseAppMap = RCTConvertFirebase.firebaseAppToWritableMap(firebaseApp); promise.resolve(firebaseAppMap); } + public static void configureAuthDomain(String name, String authDomain) { + if (authDomain != null) { + Log.d(TAG, name + " custom authDomain " + authDomain); + authDomains.put(name, authDomain); + } else { + authDomains.remove(name); + } + } + @ReactMethod public void setAutomaticDataCollectionEnabled(String appName, Boolean enabled) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); diff --git a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java index 9ad4f0877d..8914bff2af 100644 --- a/packages/app/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +++ b/packages/app/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java @@ -25,6 +25,7 @@ import com.facebook.react.bridge.WritableMap; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import io.invertase.firebase.app.ReactNativeFirebaseAppModule; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,6 +57,10 @@ public static Map firebaseAppToMap(FirebaseApp firebaseApp) { options.put("messagingSenderId", appOptions.getGcmSenderId()); options.put("storageBucket", appOptions.getStorageBucket()); + if (ReactNativeFirebaseAppModule.authDomains.get(name) != null) { + options.put("authDomain", ReactNativeFirebaseAppModule.authDomains.get(name)); + } + root.put("options", options); root.put("appConfig", appConfig); diff --git a/packages/app/ios/RNFBApp/RNFBAppModule.h b/packages/app/ios/RNFBApp/RNFBAppModule.h index 6aba0f8299..894c019d73 100644 --- a/packages/app/ios/RNFBApp/RNFBAppModule.h +++ b/packages/app/ios/RNFBApp/RNFBAppModule.h @@ -21,6 +21,8 @@ @interface RNFBAppModule : NSObject ++ (NSString *)getCustomDomain:(NSString *)appName; + - (void)setLogLevel:(NSString *)logLevel; @end diff --git a/packages/app/ios/RNFBApp/RNFBAppModule.m b/packages/app/ios/RNFBApp/RNFBAppModule.m index cc41936e6d..4d2b1d67f9 100644 --- a/packages/app/ios/RNFBApp/RNFBAppModule.m +++ b/packages/app/ios/RNFBApp/RNFBAppModule.m @@ -168,7 +168,7 @@ - (void)invalidate { #pragma mark Firebase App Methods RCT_EXPORT_METHOD(initializeApp - : (FIROptions *)firOptions appConfig + : (NSDictionary *)options appConfig : (NSDictionary *)appConfig resolver : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { @@ -176,6 +176,44 @@ - (void)invalidate { FIRApp *firApp; NSString *appName = [appConfig valueForKey:@"name"]; + NSString *appId = [options valueForKey:@"appId"]; + NSString *messagingSenderId = [options valueForKey:@"messagingSenderId"]; + FIROptions *firOptions = [[FIROptions alloc] initWithGoogleAppID:appId + GCMSenderID:messagingSenderId]; + firOptions.APIKey = [options valueForKey:@"apiKey"]; + firOptions.projectID = [options valueForKey:@"projectId"]; + // kFirebaseOptionsDatabaseUrl + if (![[options valueForKey:@"databaseURL"] isEqual:[NSNull null]]) { + firOptions.databaseURL = [options valueForKey:@"databaseURL"]; + } + // kFirebaseOptionsStorageBucket + if (![[options valueForKey:@"storageBucket"] isEqual:[NSNull null]]) { + firOptions.storageBucket = [options valueForKey:@"storageBucket"]; + } + // kFirebaseOptionsDeepLinkURLScheme + if (![[options valueForKey:@"deepLinkURLScheme"] isEqual:[NSNull null]]) { + firOptions.deepLinkURLScheme = [options valueForKey:@"deepLinkURLScheme"]; + } + // kFirebaseOptionsIosBundleId + if (![[options valueForKey:@"iosBundleId"] isEqual:[NSNull null]]) { + firOptions.bundleID = [options valueForKey:@"iosBundleId"]; + } + // kFirebaseOptionsIosClientId + if (![[options valueForKey:@"iosClientId"] isEqual:[NSNull null]]) { + firOptions.clientID = [options valueForKey:@"iosClientId"]; + } + // kFirebaseOptionsAppGroupId + if (![[options valueForKey:@"appGroupId"] isEqual:[NSNull null]]) { + firOptions.appGroupID = [options valueForKey:@"appGroupId"]; + } + + if ([options valueForKey:@"authDomain"] != nil) { + DLog(@"RNFBAuth app: %@ customAuthDomain: %@", appName, [options valueForKey:@"authDomain"]); + if (customAuthDomains == nil) { + customAuthDomains = [[NSMutableDictionary alloc] init]; + } + customAuthDomains[appName] = [options valueForKey:@"authDomain"]; + } @try { if (!appName || [appName isEqualToString:DEFAULT_APP_DISPLAY_NAME]) { [FIRApp configureWithOptions:firOptions]; @@ -195,6 +233,13 @@ - (void)invalidate { }); } +static NSMutableDictionary *customAuthDomains; + ++ (NSString *)getCustomDomain:(NSString *)appName { + DLog(@"authDomains: %@", customAuthDomains); + return customAuthDomains[appName]; +} + RCT_EXPORT_METHOD(setLogLevel : (NSString *)logLevel) { int level = FIRLoggerLevelError; if ([logLevel isEqualToString:@"verbose"]) { diff --git a/packages/app/ios/RNFBApp/RNFBSharedUtils.m b/packages/app/ios/RNFBApp/RNFBSharedUtils.m index 92e404b375..7bf8246e60 100644 --- a/packages/app/ios/RNFBApp/RNFBSharedUtils.m +++ b/packages/app/ios/RNFBApp/RNFBSharedUtils.m @@ -16,6 +16,7 @@ */ #import "RNFBSharedUtils.h" +#import "RNFBAppModule.h" #import "RNFBJSON.h" #import "RNFBMeta.h" #import "RNFBPreferences.h" @@ -65,6 +66,10 @@ + (NSDictionary *)firAppToDictionary:(FIRApp *)firApp { firAppOptions[@"clientId"] = firOptions.clientID; firAppOptions[@"androidClientID"] = firOptions.androidClientID; firAppOptions[@"deepLinkUrlScheme"] = firOptions.deepLinkURLScheme; + // not in FIROptions API but in JS SDK and project config JSON + if ([RNFBAppModule getCustomDomain:name] != nil) { + firAppOptions[@"authDomain"] = [RNFBAppModule getCustomDomain:name]; + } firAppDictionary[@"options"] = firAppOptions; firAppDictionary[@"appConfig"] = firAppConfig; diff --git a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java index 689a6b5e0d..4249379022 100644 --- a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +++ b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java @@ -68,6 +68,7 @@ import com.google.firebase.auth.TwitterAuthProvider; import com.google.firebase.auth.UserInfo; import com.google.firebase.auth.UserProfileChangeRequest; +import io.invertase.firebase.app.ReactNativeFirebaseAppModule; import io.invertase.firebase.common.ReactNativeFirebaseEvent; import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter; import io.invertase.firebase.common.ReactNativeFirebaseModule; @@ -150,6 +151,26 @@ public void onCatalystInstanceDestroy() { mMultiFactorSessions.clear(); } + @ReactMethod + public void configureAuthDomain(final String appName) { + Log.d(TAG, "configureAuthDomain"); + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + String authDomain = ReactNativeFirebaseAppModule.authDomains.get(appName); + Log.d(TAG, "configureAuthDomain - app " + appName + " domain? " + authDomain); + if (authDomain != null) { + firebaseAuth.setCustomAuthDomain(authDomain); + } + } + + @ReactMethod + public void getCustomAuthDomain(final String appName, final Promise promise) { + Log.d(TAG, "configureAuthDomain"); + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + promise.resolve(firebaseAuth.getCustomAuthDomain()); + } + /** Add a new auth state listener - if one doesn't exist already */ @ReactMethod public void addAuthStateListener(final String appName) { diff --git a/packages/auth/e2e/auth.e2e.js b/packages/auth/e2e/auth.e2e.js index 45e7accdad..6d17a4949b 100644 --- a/packages/auth/e2e/auth.e2e.js +++ b/packages/auth/e2e/auth.e2e.js @@ -1139,6 +1139,23 @@ describe('auth() modular', function () { secondaryApp.auth().app.name.should.equal('secondaryFromNative'); }); + + it('supports an app initialized with custom authDomain', async function () { + const { getAuth, getCustomAuthDomain } = authModular; + const { initializeApp } = modular; + + const name = `testscoreapp${FirebaseHelpers.id}`; + const platformAppConfig = FirebaseHelpers.app.config(); + platformAppConfig.authDomain = 'example.com'; + const newApp = await initializeApp(platformAppConfig, name); + const secondaryApp = firebase.app(name); + const secondaryAuth = getAuth(secondaryApp); + secondaryAuth.app.name.should.equal(name); + secondaryApp.auth().app.name.should.equal(name); + const customAuthDomain = await getCustomAuthDomain(secondaryAuth); + customAuthDomain.should.equal(platformAppConfig.authDomain); + return newApp.delete(); + }); }); describe('applyActionCode()', function () { // Needs a different setup to work against the auth emulator diff --git a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m index 2bc27deab5..65e2f0b489 100644 --- a/packages/auth/ios/RNFBAuth/RNFBAuthModule.m +++ b/packages/auth/ios/RNFBAuth/RNFBAuthModule.m @@ -19,6 +19,7 @@ #import #import "RNFBApp/RCTConvert+FIRApp.h" +#import "RNFBApp/RNFBAppModule.h" #import "RNFBApp/RNFBSharedUtils.h" #import "RNFBAuthModule.h" @@ -159,6 +160,21 @@ - (void)invalidate { } } +RCT_EXPORT_METHOD(configureAuthDomain : (FIRApp *)firebaseApp) { + NSString *authDomain = [RNFBAppModule getCustomDomain:firebaseApp.name]; + DLog(@"RNFBAuth app: %@ customAuthDomain: %@", firebaseApp.name, authDomain); + if (authDomain != nil) { + [FIRAuth authWithApp:firebaseApp].customAuthDomain = authDomain; + } +} + +RCT_EXPORT_METHOD(getCustomAuthDomain + : (FIRApp *)firebaseApp + : (RCTPromiseResolveBlock)resolve + : (RCTPromiseRejectBlock)reject) { + resolve([FIRAuth authWithApp:firebaseApp].customAuthDomain); +} + RCT_EXPORT_METHOD(setAppVerificationDisabledForTesting : (FIRApp *)firebaseApp : (BOOL)disabled) { [FIRAuth authWithApp:firebaseApp].settings.appVerificationDisabledForTesting = disabled; } diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index e1ecb8c4a1..9c8c1e511b 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -56,6 +56,7 @@ export { fetchSignInMethodsForEmail, getAdditionalUserInfo, getAuth, + getCustomAuthDomain, getIdToken, getIdTokenResult, getMultiFactorResolver, @@ -173,6 +174,12 @@ class FirebaseAuthModule extends FirebaseModule { this.native.addAuthStateListener(); this.native.addIdTokenListener(); + + // custom authDomain in only available from App's FirebaseOptions, + // but we need it in Auth if it exists. During app configuration we store + // mappings from app name to authDomain, this auth constructor + // is a reasonable time to use the mapping and set it into auth natively + this.native.configureAuthDomain(); } get languageCode() { @@ -500,6 +507,10 @@ class FirebaseAuthModule extends FirebaseModule { } return new MultiFactorUser(this, user); } + + getCustomAuthDomain() { + return this.native.getCustomAuthDomain(); + } } // import { SDK_VERSION } from '@react-native-firebase/auth'; diff --git a/packages/auth/lib/modular/index.js b/packages/auth/lib/modular/index.js index 254c0e2664..a9b43bfec4 100644 --- a/packages/auth/lib/modular/index.js +++ b/packages/auth/lib/modular/index.js @@ -467,3 +467,8 @@ export async function verifyBeforeUpdateEmail(user, newEmail, actionCodeSettings export function getAdditionalUserInfo(userCredential) { return userCredential.additionalUserInfo; } + +export function getCustomAuthDomain(auth) { + const _auth = _getUnderlyingAuth(auth); + return _auth.getCustomAuthDomain(); +}