invertase / react-native-firebase

🔥 A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.62k stars 2.2k forks source link

🔥 [🐛] auth().createUserWithEmailAndPassword(email, password) creates multiple accounts on firebase authentification module #7854

Open Arashadmin opened 2 months ago

Arashadmin commented 2 months ago

Issue

Describe your issue here

Summary: Our newly released Android app version creates multiple accounts on firebase authentication module when user signs up into our app via auth().createUserWithEmailAndPassword(email, password).

With our last Android release we've had some dependency updates shipped to production. Alongside other dependencies we also updated all firebase dependencies from 18.5.0 to 19.2.2.

Screenshot 2024-06-21 at 10 41 01 In our above shown image you can see, that we do post 4 times into our slack channel, that a specific person signed up into our app. (This is a automatic function, that gets triggered, whenever a new user is created).

Looking into the Firebase Authentication module, the same person has got for accounts with the same email address but with different User UIDs: Screenshot 2024-06-21 at 10 43 10

  1. As already said, we updated from 18.5.0 (that was running stable) to 19.2.2.
  2. We did NOT change our codebase regarding our firebase authentication
  3. We know, that there is a new minor version update of firebase/auth 19.3.0 but there are (according to your changelog) no bugfixes that relate to our issue
  4. This must be something internal on your side guys ;)
  5. Very important to know: we never had any issues like this in this authentication regard. This worked always perfectly fine.

Project Files

Javascript

Click To Expand

