Use private bucket for shared instance versions

This commit is contained in:
Josiah Glosson 2025-06-12 15:38:17 -05:00
parent 26364f65d6
commit e8cb2abec0
5 changed files with 64 additions and 16 deletions

View File

@ -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<String, FileHostingError> {
let cdn_url = dotenvy::var("CDN_URL").unwrap();
Ok(format!("{cdn_url}/private/{file_name}"))

View File

@ -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<String, FileHostingError>;
async fn delete_file(

View File

@ -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<String, FileHostingError> {
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)

View File

@ -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?;

View File

@ -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<PgPool>,
redis: Data<RedisPool>,
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
info: web::Path<(SharedInstanceVersionId,)>,
session_queue: Data<AuthQueue>,
) -> Result<Redirect, ApiError> {
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)
}
}