mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-12 10:59:06 +02:00
* feat(attachments): forced-attachment download URL across all storage modes Follow-up to #6713 (Refs #6712). The download button consumed `download_url`, which is load-intent: the preview / inline-media paths need it to serve media inline, so a cross-origin capability previews an image/PDF in-tab and a presigned `download_url` 401s in token-mode. Add a separate, credential-free `attachment_download_url` that forces Content-Disposition: attachment in every storage mode, and point the web + desktop download buttons at it. `download_url` is left untouched for the preview / inline-media paths. Server (single-attachment endpoint only, never in list responses): - proxy: a dl=1 variant of the #6092 capability, intent folded into the signed HMAC message so a load link cannot be flipped to a forced download (or the reverse); redemption forces AttachmentContentDisposition. Load-intent signatures stay byte-identical. - presign: PresignGetWithContentDisposition(AttachmentContentDisposition). - cloudfront: SignedURLWithContentDisposition (disposition folded into the signed resource, so a client cannot strip it). Client: use-download-attachment.ts prefers the new field on web and desktop, falling back to the #6092 capability (#6713) then the cookie-gated endpoint for older servers. Optional zod/type field, parsed with the existing .loose() fallback. Follow-up to #6092 / #6713. Refs #6712. * test(attachments): expect the second presign call for attachment_download_url GetAttachmentByID now presigns the object twice in presign mode — once inline for download_url, once with a forced attachment disposition for the new attachment_download_url. Update TestGetAttachmentByID_AutoPublicEndpointReturnsPresignedDownloadURL to expect both calls and to assert the forced-attachment presigned URL. * fix(attachments): address review — omitempty, CloudFront test, mode-keyed switch Addresses @Bohan-J's review on #6756: - Add attachment_download_url `omitempty` so it stops shipping as an empty string in every list response, matching its own doc comment ("never in list responses") and the list-payload size tuned in #5999. - Add TestGetAttachmentByID_CloudFrontModeSignsForcedAttachmentDownloadURL, covering the previously-unexercised CloudFront arm: attachment_download_url is CloudFront-signed and carries response-content-disposition=attachment folded into the signed Resource, and the load-intent download_url sibling does not force an attachment. - Key the download-URL switch on the resolved mode instead of h.CFSigner != nil, so an explicit proxy/presign override takes effect even when a signer is configured; guard the CloudFront arm on a non-nil signer to keep an explicit cloudfront mode without a signer from panicking. - Reframe the download-intent domain-separation comment as deliberate future headroom rather than a current threat mitigation (flipping today's two intents is not itself exploitable). - Blank attachment_download_url in attachmentToDraftUpload alongside download_url, so a short-lived signed URL is not persisted into a draft. Refs #6756 / #6712.
18 KiB
18 KiB