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 @@ -29,7 +29,9 @@ VerifyUserAccessTokenResponse, WalletLoginRequest, }; use crate::grpc_services::shared::get_value; -use crate::grpc_utils::DeviceKeyUploadActions; +use crate::grpc_utils::{ + ChallengeResponse, DeviceKeyUploadActions, NonceChallenge, +}; use crate::id::generate_uuid; use crate::nonce::generate_nonce_data; use crate::reserved_users::{ @@ -565,7 +567,74 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { - Err(tonic::Status::unimplemented("unimplemented")) + let code_version = get_code_version(&request); + let message = request.into_inner(); + + let challenge_response = ChallengeResponse::try_from(&message)?; + let flattened_device_key_upload = + construct_flattened_device_key_upload(&message)?; + + let user_id = message.user_id; + let device_id = flattened_device_key_upload.device_id_key.clone(); + + let NonceChallenge { nonce } = + challenge_response.verify_and_get_message(&device_id)?; + + self.verify_and_remove_nonce(&nonce).await?; + + let Some(device_list) = self + .client + .get_current_device_list(&user_id) + .await + .map_err(handle_db_error)? + else { + warn!("User {} does not have valid device list. Secondary device auth impossible.", user_id); + return Err(tonic::Status::aborted("device list error")); + }; + + if !device_list.device_ids.contains(&device_id) { + return Err(tonic::Status::permission_denied( + "device not in device list", + )); + } + + let login_time = chrono::Utc::now(); + + let user_identifier = self + .client + .get_user_identifier(&user_id) + .await + .map_err(handle_db_error)?; + let token = AccessTokenData::with_created_time( + user_id.clone(), + device_id, + login_time, + user_identifier.into(), + &mut OsRng, + ); + let access_token = token.access_token.clone(); + self + .client + .put_access_token_data(token) + .await + .map_err(handle_db_error)?; + + self + .client + .put_device_data( + &user_id, + flattened_device_key_upload, + code_version, + login_time, + ) + .await + .map_err(handle_db_error)?; + + let response = AuthResponse { + user_id, + access_token, + }; + Ok(Response::new(response)) } async fn generate_nonce( 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 @@ -622,6 +622,51 @@ .map_err(Error::from) } + /// Adds device data to devices table. If the device already exists, its + /// data is overwritten. This does not update the device list; the device ID + /// should already be present in the device list. + pub async fn put_device_data( + &self, + user_id: impl Into, + device_key_upload: FlattenedDeviceKeyUpload, + code_version: u64, + login_time: DateTime, + ) -> Result<(), Error> { + let content_one_time_keys = device_key_upload.content_one_time_keys.clone(); + let notif_one_time_keys = device_key_upload.notif_one_time_keys.clone(); + let new_device = DeviceRow::from_device_key_upload( + user_id, + device_key_upload, + code_version, + login_time, + ); + let device_id = new_device.device_id.clone(); + + self + .client + .put_item() + .table_name(devices_table::NAME) + .set_item(Some(new_device.into())) + .expression_attribute_names("#user_id", ATTR_USER_ID) + .expression_attribute_names("#item_id", ATTR_ITEM_ID) + .send() + .await + .map_err(|e| { + error!("Failed to put device data: {:?}", e); + Error::AwsSdk(e.into()) + })?; + + self + .append_one_time_prekeys( + device_id, + content_one_time_keys, + notif_one_time_keys, + ) + .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( diff --git a/services/identity/src/token.rs b/services/identity/src/token.rs --- a/services/identity/src/token.rs +++ b/services/identity/src/token.rs @@ -4,7 +4,7 @@ CryptoRng, Rng, }; -use crate::constants::ACCESS_TOKEN_LENGTH; +use crate::{constants::ACCESS_TOKEN_LENGTH, ddb_utils::Identifier}; #[derive(Clone, Eq, PartialEq)] pub enum AuthType { @@ -12,6 +12,15 @@ Wallet, } +impl From for AuthType { + fn from(id: Identifier) -> Self { + match id { + Identifier::Username(_) => AuthType::Password, + Identifier::WalletAddress(_) => AuthType::Wallet, + } + } +} + #[derive(Clone)] pub struct AccessTokenData { pub user_id: String,