diff --git a/services/backup/Cargo.lock b/services/backup/Cargo.lock --- a/services/backup/Cargo.lock +++ b/services/backup/Cargo.lock @@ -761,6 +761,8 @@ "once_cell", "rand", "reqwest", + "serde", + "serde_json", "tokio", "tokio-stream", "tonic-build", diff --git a/services/backup/Cargo.toml b/services/backup/Cargo.toml --- a/services/backup/Cargo.toml +++ b/services/backup/Cargo.toml @@ -31,6 +31,8 @@ reqwest = "0.11.18" derive_more = "0.99" actix-multipart = "0.6" +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } [build-dependencies] tonic-build = "0.8" diff --git a/services/backup/src/database/mod.rs b/services/backup/src/database/mod.rs --- a/services/backup/src/database/mod.rs +++ b/services/backup/src/database/mod.rs @@ -72,7 +72,7 @@ ), ]); - match self + let output = self .client .get_item() .table_name(BACKUP_TABLE_NAME) @@ -82,15 +82,16 @@ .map_err(|e| { error!("DynamoDB client failed to find backup item"); Error::AwsSdk(e.into()) - })? { - GetItemOutput { - item: Some(item), .. - } => { - let backup_item = item.try_into()?; - Ok(Some(backup_item)) - } - _ => Ok(None), - } + })?; + + let GetItemOutput { + item: Some(item), .. + } = output else { + return Ok(None) + }; + + let backup_item = item.try_into()?; + Ok(Some(backup_item)) } pub async fn find_last_backup_item( 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 @@ -1,6 +1,6 @@ use actix_web::{ error::{ - ErrorBadRequest, ErrorConflict, ErrorInternalServerError, + ErrorBadRequest, ErrorConflict, ErrorInternalServerError, ErrorNotFound, ErrorServiceUnavailable, HttpError, }, HttpResponse, ResponseError, @@ -15,6 +15,7 @@ Debug, derive_more::Display, derive_more::From, derive_more::Error, )] pub enum BackupError { + NoBackup, BlobError(BlobServiceError), DB(comm_services_lib::database::Error), } @@ -23,6 +24,7 @@ fn from(value: &BackupError) -> Self { trace!("Handling backup service error: {value}"); match value { + BackupError::NoBackup => ErrorNotFound("not found"), BackupError::BlobError( err @ (BlobServiceError::ClientError(_) | BlobServiceError::UnexpectedHttpStatus(_) 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 @@ -144,3 +144,66 @@ Ok(blob_info) } + +#[instrument(name = "download_user_keys", skip_all, fields(backup_id = %path.as_str()))] +pub async fn download_user_keys( + user: UserIdentity, + path: web::Path, + blob_client: web::Data, + db_client: web::Data, +) -> actix_web::Result { + info!("Download user keys request"); + let backup_id = path.into_inner(); + download_user_blob( + |item| &item.user_keys, + &user.user_id, + &backup_id, + blob_client, + db_client, + ) + .await +} + +#[instrument(name = "download_user_data", skip_all, fields(backup_id = %path.as_str()))] +pub async fn download_user_data( + user: UserIdentity, + path: web::Path, + blob_client: web::Data, + db_client: web::Data, +) -> actix_web::Result { + info!("Download user data request"); + let backup_id = path.into_inner(); + download_user_blob( + |item| &item.user_data, + &user.user_id, + &backup_id, + blob_client, + db_client, + ) + .await +} + +pub async fn download_user_blob( + data_extractor: impl FnOnce(&BackupItem) -> &BlobInfo, + user_id: &str, + backup_id: &str, + blob_client: web::Data, + db_client: web::Data, +) -> actix_web::Result { + let backup_item = db_client + .find_backup_item(user_id, backup_id) + .await + .map_err(BackupError::from)? + .ok_or(BackupError::NoBackup)?; + + let stream = blob_client + .get(&data_extractor(&backup_item).blob_hash) + .await + .map_err(BackupError::from)?; + + Ok( + HttpResponse::Ok() + .content_type("application/octet-stream") + .streaming(stream), + ) +} 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 @@ -25,8 +25,6 @@ let blob = web::Data::new(blob_client); HttpServer::new(move || { - let auth_middleware = get_comm_authentication_middleware(); - App::new() .wrap(tracing_actix_web::TracingLogger::default()) .wrap(comm_services_lib::http::cors_config( @@ -35,9 +33,20 @@ .app_data(db.clone()) .app_data(blob.clone()) .service( - web::resource("/backups") - .route(web::post().to(handlers::backup::upload)) - .wrap(auth_middleware), + // Services requiring authetication + web::scope("/backups") + .wrap(get_comm_authentication_middleware()) + .service( + web::resource("").route(web::post().to(handlers::backup::upload)), + ) + .service( + web::resource("{backup_id}/user_keys") + .route(web::get().to(handlers::backup::download_user_keys)), + ) + .service( + web::resource("{backup_id}/user_data") + .route(web::get().to(handlers::backup::download_user_data)), + ), ) }) .bind(("0.0.0.0", CONFIG.http_port))?