This is our password screen component, where the createUserWithEmailAndPassword() method gets triggered. (name and email are collected in the previous screens). For you it is only important to know, that the user presses the CircularButton and invokes the "handleSignup" function, which invokes this wrapper method: await auth.createAuthUser(email, password, name): SignUpPassword.tsx: ```tsx import AsyncStorage from '@react-native-async-storage/async-storage' import React, {useEffect, useState} from 'react' import {Text, useWindowDimensions, View} from 'react-native' import CustomKeyboardAvoidingView from '../components/CustomKeboardAvoidingView' import * as auth from '../services/firebase/auth' import { CircularButton, Copy, NewScreenContainer, Spacer, TextInput, } from '../ui' const SignUpPassword = () => { const [name, setName] = useState('') const [email, setEmail] = useState('') const [error, setError] = useState() const [password, setPassword] = useState('') const [isSigningUp, setIsSigningUp] = useState(false) useEffect(() => { const getName = async () => { const userName = await AsyncStorage.getItem('name') if (!userName) { throw new Error( 'User name needs to be in Async Storage to go continue the sign up flow' ) } setName(userName) } const getEmail = async () => { const userEmail = await AsyncStorage.getItem('email') if (!userEmail) { throw new Error( 'User email needs to be in Async Storage to go continue the sign up flow' ) } setEmail(userEmail) } getName() getEmail() }, []) const handleSignup = async () => { if (!email || !password) return setIsSigningUp(true) try { await auth.createAuthUser(email, password, name) AsyncStorage.removeItem('email') AsyncStorage.removeItem('name') } catch (error) { setIsSigningUp(false) setError((error as Error).message) } } // fontScale is made for users who have to magnify the screen a lot const fontScale = useWindowDimensions().fontScale return ( 1.7 : false} > Let's make it all yours! Please set a password {error && ( {error} )} ) } export default SignUpPassword ``` These are the main functions in our auth.ts module which invoke the respective createUserWithEmailAndPassword method. (createAuthUser): ```ts import auth, {FirebaseAuthTypes} from '@react-native-firebase/auth' import {handleErrorSilently} from '../../utils/errorUtils' import * as analytics from '../analytics' export type AuthUser = FirebaseAuthTypes.User // Copied from Firebase source because it's not exported :-( // eslint-disable-next-line @typescript-eslint/no-explicit-any type CallbackOrObserver any> = T | {next: T} type AuthListenerCallback = FirebaseAuthTypes.AuthListenerCallback const translateAuthError = ( error: FirebaseAuthTypes.NativeFirebaseAuthError ) => { switch (error.code) { case 'auth/invalid-email': return 'Email address is not valid.' case 'auth/user-disabled': return 'User corresponding to the given email has been disabled.' case 'auth/user-not-found': return 'No user corresponding to the given email.' case 'auth/wrong-password': return 'Password is invalid for the given email, or account does not have set password.' case 'auth/weak-password': return 'Password is invalid. (Tip: Make sure you have at least 6 characters)' case 'auth/email-already-in-use': return 'Something went wrong. Please try again or contact the Audrey team.' case 'auth/network-request-failed': return "Couldn't reach the server. Is your phone connected to the internet?" default: { handleErrorSilently(error) return 'Unexpected error, please try again' } } } const rethrowAsErrorMessage = ( error: FirebaseAuthTypes.NativeFirebaseAuthError ) => { throw new Error(translateAuthError(error)) } const ifEmailExists = async (email: string): Promise => { const availableAuthMethods = await auth() .fetchSignInMethodsForEmail(email) .catch(rethrowAsErrorMessage) return availableAuthMethods.length > 0 } export const createAuthUser = async ( email: string, password: string, displayName: string ) => { const isEmailExists = await ifEmailExists(email) if (isEmailExists) throw new Error( 'Something went wrong. Please try again or contact the Audrey team.' ) const cred = await auth() .createUserWithEmailAndPassword(email, password) .catch(rethrowAsErrorMessage) await cred.user.updateProfile({displayName}) analytics.setUserName(displayName) } export const sendPasswordResetEmail = (email: string) => auth().sendPasswordResetEmail(email).catch(rethrowAsErrorMessage) export const signInWithEmailAndPassword = (email: string, password: string) => auth() .signInWithEmailAndPassword(email, password) .catch(rethrowAsErrorMessage) ``` In the "createAuthUser" function you can see, that the createUserWithEmailAndPassword method is invoked correctly and that nothing else happens alongside it (after the user account is created we just update the name and send the name also the the analytics package but that is something, that has no effect on this). #### `package.json`: ```json { "name": "@audrey", "main": "__generated__/AppEntry.js", "version": "0.42.0", "scripts": { "start": "npx expo start --dev-client", "clean": "npx expo prebuild --clean && rm -rf ios && rm -rf android", "lint": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx --cache .", "test": "jest", "start-fb-emulator": "firebase emulators:start --only firestore,auth", "postinstall": "expo-yarn-workspaces postinstall", "release-manager": "cd ../tools/release-manager && yarn start", "android": "npx expo run:android", "ios": "npx expo run:ios" }, "lint-staged": { "*.{js,ts,tsx}": [ "eslint" ] }, "jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@sentry/.*|mixpanel-react-native|nanoid)" ], "setupFiles": [ "./jestSetup.js" ] }, "dependencies": { "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@contentful/rich-text-plain-text-renderer": "16.0.13", "@expo-google-fonts/ibm-plex-sans": "0.2.3", "@expo-google-fonts/ibm-plex-serif": "0.2.3", "@gorhom/bottom-sheet": "4.6.3", "@gorhom/portal": "1.0.14", "@notionhq/client": "2.2.15", "@portabletext/react-native": "1.1.10", "@portabletext/types": "2.0.13", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/masked-view": "0.1.11", "@react-native-community/slider": "4.5.2", "@react-native-firebase/analytics": "19.2.2", "@react-native-firebase/app": "19.2.2", "@react-native-firebase/app-check": "^19.2.2", "@react-native-firebase/auth": "19.2.2", "@react-native-firebase/dynamic-links": "19.2.2", "@react-native-firebase/firestore": "19.2.2", "@react-native-firebase/messaging": "19.2.2", "@react-navigation/bottom-tabs": "6.5.20", "@react-navigation/native": "6.1.17", "@react-navigation/stack": "6.3.29", "@segment/analytics-react-native": "^2.19.1", "@segment/sovran-react-native": "^1.1.1", "@sentry/react-native": "5.22.2", "@types/lodash": "4.17.0", "babel-plugin-module-resolver": "5.0.2", "dotenv": "16.4.5", "expo": "51.0.11", "expo-application": "5.9.1", "expo-build-properties": "0.12.1", "expo-constants": "16.0.1", "expo-dev-client": "4.0.14", "expo-device": "6.0.2", "expo-font": "12.0.5", "expo-linear-gradient": "13.0.2", "expo-linking": "6.3.1", "expo-localization": "15.0.3", "expo-network": "6.0.1", "expo-notifications": "~0.28.3", "expo-splash-screen": "~0.27.4", "expo-status-bar": "1.12.1", "expo-tracking-transparency": "~4.0.2", "expo-updates": "0.25.14", "immer": "9.0.21", "jest": "29.7.0", "jest-expo": "51.0.2", "lodash-es": "4.17.21", "md5": "2.3.0", "mixpanel-react-native": "2.4.1", "nanoid": "5.0.7", "picosanity": "4.1.1", "promise": "^8.3.0", "react": "18.3.1", "react-devtools-core": "5.2.0", "react-dom": "18.3.1", "react-native": "0.74.2", "react-native-gesture-handler": "2.16.2", "react-native-get-random-values": "~1.11.0", "react-native-purchases": "7.27.1", "react-native-reanimated": "3.10.1", "react-native-safe-area-context": "4.10.1", "react-native-screens": "3.31.1", "react-native-svg": "15.2.0", "react-native-svg-transformer": "1.3.0", "react-native-track-player": "4.1.1", "react-native-web": "0.19.10", "react-native-webview": "13.10.2", "react-native-youtube-iframe": "2.3.0", "semver": "7.6.0", "styled-components": "5.3.6", "zustand": "4.5.2" }, "devDependencies": { "@babel/core": "7.24.4", "@types/jest": "29.5.12", "@types/md5": "2.3.5", "@types/node-fetch": "2.6.11", "@types/styled-components": "5.1.34", "@types/styled-components-react-native": "5.2.5", "@typescript-eslint/eslint-plugin": "7.7.1", "@typescript-eslint/parser": "7.7.1", "eslint": "8.57.0", "eslint-plugin-jest": "28.5.0", "eslint-plugin-react": "7.34.1", "eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-react-native": "4.1.0", "eslint-plugin-simple-import-sort": "12.1.0", "expo-yarn-workspaces": "2.3.2", "firebase-tools": "13.10.1", "jest-file-snapshot": "0.5.0", "lint-staged": "15.2.2", "typescript": "5.3.3" }, "author": "Audrey", "private": true, "license": "UNLICENSED" } ``` #### `firebase.json` for react-native-firebase v6: ```json { "functions": { "source": "functions", "predeploy": [ "npm --prefix \"$RESOURCE_DIR\" run lint", "npm --prefix \"$RESOURCE_DIR\" run build" ] }, "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, "emulators": { "auth": { "port": 9099, "host": "0.0.0.0" }, "functions": { "port": 5001, "host": "0.0.0.0" }, "firestore": { "port": 8080, "host": "0.0.0.0" }, "ui": { "enabled": true } }, "react-native": { "analytics_auto_collection_enabled": false, "messaging_android_notification_color": "@color/black" } } ```

