in app/lib/account/backend.dart [328:427]
Future<User?> _lookupOrCreateUserByOauthUserId(AuthResult auth) async {
ArgumentError.checkNotNull(auth, 'auth');
if (auth.oauthUserId == null) {
throw StateError('Authenticated user ${auth.email} without userId.');
}
final emptyKey = _db.emptyKey;
// Attempt to lookup the user, the common case is that the user exists.
// If the user exists, it's always cheaper to lookup the user outside a
// transaction.
final user = await _lookupUserByOauthUserId(auth.oauthUserId!);
if (user != null &&
user.email != auth.email &&
auth.email != null &&
auth.email!.isNotEmpty) {
return await _updateUserEmail(user, auth.email!);
}
if (user != null) {
return user;
}
return await withRetryTransaction<User>(_db, (tx) async {
final oauthUserIdKey = emptyKey.append(
OAuthUserID,
id: auth.oauthUserId,
);
// Check that the user doesn't exist in this transaction.
final oauthUserMapping =
await tx.lookupOrNull<OAuthUserID>(oauthUserIdKey);
if (oauthUserMapping != null) {
// If the user does exist we can just return it.
return await tx.lookupValue<User>(
oauthUserMapping.userIdKey!,
orElse: () => throw StateError(
'Incomplete OAuth userId mapping missing '
'User(`${oauthUserMapping.userId}`) referenced by '
'`${oauthUserMapping.id}`.',
),
);
}
// Check pre-migrated User with existing email.
// Notice, that we're doing this outside the transaction, but these are
// legacy users, we should avoid creation of new users with only emails
// as this lookup is eventually consistent.
final usersWithEmail = await (_db.query<User>()
..filter('email =', auth.email))
.run()
.toList();
if (usersWithEmail.length == 1 &&
usersWithEmail.single.oauthUserId == null &&
!usersWithEmail.single.isDeleted) {
final user = await tx.lookupValue<User>(usersWithEmail.single.key);
if (user.oauthUserId == auth.oauthUserId) {
throw StateError(
'Incomplete user oauthid mapping OAuthUserId entity is missing '
'for User(`${user.userId}`)',
);
}
if (user.oauthUserId == null) {
// We've found a single pre-migrated, non-deleted User with empty
// `oauthUserId` field: need to create OAuthUserID for it.
user.oauthUserId = auth.oauthUserId;
tx.insert(user);
tx.insert(
OAuthUserID()
..parentKey = emptyKey
..id = auth.oauthUserId
..userIdKey = user.key,
);
return user;
}
_logger.info(
'Reusing email from User(${user.userId}) as user has a different oauth2 user_id',
);
}
// Create new user with oauth2 user_id mapping
final user = User()
..parentKey = emptyKey
..id = createUuid()
..oauthUserId = auth.oauthUserId
..email = auth.email
..created = clock.now().toUtc()
..isBlocked = false
..isDeleted = false;
tx.insert(user);
tx.insert(
OAuthUserID()
..parentKey = emptyKey
..id = auth.oauthUserId
..userIdKey = user.key,
);
return user;
});
}