fix: generate full url for attachment download

This commit is contained in:
qaz741wsd856 2025-12-07 14:50:44 +00:00
parent 28c791337a
commit 4930e747ed
3 changed files with 68 additions and 21 deletions

View file

@ -4,7 +4,7 @@ use axum::{
extract::{Multipart, Path, Query, State},
http::{header, StatusCode},
response::Response,
Json,
Extension, Json,
};
use chrono::Utc;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
@ -25,6 +25,7 @@ use crate::{
attachment::{AttachmentDB, AttachmentResponse},
cipher::{Cipher, CipherDBModel},
},
BaseUrl,
};
const ATTACHMENTS_BUCKET: &str = "ATTACHMENTS_BUCKET";
@ -104,6 +105,7 @@ async fn touch_cipher_updated_at(db: &D1Database, cipher_id: &str) -> Result<(),
pub async fn create_attachment_v2(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path(cipher_id): Path<String>,
Json(payload): Json<AttachmentCreateRequest>,
) -> Result<Json<AttachmentUploadResponse>, AppError> {
@ -158,7 +160,7 @@ pub async fn create_attachment_v2(
// Return upload URL pointing to local upload endpoint
let url = upload_url(&cipher_id, &attachment_id);
let mut cipher_response: Cipher = cipher.into();
hydrate_cipher_attachments(&db, &env, &mut cipher_response, &claims.sub).await?;
hydrate_cipher_attachments(&db, &env, &base_url, &mut cipher_response, &claims.sub).await?;
touch_cipher_updated_at(&db, &cipher_id).await?;
db::touch_user_updated_at(&db, &claims.sub).await?;
@ -253,6 +255,7 @@ pub async fn upload_attachment_v2_data(
pub async fn upload_attachment_legacy(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path(cipher_id): Path<String>,
mut multipart: Multipart,
) -> Result<Json<Cipher>, AppError> {
@ -309,7 +312,7 @@ pub async fn upload_attachment_legacy(
// 返回最新的 Cipher含附件
let mut cipher_response: Cipher = cipher.into();
hydrate_cipher_attachments(&db, &env, &mut cipher_response, &claims.sub).await?;
hydrate_cipher_attachments(&db, &env, &base_url, &mut cipher_response, &claims.sub).await?;
Ok(Json(cipher_response))
}
@ -319,6 +322,7 @@ pub async fn upload_attachment_legacy(
pub async fn get_attachment(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path((cipher_id, attachment_id)): Path<(String, String)>,
) -> Result<Json<AttachmentResponse>, AppError> {
let _bucket = require_bucket(&env)?;
@ -333,7 +337,7 @@ pub async fn get_attachment(
));
}
let url = download_url(&env, &cipher_id, &attachment_id, &claims.sub)?;
let url = download_url(&env, &base_url, &cipher_id, &attachment_id, &claims.sub)?;
Ok(Json(attachment.to_response(url)))
}
@ -449,6 +453,7 @@ pub async fn download_attachment(
pub async fn hydrate_cipher_attachments(
db: &D1Database,
env: &Env,
base_url: &str,
cipher: &mut Cipher,
user_id: &str,
) -> Result<(), AppError> {
@ -457,7 +462,7 @@ pub async fn hydrate_cipher_attachments(
return Ok(());
}
let mut map = load_attachment_map(db, env, &[cipher.id.clone()], user_id).await?;
let mut map = load_attachment_map(db, env, base_url, &[cipher.id.clone()], user_id).await?;
if let Some(list) = map.remove(&cipher.id) {
if !list.is_empty() {
cipher.attachments = Some(list);
@ -470,6 +475,7 @@ pub async fn hydrate_cipher_attachments(
pub async fn hydrate_ciphers_attachments(
db: &D1Database,
env: &Env,
base_url: &str,
ciphers: &mut [Cipher],
user_id: &str,
) -> Result<(), AppError> {
@ -481,7 +487,7 @@ pub async fn hydrate_ciphers_attachments(
}
let ids: Vec<String> = ciphers.iter().map(|c| c.id.clone()).collect();
let mut map = load_attachment_map(db, env, &ids, user_id).await?;
let mut map = load_attachment_map(db, env, base_url, &ids, user_id).await?;
for cipher in ciphers.iter_mut() {
if let Some(list) = map.remove(&cipher.id) {
@ -509,13 +515,15 @@ fn upload_url(cipher_id: &str, attachment_id: &str) -> String {
fn download_url(
env: &Env,
base_url: &str,
cipher_id: &str,
attachment_id: &str,
user_id: &str,
) -> Result<String, AppError> {
let token = build_download_token(env, user_id, cipher_id, attachment_id)?;
let normalized_base = base_url.trim_end_matches('/');
Ok(format!(
"/api/ciphers/{cipher_id}/attachment/{attachment_id}/download?token={token}"
"{normalized_base}/api/ciphers/{cipher_id}/attachment/{attachment_id}/download?token={token}"
))
}
@ -562,6 +570,7 @@ async fn fetch_attachment(db: &D1Database, attachment_id: &str) -> Result<Attach
async fn load_attachment_map(
db: &D1Database,
env: &Env,
base_url: &str,
cipher_ids: &[String],
user_id: &str,
) -> Result<HashMap<String, Vec<AttachmentResponse>>, AppError> {
@ -583,7 +592,13 @@ async fn load_attachment_map(
let mut map: HashMap<String, Vec<AttachmentResponse>> = HashMap::new();
for attachment in attachments {
let url = download_url(env, &attachment.cipher_id, &attachment.id, user_id)?;
let url = download_url(
env,
base_url,
&attachment.cipher_id,
&attachment.id,
user_id,
)?;
map.entry(attachment.cipher_id.clone())
.or_default()
.push(attachment.to_response(url));

View file

@ -1,5 +1,5 @@
use super::get_batch_size;
use axum::{extract::State, Json};
use axum::{extract::State, Extension, Json};
use chrono::{DateTime, Utc};
use log; // Used for warning logs on parse failures
use serde::Deserialize;
@ -17,6 +17,7 @@ use crate::models::cipher::{
MoveCipherData, PartialCipherData,
};
use crate::models::user::{PasswordOrOtpData, User};
use crate::BaseUrl;
use axum::extract::Path;
/// Helper to fetch a cipher by id for a user or return NotFound.
@ -37,6 +38,7 @@ async fn fetch_cipher_for_user(
pub async fn create_cipher(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Json(payload): Json<CreateCipherRequest>,
) -> Result<Json<Cipher>, AppError> {
let db = db::get_db(&env)?;
@ -95,7 +97,8 @@ pub async fn create_cipher(
.run()
.await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &mut cipher, &claims.sub).await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &base_url, &mut cipher, &claims.sub)
.await?;
db::touch_user_updated_at(&db, &claims.sub).await?;
Ok(Json(cipher))
@ -105,6 +108,7 @@ pub async fn create_cipher(
pub async fn update_cipher(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path(id): Path<String>,
Json(payload): Json<CipherRequestData>,
) -> Result<Json<Cipher>, AppError> {
@ -206,7 +210,8 @@ pub async fn update_cipher(
.run()
.await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &mut cipher, &claims.sub).await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &base_url, &mut cipher, &claims.sub)
.await?;
db::touch_user_updated_at(&db, &claims.sub).await?;
Ok(Json(cipher))
@ -217,6 +222,7 @@ pub async fn update_cipher(
pub async fn list_ciphers(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
) -> Result<Json<CipherListResponse>, AppError> {
let db = db::get_db(&env)?;
@ -233,7 +239,14 @@ pub async fn list_ciphers(
let mut ciphers: Vec<Cipher> = ciphers_db.into_iter().map(|c| c.into()).collect();
attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut ciphers, &claims.sub).await?;
attachments::hydrate_ciphers_attachments(
&db,
env.as_ref(),
&base_url,
&mut ciphers,
&claims.sub,
)
.await?;
Ok(Json(CipherListResponse {
data: ciphers,
@ -247,13 +260,15 @@ pub async fn list_ciphers(
pub async fn get_cipher(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path(id): Path<String>,
) -> Result<Json<Cipher>, AppError> {
let db = db::get_db(&env)?;
let cipher = fetch_cipher_for_user(&db, &id, &claims.sub).await?;
let mut cipher: Cipher = cipher.into();
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &mut cipher, &claims.sub).await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &base_url, &mut cipher, &claims.sub)
.await?;
Ok(Json(cipher))
}
@ -263,9 +278,10 @@ pub async fn get_cipher(
pub async fn get_cipher_details(
claims: Claims,
state: State<Arc<Env>>,
extension: Extension<BaseUrl>,
id: Path<String>,
) -> Result<Json<Cipher>, AppError> {
get_cipher(claims, state, id).await
get_cipher(claims, state, extension, id).await
}
/// PUT/POST /api/ciphers/{id}/partial
@ -273,6 +289,7 @@ pub async fn get_cipher_details(
pub async fn update_cipher_partial(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path(id): Path<String>,
Json(payload): Json<PartialCipherData>,
) -> Result<Json<Cipher>, AppError> {
@ -317,7 +334,8 @@ pub async fn update_cipher_partial(
let cipher = fetch_cipher_for_user(&db, &id, user_id).await?;
let mut cipher: Cipher = cipher.into();
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &mut cipher, &claims.sub).await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &base_url, &mut cipher, &claims.sub)
.await?;
Ok(Json(cipher))
}
@ -449,6 +467,7 @@ pub async fn hard_delete_ciphers_bulk(
pub async fn restore_cipher(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Path(id): Path<String>,
) -> Result<Json<Cipher>, AppError> {
let db = db::get_db(&env)?;
@ -479,7 +498,8 @@ pub async fn restore_cipher(
.ok_or(AppError::NotFound("Cipher not found".to_string()))?;
let mut cipher: Cipher = cipher_db.into();
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &mut cipher, &claims.sub).await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &base_url, &mut cipher, &claims.sub)
.await?;
db::touch_user_updated_at(&db, &claims.sub).await?;
@ -500,6 +520,7 @@ pub struct BulkRestoreResponse {
pub async fn restore_ciphers_bulk(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Json(payload): Json<CipherIdsData>,
) -> Result<Json<BulkRestoreResponse>, AppError> {
let db = db::get_db(&env)?;
@ -546,8 +567,14 @@ pub async fn restore_ciphers_bulk(
.map(|cipher| cipher.into())
.collect();
attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut restored_ciphers, &claims.sub)
.await?;
attachments::hydrate_ciphers_attachments(
&db,
env.as_ref(),
&base_url,
&mut restored_ciphers,
&claims.sub,
)
.await?;
db::touch_user_updated_at(&db, &claims.sub).await?;
@ -565,6 +592,7 @@ pub async fn restore_ciphers_bulk(
pub async fn create_cipher_simple(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
Json(payload): Json<CipherRequestData>,
) -> Result<Json<Cipher>, AppError> {
let db = db::get_db(&env)?;
@ -617,7 +645,8 @@ pub async fn create_cipher_simple(
.run()
.await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &mut cipher, &claims.sub).await?;
attachments::hydrate_cipher_attachments(&db, env.as_ref(), &base_url, &mut cipher, &claims.sub)
.await?;
db::touch_user_updated_at(&db, &claims.sub).await?;
Ok(Json(cipher))

View file

@ -1,4 +1,4 @@
use axum::{extract::State, Json};
use axum::{extract::State, Extension, Json};
use serde_json::Value;
use std::sync::Arc;
use worker::Env;
@ -14,12 +14,14 @@ use crate::{
sync::{Profile, SyncResponse},
user::User,
},
BaseUrl,
};
#[worker::send]
pub async fn get_sync_data(
claims: Claims,
State(env): State<Arc<Env>>,
Extension(BaseUrl(base_url)): Extension<BaseUrl>,
) -> Result<Json<SyncResponse>, AppError> {
let user_id = claims.sub;
let db = db::get_db(&env)?;
@ -65,7 +67,8 @@ pub async fn get_sync_data(
.collect::<Vec<Cipher>>();
let mut ciphers = ciphers;
attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut ciphers, &user_id).await?;
attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &base_url, &mut ciphers, &user_id)
.await?;
let profile = Profile::from_user(user)?;