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 @@ -579,6 +579,7 @@ let login_time = chrono::Utc::now(); + let initial_device_list = message.get_and_verify_initial_device_list()?; let social_proof = SocialProof::new(message.siwe_message, message.siwe_signature); @@ -592,6 +593,7 @@ code_version, login_time, message.farcaster_id, + initial_device_list, ) .await .map_err(handle_db_error)?; @@ -660,6 +662,7 @@ let flattened_device_key_upload = construct_flattened_device_key_upload(&message)?; + let initial_device_list = message.get_and_verify_initial_device_list()?; let social_proof = SocialProof::new(message.siwe_message, message.siwe_signature); @@ -674,6 +677,7 @@ code_version, login_time, None, + initial_device_list, ) .await .map_err(handle_db_error)?; diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -18,8 +18,11 @@ pub use crate::database::device_list::DeviceIDAttribute; pub use crate::database::one_time_keys::OTKRow; use crate::{ - constants::error_types, constants::USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME, - ddb_utils::EthereumIdentity, reserved_users::UserDetail, siwe::SocialProof, + constants::{error_types, USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME}, + ddb_utils::EthereumIdentity, + device_list::SignedDeviceList, + reserved_users::UserDetail, + siwe::SocialProof, }; use crate::{ ddb_utils::{DBIdentity, OlmAccountType}, @@ -168,14 +171,29 @@ ) .await?; - self - .add_device( - &user_id, - device_key_upload.clone(), - code_version, - access_token_creation_time, - ) - .await?; + // When initial device list is present, we should apply it + // instead of auto-creating one. + if let Some(device_list) = registration_state.initial_device_list { + let initial_device_list = DeviceListUpdate::try_from(device_list)?; + self + .register_primary_device( + &user_id, + device_key_upload.clone(), + code_version, + access_token_creation_time, + initial_device_list, + ) + .await?; + } else { + self + .add_device( + &user_id, + device_key_upload.clone(), + code_version, + access_token_creation_time, + ) + .await?; + } self .append_one_time_prekeys( @@ -198,6 +216,7 @@ code_version: u64, access_token_creation_time: DateTime, farcaster_id: Option, + initial_device_list: Option, ) -> Result { let wallet_identity = EthereumIdentity { wallet_address, @@ -212,14 +231,29 @@ ) .await?; - self - .add_device( - &user_id, - flattened_device_key_upload.clone(), - code_version, - access_token_creation_time, - ) - .await?; + // When initial device list is present, we should apply it + // instead of auto-creating one. + if let Some(device_list) = initial_device_list { + let initial_device_list = DeviceListUpdate::try_from(device_list)?; + self + .register_primary_device( + &user_id, + flattened_device_key_upload.clone(), + code_version, + access_token_creation_time, + initial_device_list, + ) + .await?; + } else { + self + .add_device( + &user_id, + flattened_device_key_upload.clone(), + code_version, + access_token_creation_time, + ) + .await?; + } self .append_one_time_prekeys( 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 @@ -816,6 +816,60 @@ Ok(()) } + /// Registers primary device for user, stores its signed device list + pub async fn register_primary_device( + &self, + user_id: impl Into, + device_key_upload: FlattenedDeviceKeyUpload, + code_version: u64, + login_time: DateTime, + initial_device_list: DeviceListUpdate, + ) -> Result<(), Error> { + let user_id: String = user_id.into(); + self + .transact_update_devicelist(&user_id, |device_ids, devices_data| { + if !device_ids.is_empty() || !devices_data.is_empty() { + warn!( + "Tried creating initial device list for already existing user + (userID={})", + &user_id, + ); + return Err(Error::DeviceList(DeviceListError::DeviceAlreadyExists)); + } + + // Set device list + *device_ids = initial_device_list.devices.clone(); + + let primary_device = DeviceRow::from_device_key_upload( + &user_id, + device_key_upload, + code_version, + login_time, + )?; + + // Put device keys into DDB + let put_device = Put::builder() + .table_name(devices_table::NAME) + .set_item(Some(primary_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(); + + let update_info = + UpdateOperationInfo::primary_device_issued(initial_device_list) + .with_ddb_operation(put_device_operation); + Ok(update_info) + }) + .await?; + + Ok(()) + } + /// 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(