正如我们在与此情况和 github 问题相关的另一篇文章中看到的那样,预期的行为是 Google IDP 会覆盖与同一电子邮件相关的其他不受信任的提供商(例如,具有相同电子邮件 + 密码的另一个帐户(未经验证)) .
尝试了解 Firebase 身份验证每个电子邮件地址一个帐户和受信任的提供商
https://github.com/firebase/firebase-ios-sdk/issues/5344
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ
所以,好吧,根据谷歌的说法,这是预期的行为。
当我们查看文档时,我们的问题就出现了,有一个用户使用 google 登录并收到此错误的示例
auth/account-exists-with-different-credential
只是因为使用同一电子邮件地址+密码创建了另一个帐户。然后,他们建议捕获错误,检查用户电子邮件相关的登录方法,并要求用户使用其他提供商登录,然后链接到谷歌。
这有道理吗?如果他们说预期的行为是谷歌作为受信任的提供商将覆盖其他提供商(这就是我们身上发生的事情),代码示例的情况怎么可能发生?
https://firebase.google.com/docs/auth/web/google-signin#expandable-1
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(result) {
// Step 4a.
return result.user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that they already have an account
// but with a different provider, and let them validate the fact they want to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account they own.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
flutter 文档中还有另一个具有相同结构的示例:
文档中是否存在矛盾?同样,如果在这种情况下 Firebase 始终优先考虑受信任的 IDP(Google 电子邮件),那么如果删除其他提供商(至少在激活帐户链接时 - 每个电子邮件激活一个帐户),怎么可能出现此错误?
至少这是我们的情况。我们使用电子邮件和密码创建一个帐户,然后尝试使用相同的电子邮件登录谷歌,结果是电子邮件和密码帐户被新的谷歌提供商覆盖。
不幸的是,您无法更改它。如果拥有
@gmail.com
电子邮件和 password
身份验证的用户更新其个人资料图片,然后使用 Google 登录,则个人资料图片和任何其他信息都将被 Google 的数据覆盖。唯一的选择是在数据库中创建一条用户记录,当第一次创建用户时,该记录会填充用户数据(displayName
、photoURL
等)。然后,您始终使用此记录中的数据,而不是身份验证返回的默认 user
对象。
创建记录的另一个优点是您可以为其附加侦听器。这样,如果用户更改其详细信息,则会在各处得到反映。
我使用了阻塞功能来解决这个问题。如果用户之前使用电子邮件/密码注册但未经验证,我会阻止他们使用 Google 登录,并在客户端上向他们显示错误。
export const beforesignin = beforeUserSignedIn(async (event) => {
const user = event.data
if (user.email && event.eventType.includes(':google.com')) {
let existingUser
try {
existingUser = await admin.auth().getUserByEmail(user.email)
} catch (error) {
logger.error('Error fetching user by email:', error)
}
if (existingUser && !existingUser.emailVerified) {
logger.info(
`User with email ${user.email} has an existing unverified account: `,
existingUser
)
// check if email is unverified
throw new HttpsError(
'failed-precondition',
'Unverified email in existing account'
)
}
// otherwise, allow the sign in
}
})