iOS

Click To Expand

#### `ios/Podfile`: - [ ] I'm not using Pods - [x] I'm using Pods and my Podfile looks like: ```ruby # N/A ``` #### `AppDelegate.m`: ```objc // N/A ```


Android

Click To Expand

#### Have you converted to AndroidX? - [ ] my application is an AndroidX application? - [ ] I am using `android/gradle.settings` `jetifier=true` for Android compatibility? - [ ] I am using the NPM package `jetifier` for react-native compatibility? #### `android/build.gradle`: ```groovy // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { buildToolsVersion = findProperty('android.buildToolsVersion') ?: '34.0.0' minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '23') compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '34') targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34') kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.23' ndkVersion = "26.1.10909125" } repositories { google() mavenCentral() } dependencies { classpath 'com.google.gms:google-services:4.3.3' classpath('com.android.tools.build:gradle') classpath('com.facebook.react:react-native-gradle-plugin') classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') } } apply plugin: "com.facebook.react.rootproject" allprojects { repositories { maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) } maven { // Android JSC is installed from npm url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')) } google() mavenCentral() maven { url 'https://www.jitpack.io' } } } ``` #### `android/app/build.gradle`: ```groovy // N/A ``` #### `android/settings.gradle`: ```groovy apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() /** * This is the configuration block to customize your React Native Android app. * By default you don't need to apply any configuration, just uncomment the lines you need. */ react { entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() // Use Expo CLI to bundle the app, this ensures the Metro config // works correctly with Expo projects. cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) bundleCommand = "export:embed" /* Folders */ // The root of your project, i.e. where "package.json" lives. Default is '..' // root = file("../") // The folder where the react-native NPM package is. Default is ../node_modules/react-native // reactNativeDir = file("../node_modules/react-native") // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen // codegenDir = file("../node_modules/@react-native/codegen") /* Variants */ // The list of variants to that are debuggable. For those we're going to // skip the bundling of the JS bundle and the assets. By default is just 'debug'. // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. // debuggableVariants = ["liteDebug", "prodDebug"] /* Bundling */ // A list containing the node command and its flags. Default is just 'node'. // nodeExecutableAndArgs = ["node"] // // The path to the CLI configuration file. Default is empty. // bundleConfig = file(../rn-cli.config.js) // // The name of the generated asset file containing your JS bundle // bundleAssetName = "MyApplication.android.bundle" // // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' // entryFile = file("../js/MyApplication.android.js") // // A list of extra flags to pass to the 'bundle' commands. // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle // extraPackagerArgs = [] /* Hermes Commands */ // The hermes compiler command to run. By default it is 'hermesc' // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] } /** * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() /** * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle") android { ndkVersion rootProject.ext.ndkVersion buildToolsVersion rootProject.ext.buildToolsVersion compileSdk rootProject.ext.compileSdkVersion namespace 'com.audrey.audrey.dev' defaultConfig { applicationId 'com.audrey.audrey.dev' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 4200 versionName "0.42.0" } signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes { debug { signingConfig signingConfigs.debug } release { // Caution! In production, you need to generate your own keystore file. // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } packagingOptions { jniLibs { useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) } } } // Apply static values from `gradle.properties` to the `android.packagingOptions` // Accepts values in comma delimited lists, example: // android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini ["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> // Split option: 'foo,bar' -> ['foo', 'bar'] def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); // Trim all elements in place. for (i in 0.. 0) { println "android.packagingOptions.$prop += $options ($options.length)" // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' options.each { android.packagingOptions[prop] += it } } } dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; if (isGifEnabled) { // For animated gif support implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") } if (isWebpEnabled) { // For webp support implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") if (isWebpAnimatedEnabled) { // Animated webp support implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") } } if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } } apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); applyNativeModulesAppBuildGradle(project) apply plugin: 'com.google.gms.google-services' ``` #### `MainApplication.kt`: ```kotlin package com.audrey.audrey.dev import android.app.Application import android.content.res.Configuration import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.ReactHost import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.soloader.SoLoader import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ReactNativeHostWrapper class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( this, object : DefaultReactNativeHost(this) { override fun getPackages(): List { // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); return PackageList(this).packages } override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED } ) override val reactHost: ReactHost get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) override fun onCreate() { super.onCreate() SoLoader.init(this, false) if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // If you opted-in for the New Architecture, we load the native entry point for this app. load() } ApplicationLifecycleDispatcher.onApplicationCreate(this) } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) } } ``` #### `AndroidManifest.xml`: ```xml ```


