tauri-app: 8a review fixes
- fetchBlob cache keyed by (did, cid), not just cid. Security: future per-DID access control on getBlob would otherwise leak the first responder's bytes to subsequent viewers. - EmbedImage: pass did to releaseBlob, release previous cid on cid change (no leaked URLs). - ComposeBox: releaseBlob called with both did and cid. - pds-server: rename test get_blob_after_upload_with_different_did -> get_blob_returns_404_for_cross_did_cid_lookup. The docstring was misleading — the test only verifies the (did,cid) PK on the PDS row, not auth. The renamed name matches what the test actually checks. - vitest: update releaseBlob call sites to the new (did, cid) signature.
This commit is contained in:
@@ -322,4 +322,66 @@ impl PdsHttpClient {
|
||||
let bytes = resp.bytes().await?;
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
|
||||
/// `POST /xrpc/com.atproto.uploadBlob`
|
||||
///
|
||||
/// Authenticated; the server derives the DID from the JWT `sub`
|
||||
/// claim and writes the block to `(did, sha256(cid))` in
|
||||
/// `repo_blocks`. The body is the raw blob bytes and the
|
||||
/// `Content-Type` header is mandatory — the PDS uses it as the
|
||||
/// authoritative MIME type for the row.
|
||||
///
|
||||
/// Returns the parsed `com.atproto.uploadBlob` response verbatim:
|
||||
/// `{ blob: { $type, ref: { $link }, mimeType, size } }`. The
|
||||
/// caller is expected to forward this to the Svelte UI so it can
|
||||
/// drop the blob ref straight into a record's `embed.images[]`.
|
||||
pub async fn upload_blob(
|
||||
&self,
|
||||
bytes: Vec<u8>,
|
||||
content_type: &str,
|
||||
jwt: &str,
|
||||
) -> Result<UploadBlobResp> {
|
||||
let resp = self
|
||||
.client
|
||||
.post(format!("{}/xrpc/com.atproto.uploadBlob", self.base_url))
|
||||
.bearer_auth(jwt)
|
||||
.header(reqwest::header::CONTENT_TYPE, content_type)
|
||||
.body(bytes)
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("uploadBlob failed: {} {}", status, body);
|
||||
}
|
||||
Ok(resp.json::<UploadBlobResp>().await?)
|
||||
}
|
||||
}
|
||||
|
||||
/// `com.atproto.uploadBlob` response. Spec'd at
|
||||
/// <https://atproto.com/specs/blob>. The server returns a `blob`
|
||||
/// object that mirrors what an `app.bsky.embed.images#image` entry
|
||||
/// expects on the wire — the Tauri command forwards this verbatim so
|
||||
/// the UI can drop it into the post record with no further
|
||||
/// transformation.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UploadBlobResp {
|
||||
pub blob: UploadedBlob,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UploadedBlob {
|
||||
#[serde(rename = "$type")]
|
||||
pub ty: String,
|
||||
#[serde(rename = "ref")]
|
||||
pub blob_ref: UploadedBlobRef,
|
||||
#[serde(rename = "mimeType")]
|
||||
pub mime_type: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UploadedBlobRef {
|
||||
#[serde(rename = "$link")]
|
||||
pub link: String,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user