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 @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use chrono::{DateTime, Utc}; use comm_lib::{ @@ -14,7 +14,7 @@ DynamoDBError, TryFromAttribute, }, }; -use tracing::{error, warn}; +use tracing::{debug, error, warn}; use crate::{ client_service::FlattenedDeviceKeyUpload, @@ -746,6 +746,36 @@ }) .await?; Ok(()) + } + + /// applies updated device list received from primary device + pub async fn apply_devicelist_update( + &self, + user_id: &str, + update: DeviceListUpdate, + ) -> Result { + let DeviceListUpdate { devices: new_list } = update; + self + .transact_update_devicelist(user_id, |current_list, _| { + // TODO: Add proper validation according to the whitepaper + // currently only adding new device is supported (new.len - old.len = 1) + + let new_set: HashSet<_> = new_list.iter().collect(); + let current_set: HashSet<_> = current_list.iter().collect(); + // difference is A - B (only new devices) + let difference: HashSet<_> = new_set.difference(¤t_set).collect(); + if difference.len() != 1 { + warn!("Received invalid device list update"); + return Err(Error::DeviceList( + DeviceListError::InvalidDeviceListUpdate, + )); + } + + debug!("Applying device list update. Difference: {:?}", difference); + *current_list = new_list; + + Ok(None) + }) .await } 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 @@ -29,6 +29,7 @@ DeviceAlreadyExists, DeviceNotFound, ConcurrentUpdateError, + InvalidDeviceListUpdate, } #[deprecated(note = "Use comm-lib traits instead")] diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs --- a/services/identity/src/grpc_services/authenticated.rs +++ b/services/identity/src/grpc_services/authenticated.rs @@ -416,9 +416,24 @@ async fn update_device_list_for_user( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { - Err(tonic::Status::unimplemented("not implemented")) + let (user_id, _device_id) = get_user_and_device_id(&request)?; + // TODO: when we stop doing "primary device rotation" (migration procedure) + // we should verify if this RPC is called by primary device only + + let update = DeviceListUpdate::try_from(request.into_inner())?; + let result = self + .db_client + .apply_devicelist_update(&user_id, update) + .await + .map_err(handle_db_error)?; + + // prepare response + let raw_list = RawDeviceList::from(result); + let signed_list = SignedDeviceList::try_from_raw(raw_list)?; + let response = UpdateDeviceListResponse::try_from(signed_list)?; + Ok(Response::new(response)) } }