Fix anonymous account upgrade to preserve Firebase UID

This commit is contained in:
bponders12-cell 2026-08-31 10:57:08 -04:00
parent 81064da7f4
commit 0522e6cbbf
1 changed files with 69 additions and 12 deletions

View File

@ -131,6 +131,16 @@ class FirebaseAuthService implements AuthService {
); );
} }
bool get _hasAnonymousFirebaseUser =>
_firebaseAuth?.currentUser?.isAnonymous == true;
void _updateAuthenticatedUser(fb.User user) {
_updateState(AuthState.authenticated(
_mapFirebaseUser(user),
isFirebaseConfigured: true,
));
}
@override @override
Future<void> signInWithEmailAndPassword(String email, String password) async { Future<void> signInWithEmailAndPassword(String email, String password) async {
final auth = _firebaseAuth; final auth = _firebaseAuth;
@ -149,13 +159,29 @@ class FirebaseAuthService implements AuthService {
)); ));
try { try {
final normalizedEmail = email.trim();
// If the current Firebase identity is anonymous, upgrade that identity
// instead of signing into a new Firebase UID. This preserves ownership
// of any cloud data already associated with the guest account.
if (_hasAnonymousFirebaseUser) {
final credential = fb.EmailAuthProvider.credential(
email: normalizedEmail,
password: password,
);
final result = await auth.currentUser!.linkWithCredential(credential);
if (result.user != null) {
_updateAuthenticatedUser(result.user!);
}
return;
}
final credential = await auth.signInWithEmailAndPassword( final credential = await auth.signInWithEmailAndPassword(
email: email.trim(), email: normalizedEmail,
password: password, password: password,
); );
if (credential.user != null) { if (credential.user != null) {
final user = _mapFirebaseUser(credential.user!); _updateAuthenticatedUser(credential.user!);
_updateState(AuthState.authenticated(user, isFirebaseConfigured: true));
} }
} catch (e) { } catch (e) {
_updateState(AuthState.error( _updateState(AuthState.error(
@ -188,16 +214,29 @@ class FirebaseAuthService implements AuthService {
)); ));
try { try {
final credential = await auth.createUserWithEmailAndPassword( final normalizedEmail = email.trim();
email: email.trim(), final emailCredential = fb.EmailAuthProvider.credential(
email: normalizedEmail,
password: password, password: password,
); );
fb.UserCredential credential;
if (_hasAnonymousFirebaseUser) {
// Upgrade the existing anonymous UID rather than creating a second
// account. This keeps prior guest-owned cloud data attached to the user.
credential = await auth.currentUser!.linkWithCredential(emailCredential);
} else {
credential = await auth.createUserWithEmailAndPassword(
email: normalizedEmail,
password: password,
);
}
if (credential.user != null) { if (credential.user != null) {
if (displayName != null && displayName.trim().isNotEmpty) { if (displayName != null && displayName.trim().isNotEmpty) {
await credential.user!.updateDisplayName(displayName.trim()); await credential.user!.updateDisplayName(displayName.trim());
} }
final user = _mapFirebaseUser(credential.user!); _updateAuthenticatedUser(credential.user!);
_updateState(AuthState.authenticated(user, isFirebaseConfigured: true));
} }
} catch (e) { } catch (e) {
_updateState(AuthState.error( _updateState(AuthState.error(
@ -231,7 +270,14 @@ class FirebaseAuthService implements AuthService {
if (googleUser == null) { if (googleUser == null) {
// User cancelled the sign-in modal/flow // User cancelled the sign-in modal/flow
_updateState(_currentState.copyWith(status: _currentState.user != null ? AuthStatus.authenticated : AuthStatus.unauthenticated, clearError: true)); _updateState(_currentState.copyWith(
status: _currentState.user != null
? (_currentState.user!.isAnonymous
? AuthStatus.anonymous
: AuthStatus.authenticated)
: AuthStatus.unauthenticated,
clearError: true,
));
return; return;
} }
@ -241,10 +287,17 @@ class FirebaseAuthService implements AuthService {
idToken: googleAuth.idToken, idToken: googleAuth.idToken,
); );
final result = await auth.signInWithCredential(credential); fb.UserCredential result;
if (_hasAnonymousFirebaseUser) {
// Critical: link the Google provider to the existing anonymous user.
// Do not call signInWithCredential here or the guest UID can be replaced.
result = await auth.currentUser!.linkWithCredential(credential);
} else {
result = await auth.signInWithCredential(credential);
}
if (result.user != null) { if (result.user != null) {
final user = _mapFirebaseUser(result.user!); _updateAuthenticatedUser(result.user!);
_updateState(AuthState.authenticated(user, isFirebaseConfigured: true));
} }
} catch (e) { } catch (e) {
_updateState(AuthState.error( _updateState(AuthState.error(
@ -358,7 +411,11 @@ class FirebaseAuthService implements AuthService {
case 'invalid-credential': case 'invalid-credential':
return 'Invalid credentials. Please verify your email and password.'; return 'Invalid credentials. Please verify your email and password.';
case 'email-already-in-use': case 'email-already-in-use':
return 'An account already exists with this email address.'; return 'An account already exists with this email address. Sign in to that account instead.';
case 'credential-already-in-use':
return 'This Google account is already linked to another MotorSignal account. Sign in with that account instead of replacing the current guest account.';
case 'provider-already-linked':
return 'This sign-in provider is already linked to the current account.';
case 'invalid-email': case 'invalid-email':
return 'The email address format is invalid.'; return 'The email address format is invalid.';
case 'weak-password': case 'weak-password':