[go: nahoru, domu]

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

GetUserByEmail and/or getUsers should return all users[FR] #2430

Open
Flucadetena opened this issue Jan 24, 2024 · 7 comments
Open

GetUserByEmail and/or getUsers should return all users[FR] #2430

Flucadetena opened this issue Jan 24, 2024 · 7 comments

Comments

@Flucadetena
Copy link
Flucadetena commented Jan 24, 2024

Is your feature request related to a problem? Yes
It is very hard to search all the users by email when you allow the user to link multiple providers or if you allow multiple accounts with the same email.

Describe the solution you'd like

  • The function "getUserByEmail" should return the first user that find where the email matches any of the emails found in its providers.
  • The function "getUsers" should return a list of all the users that match that email in any of its providers.

Describe alternatives you've considered
We've been using a property to store the email as a record with the user data in firestore. But if you have any bug in any of your versions that list will break. It even becomes harder when you allow for multiple accounts and multiple provider linking in the same account.

We've consider using auth triggers in functions to keep that list updated, but there are no triggers when a user updates their auth credentials.

Additional context
Also as the feature "fetchSignInForEmail" and others like it are being removed for security purposes this feature is going to become even more useful and needed, and the admin is the perfect place to do it.

Also this is the behavior you get when searching for an email in the firebase auth panel.

Thank you very much for the great work. If I can help in any way I'll be happy to :)
Hope you have a great day.

@google-oss-bot
Copy link

I found a few problems with this issue:

  • I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
  • This issue does not seem to follow the issue template. Make sure you provide all the required information.

@gabaudette
Copy link
gabaudette commented Apr 18, 2024

I have the exact problem right now, the fact that there's no other way than loading all users or alternative shown by op and manually match to an email/provider combinaison is a real pain even more painful when you have (as mentionned by op) "multiple accounts and multiple provider linking in the same account."

@trullock
Copy link

I'm in this mess, how do i "get all users" as the workaround? getUsers requires the uids which i dont have... help!

@Flucadetena
Copy link
Author

Hi @trullock I don't know what you are trying to achieve, maybe with some more info I could help you with a more "fine tune" solution. But to answer your questions you can see how to do it in the documentation: Here

For your convenience this is the snippet of the docs:

const listAllUsers = (nextPageToken) => {
  // List batch of users, 1000 at a time.
  getAuth()
    .listUsers(1000, nextPageToken)
    .then((listUsersResult) => {
      listUsersResult.users.forEach((userRecord) => {
        console.log('user', userRecord.toJSON());
      });
      if (listUsersResult.pageToken) {
        // List next batch of users.
        listAllUsers(listUsersResult.pageToken);
      }
    })
    .catch((error) => {
      console.log('Error listing users:', error);
    });
};
// Start listing users from the beginning, 1000 at a time.
listAllUsers();

You can only retrieve 1K users at a time. On the top you have other methods to retrieve users.

The getUsers does not require the "UID", it only requires you to provide one of this: user ID, email, phone number or ProviderId with its corresponding providerUid. So if you have any of the other pieces of info from the user you can use that.

Alternatively you can use this: Import/Export Firebase auth to export your current data, map it and save a copy of all UIDs and email/providers in you db. Then on every new user, save that data.
The problem with this is that if you plan on supporting linking multiple accounts or switching email/phone or whatever auth system you have, you will need to keep that data updated and sync at all times.

Hope this helps and you have a great day. :)

@trullock
Copy link
trullock commented Aug 22, 2024

ah, listUsers is what I was looking for, thanks.

I'd accidentally created multiple users with the same email with no way to enumerate them to tidy up

The docs for getUsers says it needs uids 😫

@Flucadetena
Copy link
Author
Flucadetena commented Aug 22, 2024

@trullock ohhhh I see. There is a setting inside the Firebase Auth section, under "Configuration > Link users accounts" where you can force accounts using the same email to be linked together, even if they use different providers.

I guess this won't work for already existing accounts. One choice would be to activate the setting, export all the users auth data, restructure locally and import the new auth list.

Another alternative, a bit slower, is for you to manually link the accounts with a local function:
With a list of all UIds you want to merge extracted by exporting the auth data, you can:

// This function is one I use to migrate accounts of new users created by accident to link the new provider with the old
// account. You can adapt it to fit your purpose
export async function mergeAuthUsers(oldUserId: string, newUserId:string) {
  try {
    const oldUser = await auth().getUser(oldUserId);
    const newUser = await auth().getUser(newUserId);

    await auth().deleteUser(newUserId);

    const results = await auth().importUsers([
     //? This is a `UserImportRecord` model. I leave the interface bellow to make it easier.
      {
        uid: oldUser.uid,
        email: newUser.email,
        displayName: oldUser.displayName ?? newUser.displayName,
        photoURL: oldUser.photoURL ?? newUser.photoURL,
        emailVerified: newUser.emailVerified,
        //TODO: Here you can add all the providers from both accounts to link them
        providerData: newUser.providerData,
      },
    ]);
    console.log(`success: ${results.successCount}`);
    console.log(`errors: ${results.failureCount}`);

    results.errors.forEach((indexedError) => {
      console.log(`Error importing user ${indexedError.index}, err: ${indexedError.error}`);
    });
  } catch (err) {
    console.log("Error importing users :", err);
  }
}

UserImportRecord interface:

interface UserImportRecord {
    /**
     * The user's `uid`.
     */
    uid: string;
    /**
     * The user's primary email, if set.
     */
    email?: string;
    /**
     * Whether or not the user's primary email is verified.
     */
    emailVerified?: boolean;
    /**
     * The user's display name.
     */
    displayName?: string;
    /**
     * The user's primary phone number, if set.
     */
    phoneNumber?: string;
    /**
     * The user's photo URL.
     */
    photoURL?: string;
    /**
     * Whether or not the user is disabled: `true` for disabled; `false` for
     * enabled.
     */
    disabled?: boolean;
    /**
     * Additional metadata about the user.
     */
    metadata?: UserMetadataRequest;
    /**
     * An array of providers (for example, Google, Facebook) linked to the user.
     */
    providerData?: UserProviderRequest[];
    /**
     * The user's custom claims object if available, typically used to define
     * user roles and propagated to an authenticated user's ID token.
     */
    customClaims?: {
        [key: string]: any;
    };
    /**
     * The buffer of bytes representing the user's hashed password.
     * When a user is to be imported with a password hash,
     * {@link UserImportOptions} are required to be
     * specified to identify the hashing algorithm used to generate this hash.
     */
    passwordHash?: Buffer;
    /**
     * The buffer of bytes representing the user's password salt.
     */
    passwordSalt?: Buffer;
    /**
     * The identifier of the tenant where user is to be imported to.
     * When not provided in an `admin.auth.Auth` context, the user is uploaded to
     * the default parent project.
     * When not provided in an `admin.auth.TenantAwareAuth` context, the user is uploaded
     * to the tenant corresponding to that `TenantAwareAuth` instance's tenant ID.
     */
    tenantId?: string;
    /**
     * The user's multi-factor related properties.
     */
    multiFactor?: MultiFactorUpdateSettings;
}

@trullock
Copy link

Yeah ive got that setting ticked, but if you use the admin sdk to create auth users, you can create as many as you like with the same email address. Beware!

It was a cockup on my part importing users from a different system, but still I expected FB to both prevent this and then let me enumerate (it does now ive found the right method, thanks)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants