diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs
--- a/services/identity/src/client_service.rs
+++ b/services/identity/src/client_service.rs
@@ -141,6 +141,12 @@
message.username.clone(),
message.farcaster_id.clone(),
)?;
+ self
+ .check_device_id_taken(
+ ®istration_state.flattened_device_key_upload,
+ None,
+ )
+ .await?;
let server_registration = comm_opaque2::server::Registration::new();
let server_message = server_registration
.start(
@@ -197,6 +203,12 @@
message.username.clone(),
None,
)?;
+ self
+ .check_device_id_taken(
+ ®istration_state.flattened_device_key_upload,
+ None,
+ )
+ .await?;
let server_registration = comm_opaque2::server::Registration::new();
let server_message = server_registration
.start(
@@ -322,6 +334,9 @@
let flattened_device_key_upload =
construct_flattened_device_key_upload(&message)?;
+ self
+ .check_device_id_taken(&flattened_device_key_upload, Some(&user_id))
+ .await?;
let maybe_device_to_remove = self
.get_keyserver_device_to_remove(
@@ -484,6 +499,10 @@
return Err(tonic::Status::not_found("user not found"));
};
+ self
+ .check_device_id_taken(&flattened_device_key_upload, Some(&user_id))
+ .await?;
+
self
.client
.add_user_device(
@@ -573,6 +592,9 @@
let flattened_device_key_upload =
construct_flattened_device_key_upload(&message)?;
+ self
+ .check_device_id_taken(&flattened_device_key_upload, None)
+ .await?;
let login_time = chrono::Utc::now();
@@ -658,6 +680,9 @@
let flattened_device_key_upload =
construct_flattened_device_key_upload(&message)?;
+ self
+ .check_device_id_taken(&flattened_device_key_upload, None)
+ .await?;
let initial_device_list = message.get_and_verify_initial_device_list()?;
let social_proof =
@@ -722,6 +747,10 @@
let nonce = challenge_response.verify_and_get_nonce(&device_id)?;
self.verify_and_remove_nonce(&nonce).await?;
+ self
+ .check_device_id_taken(&flattened_device_key_upload, Some(&user_id))
+ .await?;
+
let user_identity = self
.client
.get_user_identity(&user_id)
@@ -1048,6 +1077,38 @@
Ok(())
}
+ async fn check_device_id_taken(
+ &self,
+ key_upload: &FlattenedDeviceKeyUpload,
+ requesting_user_id: Option<&str>,
+ ) -> Result<(), tonic::Status> {
+ let device_id = key_upload.device_id_key.as_str();
+ let Some(existing_device_user_id) = self
+ .client
+ .find_user_id_for_device(device_id)
+ .await
+ .map_err(handle_db_error)?
+ else {
+ // device ID doesn't exist
+ return Ok(());
+ };
+
+ // allow already-existing device ID for the same user
+ match requesting_user_id {
+ Some(user_id) if user_id == existing_device_user_id => {
+ debug!(
+ "Found already-existing device {} for user {}",
+ device_id, user_id
+ );
+ Ok(())
+ }
+ _ => {
+ warn!("Device ID already exists: {device_id}");
+ Err(tonic::Status::already_exists("device_already_exists"))
+ }
+ }
+ }
+
async fn verify_and_remove_nonce(
&self,
nonce: &str,
diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs
--- a/services/identity/src/database/device_list.rs
+++ b/services/identity/src/database/device_list.rs
@@ -746,11 +746,13 @@
Ok(user_devices_keys)
}
+ /// Find owner's user ID for given device ID. Useful for finding
+ /// devices table partition key.
#[tracing::instrument(skip_all)]
- pub async fn find_device_by_id(
+ pub async fn find_user_id_for_device(
&self,
device_id: &str,
- ) -> Result