diff --git a/apps/labrinth/src/file_hosting/mock.rs b/apps/labrinth/src/file_hosting/mock.rs index 58cf63e6f..851da4344 100644 --- a/apps/labrinth/src/file_hosting/mock.rs +++ b/apps/labrinth/src/file_hosting/mock.rs @@ -8,7 +8,6 @@ use chrono::Utc; use hex::ToHex; use sha2::Digest; use std::path::PathBuf; -use std::time::Duration; #[derive(Default)] pub struct MockHost(()); @@ -51,7 +50,7 @@ impl FileHost for MockHost { async fn get_url_for_private_file( &self, file_name: &str, - _expiry: Duration, + _expiry_secs: u32, ) -> Result { let cdn_url = dotenvy::var("CDN_URL").unwrap(); Ok(format!("{cdn_url}/private/{file_name}")) diff --git a/apps/labrinth/src/file_hosting/mod.rs b/apps/labrinth/src/file_hosting/mod.rs index 0013e753c..c13816b95 100644 --- a/apps/labrinth/src/file_hosting/mod.rs +++ b/apps/labrinth/src/file_hosting/mod.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use std::time::Duration; use thiserror::Error; mod mock; @@ -55,7 +54,7 @@ pub trait FileHost { async fn get_url_for_private_file( &self, file_name: &str, - expiry: Duration, + expiry_secs: u32, ) -> Result; async fn delete_file( diff --git a/apps/labrinth/src/file_hosting/s3_host.rs b/apps/labrinth/src/file_hosting/s3_host.rs index e80957c78..15e2d11ed 100644 --- a/apps/labrinth/src/file_hosting/s3_host.rs +++ b/apps/labrinth/src/file_hosting/s3_host.rs @@ -10,7 +10,6 @@ use s3::bucket::Bucket; use s3::creds::Credentials; use s3::region::Region; use sha2::Digest; -use std::time::Duration; pub struct S3Host { public_bucket: Bucket, @@ -114,15 +113,11 @@ impl FileHost for S3Host { async fn get_url_for_private_file( &self, file_name: &str, - expiry: Duration, + expiry_secs: u32, ) -> Result { let url = self .private_bucket - .presign_get( - format!("/{file_name}"), - expiry.as_secs().try_into().unwrap(), - None, - ) + .presign_get(format!("/{file_name}"), expiry_secs, None) .await .map_err(|e| { FileHostingError::S3Error("generating presigned URL", e) diff --git a/apps/labrinth/src/routes/v3/shared_instance_version_creation.rs b/apps/labrinth/src/routes/v3/shared_instance_version_creation.rs index 471240778..aaf35b6fb 100644 --- a/apps/labrinth/src/routes/v3/shared_instance_version_creation.rs +++ b/apps/labrinth/src/routes/v3/shared_instance_version_creation.rs @@ -158,8 +158,7 @@ async fn shared_instance_version_create_inner( let file_data = file_data.freeze(); let file_path = format!( - "shared_instance/{}/{}.mrpack", - SharedInstanceId::from(instance_id), + "shared_instance/{}.mrpack", SharedInstanceVersionId::from(version_id), ); @@ -167,7 +166,7 @@ async fn shared_instance_version_create_inner( .upload_file( MRPACK_MIME_TYPE, &file_path, - FileHostPublicity::Public, // TODO + FileHostPublicity::Private, file_data, ) .await?; diff --git a/apps/labrinth/src/routes/v3/shared_instances.rs b/apps/labrinth/src/routes/v3/shared_instances.rs index 01cf3d4da..fd023abe8 100644 --- a/apps/labrinth/src/routes/v3/shared_instances.rs +++ b/apps/labrinth/src/routes/v3/shared_instances.rs @@ -7,6 +7,7 @@ use crate::database::models::{ DBSharedInstanceId, DBSharedInstanceVersionId, generate_shared_instance_id, }; use crate::database::redis::RedisPool; +use crate::file_hosting::FileHost; use crate::models::ids::{SharedInstanceId, SharedInstanceVersionId}; use crate::models::pats::Scopes; use crate::models::shared_instances::{ @@ -16,11 +17,12 @@ use crate::models::users::User; use crate::queue::session::AuthQueue; use crate::routes::ApiError; use crate::util::routes::read_typed_from_payload; -use actix_web::web::Data; +use actix_web::web::{Data, Redirect}; use actix_web::{HttpRequest, HttpResponse, web}; use futures_util::future::try_join_all; use serde::Deserialize; use sqlx::PgPool; +use std::sync::Arc; use validator::Validate; pub fn config(cfg: &mut web::ServiceConfig) { @@ -36,7 +38,11 @@ pub fn config(cfg: &mut web::ServiceConfig) { cfg.service( web::scope("shared-instance-version") .route("{id}", web::get().to(shared_instance_version_get)) - .route("{id}", web::delete().to(shared_instance_version_delete)), + .route("{id}", web::delete().to(shared_instance_version_delete)) + .route( + "{id}/download", + web::get().to(shared_instance_version_download), + ), ); } @@ -554,3 +560,53 @@ async fn delete_instance_version( transaction.commit().await?; Ok(()) } + +pub async fn shared_instance_version_download( + req: HttpRequest, + pool: Data, + redis: Data, + file_host: Data>, + info: web::Path<(SharedInstanceVersionId,)>, + session_queue: Data, +) -> Result { + let version_id = info.into_inner().0.into(); + + let user = get_maybe_user_from_headers( + &req, + &**pool, + &redis, + &session_queue, + Scopes::SHARED_INSTANCE_VERSION_READ, + ) + .await? + .map(|(_, user)| user); + + let version = DBSharedInstanceVersion::get(version_id, &**pool).await?; + + if let Some(version) = version { + let instance = + DBSharedInstance::get(version.shared_instance_id, &**pool).await?; + if let Some(instance) = instance { + if !can_access_instance_as_maybe_user( + &pool, &redis, &instance, user, + ) + .await? + { + return Err(ApiError::NotFound); + } + + let file_name = format!( + "shared_instance/{}.mrpack", + SharedInstanceVersionId::from(version_id) + ); + let url = + file_host.get_url_for_private_file(&file_name, 60).await?; + + Ok(Redirect::to(url).see_other()) + } else { + Err(ApiError::NotFound) + } + } else { + Err(ApiError::NotFound) + } +}