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 @@ -5,6 +5,7 @@ use comm_lib::aws::DynamoDBError; use comm_lib::shared::reserved_users::RESERVED_USERNAME_SET; use comm_opaque2::grpc::protocol_error_to_grpc_status; +use grpc_clients::identity::PlatformMetadata; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use siwe::eip55; @@ -15,7 +16,7 @@ use crate::config::CONFIG; use crate::constants::{error_types, tonic_status_messages}; use crate::database::{ - DBDeviceTypeInt, DatabaseClient, DeviceType, KeyPayload, UserInfoAndPasswordFile + DBDeviceTypeInt, DatabaseClient, DeviceType, KeyPayload, UserInfoAndPasswordFile, DeviceListRow }; use crate::device_list::SignedDeviceList; use crate::error::{DeviceListError, Error as DBError}; @@ -692,14 +693,93 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { - unimplemented!(); + let platform_metadata = get_platform_metadata(&request)?; + let message = request.into_inner(); + debug!("Attempting to restore user: {:?}", &message.username); + + let user_id = self + .client + .get_user_id_from_user_info(message.username.clone(), &AuthType::Password) + .await + .map_err(handle_db_error)? + .ok_or_else(|| { + tonic::Status::not_found(tonic_status_messages::USER_NOT_FOUND) + })?; + + let flattened_device_key_upload = + construct_flattened_device_key_upload(&message)?; + + // verify nonce + let challenge_response = SignedNonce::try_from(&message)?; + let nonce = challenge_response + .verify_and_get_nonce(&flattened_device_key_upload.device_id_key)?; + self.verify_and_remove_nonce(&nonce).await?; + + let access_token = self + .restore_primary_device_and_get_csat( + user_id.clone(), + message.device_list, + platform_metadata, + flattened_device_key_upload, + ) + .await?; + + let response = AuthResponse { + user_id, + access_token, + username: message.username, + }; + Ok(Response::new(response)) } async fn restore_wallet_user( &self, request: tonic::Request, ) -> Result, tonic::Status> { - unimplemented!(); + let platform_metadata = get_platform_metadata(&request)?; + let message = request.into_inner(); + + let parsed_message = parse_and_verify_siwe_message( + &message.siwe_message, + &message.siwe_signature, + ) + .await?; + self.verify_and_remove_nonce(&parsed_message.nonce).await?; + let wallet_address = eip55(&parsed_message.address); + + let user_id = self + .client + .get_user_id_from_user_info(wallet_address.clone(), &AuthType::Wallet) + .await + .map_err(handle_db_error)? + .ok_or_else(|| { + tonic::Status::not_found(tonic_status_messages::USER_NOT_FOUND) + })?; + let flattened_device_key_upload = + construct_flattened_device_key_upload(&message)?; + + let social_proof = + SocialProof::new(message.siwe_message, message.siwe_signature); + self + .client + .update_wallet_user_social_proof(&user_id, social_proof) + .await?; + + let access_token = self + .restore_primary_device_and_get_csat( + user_id.clone(), + message.device_list, + platform_metadata, + flattened_device_key_upload, + ) + .await?; + + let response = AuthResponse { + user_id, + access_token, + username: wallet_address, + }; + Ok(Response::new(response)) } #[tracing::instrument(skip_all)] @@ -1166,6 +1246,91 @@ )) } } + + /// This function is used in Backup Restore protocol. It: + /// - Verifies singleton device list with both old and new primary signature + /// - Performs device list update (updates with singleton payload) + /// - Removes all CSATs, OTKs, Devices data for all devices + /// - Closes Tunnelbroker connections with these devices + /// - Registers the new primary device (Device Key Upload) + /// - Issues a new CSAT for the new primary device and returns it + async fn restore_primary_device_and_get_csat( + &self, + user_id: String, + device_list_payload: String, + platform_metadata: PlatformMetadata, + device_key_upload: FlattenedDeviceKeyUpload, + ) -> Result { + let new_primary_device_id = device_key_upload.device_id_key.clone(); + let previous_primary_device_id = self + .client + .get_current_device_list(&user_id) + .await? + .and_then(|device_list| device_list.primary_device_id().cloned()) + .ok_or_else(|| { + error!( + user_id = redact_sensitive_data(&user_id), + errorType = error_types::GRPC_SERVICES_LOG, + "User had missing or empty device list (before backup restore)!" + ); + tonic::Status::failed_precondition( + tonic_status_messages::NO_DEVICE_LIST, + ) + })?; + + // Verify device list payload + let signed_list: SignedDeviceList = device_list_payload.parse()?; + let device_list_payload = + crate::database::DeviceListUpdate::try_from(signed_list)?; + crate::device_list::verify_singleton_device_list( + &device_list_payload, + &new_primary_device_id, + Some(&previous_primary_device_id), + )?; + + debug!(user_id, "Attempting to revoke user's old access tokens"); + self.client.delete_all_tokens_for_user(&user_id).await?; + // We must delete the one-time keys first because doing so requires device + // IDs from the devices table + debug!(user_id, "Attempting to delete user's old one-time keys"); + self + .client + .delete_otks_table_rows_for_user(&user_id) + .await?; + debug!(user_id, "Attempting to delete user's old devices"); + let _old_device_ids = + self.client.delete_devices_data_for_user(&user_id).await?; + + // TODO: Revoke TB sessions with previous devices + + // Reset device list (perform update) + let login_time = chrono::Utc::now(); + self + .client + .register_primary_device( + &user_id, + device_key_upload, + platform_metadata, + login_time, + device_list_payload, + ) + .await + .map_err(handle_db_error)?; + + // Create new access token + let token_data = AccessTokenData::with_created_time( + user_id.clone(), + new_primary_device_id, + login_time, + crate::token::AuthType::Password, + &mut OsRng, + ); + + let access_token = token_data.access_token.clone(); + self.client.put_access_token_data(token_data).await?; + + Ok(access_token) + } } #[tracing::instrument(skip_all)] @@ -1200,6 +1365,12 @@ } } +impl From for tonic::Status { + fn from(err: DBError) -> Self { + handle_db_error(err) + } +} + fn construct_user_registration_info( message: &(impl DeviceKeyUploadActions + RegistrationActions), user_id: Option, 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 @@ -197,6 +197,10 @@ pub fn has_secondary_device(&self, device_id: &String) -> bool { self.has_device(device_id) && !self.is_primary_device(device_id) } + + pub fn primary_device_id(&self) -> Option<&String> { + self.device_ids.first() + } } impl PlatformDetails {