changeUsername method
Changes a username while preserving uniqueness atomically.
Implementation
@override
Future<AuthUsernameMutationResult> changeUsername(
AuthUsernameChangeCommand command,
) => _serializeUsernameMutation(command.userId, () async {
final user = _users._usersById[command.userId];
final credential = _credentials._credentialsById[command.credentialId];
if (user == null ||
credential == null ||
credential.userId != command.userId ||
credential.identifier.contains('@')) {
return const AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.notFound,
);
}
if (authUserIsDisabled(user)) {
return const AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.userUnavailable,
);
}
final state = await _accountStates.find(user.id);
if ((state?.disabled ?? false) || (state?.isLocked() ?? false)) {
return const AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.userUnavailable,
);
}
if (credential.identifier == command.username &&
user.attributes['username'] == command.username) {
return AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.unchanged,
user: user,
credential: credential,
);
}
if (credential.identifier != command.expectedUsername ||
user.attributes['username'] != command.expectedUsername) {
return const AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.conflict,
);
}
final conflictingId =
_credentials._credentialIdsByIdentifier[command.username];
if (conflictingId != null && conflictingId != credential.id) {
return const AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.conflict,
);
}
if (!_credentials._inFlightIdentifiers.add(command.username)) {
return const AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.conflict,
);
}
final updatedCredential = AuthPasswordCredential(
id: credential.id,
userId: credential.userId,
identifier: command.username,
passwordHash: credential.passwordHash,
createdAt: credential.createdAt,
updatedAt: command.updatedAt.toUtc(),
enabled: credential.enabled,
);
final updatedUser = _usernameProjection(
user,
from: command.expectedUsername,
to: command.username,
);
try {
_credentials._credentialIdsByIdentifier.remove(credential.identifier);
_credentials._credentialIdsByIdentifier[command.username] = credential.id;
_credentials._credentialsById[credential.id] = updatedCredential;
await _injectUsernameFault(
AuthUsernameFaultPoint.changeAfterCredentialWrite,
);
_users._usersById[user.id] = updatedUser;
if (user.email != null) _users._usersByEmail[user.email!] = updatedUser;
await _injectUsernameFault(AuthUsernameFaultPoint.changeAfterUserWrite);
return AuthUsernameMutationResult(
status: AuthUsernameMutationStatus.changed,
user: updatedUser,
credential: updatedCredential,
);
} catch (_) {
_credentials._credentialIdsByIdentifier.remove(command.username);
_credentials._credentialIdsByIdentifier[credential.identifier] =
credential.id;
_credentials._credentialsById[credential.id] = credential;
_users._usersById[user.id] = user;
if (user.email != null) _users._usersByEmail[user.email!] = user;
rethrow;
} finally {
_credentials._inFlightIdentifiers.remove(command.username);
}
});