Environment

Click To Expand

**`react-native info` output:** ``` System: OS: macOS 14.5 CPU: (14) arm64 Apple M3 Max Memory: 587.88 MB / 36.00 GB Shell: version: "5.9" path: /bin/zsh Binaries: Node: version: 18.17.1 path: /private/var/folders/nm/fpbywkw56d74gt4309qd68bm0000gn/T/xfs-542320a6/node Yarn: version: 3.2.0 path: /private/var/folders/nm/fpbywkw56d74gt4309qd68bm0000gn/T/xfs-542320a6/yarn npm: version: 9.6.7 path: /usr/local/bin/npm Watchman: Not Found Managers: CocoaPods: version: 1.15.2 path: /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: - DriverKit 23.2 - iOS 17.2 - macOS 14.2 - tvOS 17.2 - visionOS 1.0 - watchOS 10.2 Android SDK: Not Found IDEs: Android Studio: 2023.1 AI-231.9392.1.2311.11255304 Xcode: version: 15.2/15C500b path: /usr/bin/xcodebuild Languages: Java: version: 17.0.10 path: /usr/bin/javac Ruby: version: 3.3.0 path: /opt/homebrew/opt/ruby/bin/ruby npmPackages: "@react-native-community/cli": Not Found react: Not Found react-native: installed: 0.74.2 wanted: 0.74.2 react-native-macos: Not Found npmGlobalPackages: "*react-native*": Not Found Android: hermesEnabled: true newArchEnabled: false iOS: hermesEnabled: Not found newArchEnabled: Not found ``` - **Platform that you're experiencing the issue on**: - [] iOS - [x] Android - [ ] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - `19.2.2` - **`Firebase` module(s) you're using that has the issue:** - `e.g. Instance ID` - **Are you using `TypeScript`?** - `Y` & `5.3.3`


