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 @@ -13,6 +13,7 @@ use tracing::{error, warn}; use crate::{ + client_service::FlattenedDeviceKeyUpload, constants::{ devices_table::{self, *}, USERS_TABLE, USERS_TABLE_DEVICELIST_TIMESTAMP_ATTRIBUTE_NAME, @@ -67,6 +68,34 @@ pub pre_key_signature: String, } +impl DeviceRow { + pub fn from_device_key_upload( + user_id: impl Into, + upload: FlattenedDeviceKeyUpload, + social_proof: Option, + ) -> Self { + Self { + user_id: user_id.into(), + device_id: upload.device_id_key, + device_type: DeviceType::from_str_name(upload.device_type.as_str_name()) + .expect("DeviceType conversion failed. Identity client and server protos mismatch"), + device_key_info: IdentityKeyInfo { + key_payload: upload.key_payload, + key_payload_signature: upload.key_payload_signature, + social_proof, + }, + content_prekey: PreKey { + pre_key: upload.content_prekey, + pre_key_signature: upload.content_prekey_signature, + }, + notif_prekey: PreKey { + pre_key: upload.notif_prekey, + pre_key_signature: upload.notif_prekey_signature, + } + } + } +} + impl DeviceListRow { /// Generates new device list row from given devices fn new(user_id: impl Into, device_ids: Vec) -> Self { @@ -448,6 +477,51 @@ .map_err(Error::from) } + /// Adds new device to user's device list. If the device already exists, the + /// operation fails. Transactionally generates new device list version. + pub async fn add_device( + &self, + user_id: impl Into, + device_key_upload: FlattenedDeviceKeyUpload, + social_proof: Option, + ) -> Result<(), Error> { + let user_id: String = user_id.into(); + self + .transact_update_devicelist(&user_id, |ref mut device_ids| { + let new_device = DeviceRow::from_device_key_upload( + &user_id, + device_key_upload, + social_proof, + ); + + if device_ids.iter().any(|id| &new_device.device_id == id) { + warn!( + "Device already exists in user's device list \ + (userID={}, deviceID={})", + &user_id, &new_device.device_id + ); + return Err(Error::DeviceList(DeviceListError::DeviceAlreadyExists)); + } + device_ids.push(new_device.device_id.clone()); + + // Put new device + let put_device = Put::builder() + .table_name(devices_table::NAME) + .set_item(Some(new_device.into())) + .condition_expression( + "attribute_not_exists(#user_id) AND attribute_not_exists(#item_id)", + ) + .expression_attribute_names("#user_id", ATTR_USER_ID) + .expression_attribute_names("#item_id", ATTR_ITEM_ID) + .build(); + let put_device_operation = + TransactWriteItem::builder().put(put_device).build(); + + Ok(put_device_operation) + }) + .await + } + /// Performs a transactional update of the device list for the user. Afterwards /// generates a new device list and updates the timestamp in the users table. /// This is done in a transaction. Operation fails if the device list has been diff --git a/services/identity/src/error.rs b/services/identity/src/error.rs --- a/services/identity/src/error.rs +++ b/services/identity/src/error.rs @@ -23,6 +23,7 @@ #[derive(Debug, derive_more::Display, derive_more::Error)] pub enum DeviceListError { + DeviceAlreadyExists, ConcurrentUpdateError, }