返回

Firebase LinkWithCredential 保留原有主邮箱地址指南

vue.js

通过 Firebase linkWithCredential 保留主要电子邮件

导言

Firebase linkWithCredential 方法提供了一种将用户帐户与多个身份提供者关联的方法。然而,默认情况下,此方法会自动更新用户的主要电子邮件地址,即使未进行电子邮件验证。对于希望保留 Google 提供者电子邮件地址的用户来说,这是一个问题。

问题

当通过密码提供者将用户与 Google 提供者链接时,linkWithCredential 方法会将 Google 提供者的电子邮件地址设置为主要电子邮件。这导致无法在取消链接 Google 提供者或密码提供者后恢复到以前的电子邮件地址。

解决方案

链接电子邮件和密码

为了链接电子邮件和密码而不更新主要电子邮件地址,请执行以下步骤:

  1. 创建一个新的 EmailAuthProvider 凭证。
  2. 使用 linkWithCredential 方法将凭证链接到当前用户。
  3. 发送电子邮件验证以确认新电子邮件地址。
  4. 在 Firestore 中更新用户文档,将电子邮件更新为新电子邮件。

设置主要电子邮件

在链接新电子邮件后,可以将其设置为主要电子邮件:

  1. 如果新主要电子邮件来自 Google,请先重新验证用户。
  2. 使用 updateEmail 方法更新电子邮件地址。
  3. 在 Firestore 中更新用户文档,将电子邮件更新为新电子邮件。

取消链接提供者

要取消链接提供者,请执行以下步骤:

  1. 确保用户已链接多个提供者。
  2. 使用 unlink 方法取消链接提供者。
  3. 如果取消链接的提供者是密码提供者,请尝试将电子邮件恢复为 Google 提供者的电子邮件地址(如果存在)。

代码示例

以下代码示例演示了如何实现上述方法:

// 链接电子邮件和密码
async function linkEmailAndPassword(newEmail, newPassword) {
  const currentUser = getAuth().currentUser;

  if (currentUser) {
    try {
      console.log(`Linking new email: ${newEmail}`);

      const credential = EmailAuthProvider.credential(newEmail, newPassword);
      await linkWithCredential(currentUser, credential);

      console.log(`Email linked successfully, sending verification email.`);

      // Send email verification
      await sendEmailVerification(currentUser);

      // Update Firestore
      const userDocRef = doc(firestore, "users", currentUser.uid);
      await updateDoc(userDocRef, {
        email: newEmail, // Update Firestore with the new email
        emailVerified: false, // Set emailVerified to false until the user verifies it
      });

      // Refresh local state
      authStore.setUser({
        ...authStore.user,
        email: newEmail,
        emailVerified: false,
      });

      return {
        success: true,
        message: "New email linked successfully. Please verify your email.",
      };
    } catch (error) {
      console.error(`Error linking new email: ${error}`);
      throw error;
    }
  } else {
    throw new Error("No user currently signed in.");
  }
}

// 设置主要电子邮件
async function makeEmailPrimary(newEmail) {
  const currentUser = getAuth().currentUser;

  if (currentUser && newEmail !== currentUser.email) {
    try {
      // Reauthentication should have just occurred before this action is triggered.
      await updateEmail(currentUser, newEmail);

      // Update Firestore and local authStore only after successful email update.
      const userDocRef = doc(firestore, "users", currentUser.uid);
      await updateDoc(userDocRef, { email: newEmail });

      authStore.setUser({ ...authStore.user, email: newEmail }); // Update the local state.

      return { success: true, message: "Primary email updated successfully." };
    } catch (error) {
      console.error(`Error updating primary email: ${error}`);
      return { success: false, message: `Failed to update primary email: ${error.message}` };
    }
  } else {
    return {
      success: false,
      message: "New email is the same as the current one or no user signed in.",
    };
  }
}

// 取消链接提供者
async function unlinkProvider(providerId) {
  console.log(`Attempting to unlink provider: ${providerId}`);

  try {
    if (authStore.user.providerData.length > 1) {
      await unlink(authFirebase.currentUser, providerId);
      console.log(`Provider ${providerId} unlinked successfully`);

      if (providerId === "password") {
        const googleProviderData = authStore.user.providerData.find(
          (pd) => pd.providerId === "google.com"
        );

        if (googleProviderData && googleProviderData.email) {
          try {
            // Attempt to update the email back to the Google one.
            await updateEmail(authFirebase.currentUser, googleProviderData.email);
            console.log(
              `Firebase Auth email reverted to Google email: ${googleProviderData.email}`
            );
            await authStore.updateUserInfo({ email: googleProviderData.email });
          } catch (error) {
            if (error.code === "auth/requires-recent-login") {
              console.log("User needs to re-authenticate to update their email.");
            } else {
              console.error(`Error updating email: ${error}`);
            }
          }
        } else {
          console.log("Google email not found or user not signed in with Google.");
        }
      }

      successMessage.value = `Successfully unlinked ${getProviderName(providerId)}`;
      await authStore.refreshUser(); // Refresh local user data to reflect changes
    } else {
      console.log("Cannot unlink the last remaining provider");
      error.value = "You cannot unlink the last remaining provider.";
    }
  } catch (err) {
    console.error(`Error unlinking ${getProviderName(providerId)}:`, err);
    error.value = `Failed to unlink ${getProviderName(providerId)}. Please try again.`;
  }
}

结论

通过使用上述方法,可以链接新身份提供者而不更新主要电子邮件地址,并在取消链接后优雅地恢复到先前方法或替代方法。这提供了更高的灵活性,并确保用户的电子邮件地址始终受到控制。

常见问题解答

  1. 我可以在 linkWithCredential 中同时更新电子邮件和密码吗?

    可以,但在更新密码之前必须先验证电子邮件。

  2. 如何验证新的电子邮件地址?

    通过 sendEmailVerification 方法发送电子邮件验证并单击电子邮件中的链接进行验证。

  3. 取消链接密码提供者后,为什么我需要重新验证?

    取消链接密码提供者会将用户的状态重置为未验证,因此需要重新验证才能继续进行其他操作。

  4. 如果我忘记了新密码,如何恢复我的帐户?

    可以使用 sendPasswordResetEmail 方法重置密码。

  5. 我在尝试使用提供的代码示例时遇到错误,该怎么办?

    确保将代码更新为与 Firebase 当前 SDK 版本兼容的代码。如有任何疑问,请查阅 Firebase 文档或在 Stack Overflow 等论坛上寻求帮助。