Arashadmin commented 2 months ago

Hey ho, just for your notice: we moved from react-native-firebase 19.2.2 packages to 19.3.0 and the error still exists.

Salakar commented 2 months ago

👋 There is no way our library can create multiple users like this from a single API call, it's a direct API from JS to Native.

I'd suggest adding a logger in your handleSignup method as it's likely some state updates a triggering this multiple times

Arashadmin commented 1 month ago

Hi Mike,

Thanks for your answer and yes, I'd believe it too, that "there is no way" your library is creating multiple users and because it is a direct API Im a bit helpless to log what is happening behind the scenes.

When you take a look at the code samples that I attached, especially in the handleSignup function, it calls the Promise method "await auth.createAuthUser(email, password, name)" which then is this:

export const createAuthUser = async (
  email: string,
  password: string,
  displayName: string
) => {
  const isEmailExists = await ifEmailExists(email)
  if (isEmailExists) {
    throw new Error(
      'Something went wrong. Please try again or contact the Audrey team.'
    )
  }

  const cred = await auth()
    .createUserWithEmailAndPassword(email, password)
    .catch(rethrowAsErrorMessage)
  await cred.user.updateProfile({displayName})
  analytics.setUserName(displayName)
}

In here you can see, that we first call the createUser method and only if it does not throw an error (when the .catch does not catch anything), than we update the userName with cred.user.updateProfile({displayName}). In other words, if there would any errors be thrown (like email exists error), than our sentry system would catch this error (but doesn't).

So the error still occurs (only on production):

Screenshot 2024-07-10 at 17 35 31

I tried everything to re-create this bug locally with the local firebase instance or on our staging environment but there it works fine.

For now I created a cronJob, that deletes duplicate user accounts, if it is not connected to a firebase document but this should only be temporary solution.

Lyokone commented 1 month ago

Hello @Arashadmin, have you been able to reproduce it reliably? We still cannot reproduce it on our end.

Arashadmin commented 1 month ago

@Lyokone at least not on purpose. It only happens on production without any errors in our logs. But I'm wondering, if you even can build the project, with my provided code and if you go with the bare flow or with expo (as stated in our package.json).

The reason why I'm saying this is, because we have around 6 config plugins for our expo project, to be able to write into our AppDelegate or into our Android Manifest. I'm just wondering, if everything is configured correctly and if this error has anything to do with that since nearly each file is dedicated to a certain module of firebase. But on the other hand you do see our Android Manifest, that is a product of these config plugins, that get merged into the Manifest

russellwheatley commented 3 weeks ago

How is it possible to create users with the same email address? I thought that Firebase Auth blocked creating users with the same email address i.e error code email-already-in-use.

Arashadmin commented 3 weeks ago

locally I can reproduce the "email-already-in-use" error code for 100%. Why this happens on production, I can not tell.

russellwheatley commented 2 weeks ago

It might be this setting in Firebase console that allows the same email address but with different providers:

Screenshot 2024-08-19 at 14 02 34

Note: you cannot create multiple accounts using same provider with same email address. See this google answer: https://support.google.com/firebase/answer/9134820

If you are seeing same email address with same provider, I would open a ticket with Firebase support and ask them to verify because it would be a bug on the Firebase server.

Arashadmin commented 2 weeks ago

Hi Russel,

thanks for your reply. This is exactly what we've checked: image

I'll go and pursue the Firebase support thingy since we don't have any provider taking care of account creation - we only have basicAuth (email+pw) in combination with Firebase on our app.

russellwheatley commented 2 weeks ago

@Arashadmin - as an FYI, Email/Password is considered a provider. You can only have one email address for Email/Password provider per user in Auth.

Arashadmin commented 2 weeks ago

Thx Russel, I thought provider are things like facebook auth etc. :)