diff --git a/services/backup/src/error.rs b/services/backup/src/error.rs --- a/services/backup/src/error.rs +++ b/services/backup/src/error.rs @@ -8,7 +8,7 @@ pub use aws_sdk_dynamodb::Error as DynamoDBError; use comm_lib::database::Error as DBError; use comm_lib::{auth::AuthServiceError, blob::client::BlobServiceError}; -use grpc_clients::error::Error as IdentityClientError; +use grpc_clients::error::Error as IdentityError; use reqwest::StatusCode; use tracing::{error, trace, warn}; @@ -21,7 +21,8 @@ BlobError(BlobServiceError), AuthError(AuthServiceError), DB(comm_lib::database::Error), - IdentityClientError(IdentityClientError), + IdentityError(IdentityError), + BadRequest, } impl From<&BackupError> for actix_web::Error { @@ -68,11 +69,12 @@ ErrorInternalServerError("server error") } }, - BackupError::IdentityClientError(err) => { + BackupError::IdentityError(err) => { warn!("Transient identity error occurred: {err}"); ErrorServiceUnavailable("please retry") } BackupError::NoUserID => ErrorBadRequest("bad request"), + BackupError::BadRequest => ErrorBadRequest("bad request"), } } } diff --git a/services/backup/src/http/handlers/backup.rs b/services/backup/src/http/handlers/backup.rs --- a/services/backup/src/http/handlers/backup.rs +++ b/services/backup/src/http/handlers/backup.rs @@ -52,46 +52,10 @@ ) .await?; - let attachments_hashes: Vec = - match get_text_field(&mut multipart).await? { - Some((name, attachments)) => { - if name != "attachments" { - warn!( - name, - "Malformed request: 'attachments' text field expected." - ); - return Err(ErrorBadRequest("Bad request")); - } - - attachments.lines().map(ToString::to_string).collect() - } - None => Vec::new(), - }; - - let mut attachments = Vec::new(); - let mut attachments_revokes = Vec::new(); - for attachment_hash in attachments_hashes { - let (holder, revoke) = - create_attachment_holder(&attachment_hash, &blob_client).await?; + let (attachments, attachments_revokes) = + process_attachments(&mut multipart, &blob_client).await?; - attachments.push(BlobInfo { - blob_hash: attachment_hash, - holder, - }); - attachments_revokes.push(revoke); - } - - let siwe_backup_msg_option: Option = - match get_text_field(&mut multipart).await? { - Some((name, siwe_backup_msg)) => { - if name == "siwe_backup_msg" { - Some(siwe_backup_msg) - } else { - None - } - } - _ => None, - }; + let siwe_backup_msg = get_siwe_backup_msg(&mut multipart).await?; let item = BackupItem::new( user.user_id.clone(), @@ -99,7 +63,7 @@ user_keys_blob_info, user_data_blob_info, attachments, - siwe_backup_msg_option, + siwe_backup_msg, ); db_client @@ -194,7 +158,7 @@ async fn create_attachment_holder<'revoke, 'blob: 'revoke>( attachment: &str, blob_client: &'blob BlobServiceClient, -) -> Result<(String, Defer<'revoke>), BackupError> { +) -> Result<(BlobInfo, Defer<'revoke>), BackupError> { let holder = uuid::Uuid::new_v4().to_string(); if !blob_client @@ -211,7 +175,57 @@ blob_client.schedule_revoke_holder(revoke_hash, revoke_holder) }); - Ok((holder, revoke_holder)) + let blob_info = BlobInfo { + blob_hash: attachment.to_string(), + holder, + }; + + Ok((blob_info, revoke_holder)) +} + +#[instrument(skip_all)] +async fn process_attachments<'revoke, 'blob: 'revoke>( + multipart: &mut actix_multipart::Multipart, + blob_client: &'blob BlobServiceClient, +) -> Result<(Vec, Vec>), BackupError> { + let attachments_hashes: Vec = match get_text_field(multipart).await { + Ok(Some((name, attachments))) => { + if name != "attachments" { + warn!( + name, + "Malformed request: 'attachments' text field expected." + ); + return Err(BackupError::BadRequest); + } + + attachments.lines().map(ToString::to_string).collect() + } + Ok(None) => Vec::new(), + Err(_) => return Err(BackupError::BadRequest), + }; + + let mut attachments = Vec::new(); + let mut attachments_revokes = Vec::new(); + for attachment_hash in attachments_hashes { + let (attachment, revoke) = + create_attachment_holder(&attachment_hash, blob_client).await?; + attachments.push(attachment); + attachments_revokes.push(revoke); + } + + Ok((attachments, attachments_revokes)) +} + +#[instrument(skip_all)] +pub async fn get_siwe_backup_msg( + multipart: &mut actix_multipart::Multipart, +) -> actix_web::Result> { + Ok( + get_text_field(multipart) + .await? + .filter(|(name, _)| name == "siwe_backup_msg") + .map(|(_, siwe_backup_msg)| siwe_backup_msg), + ) } #[instrument(skip_all, fields(backup_id = %path))] diff --git a/services/backup/src/http/mod.rs b/services/backup/src/http/mod.rs --- a/services/backup/src/http/mod.rs +++ b/services/backup/src/http/mod.rs @@ -38,7 +38,7 @@ .app_data(auth_service.to_owned()) .route("/health", web::get().to(HttpResponse::Ok)) .service( - // Backup services that don't require authetication + // Backup services that don't require authentication web::scope("/backups/latest") .service( web::resource("{user_identifier}/backup_info") @@ -49,9 +49,14 @@ )), ) .service( - // Backup services requiring authetication + // Backup services requiring authentication web::scope("/backups") .wrap(get_comm_authentication_middleware()) + // Uploads backup data from multipart form data. + // This function requires both User Keys and User Data form fields + // in order to proceed with the upload. + // If either User Keys or User Data is not present in the form data, + // the upload will fail. .service( web::resource("").route(web::post().to(handlers::backup::upload)), )