diff --git a/plugins/storage/server/api/files.ts b/plugins/storage/server/api/files.ts index 6cb0b450f..e97a95e13 100644 --- a/plugins/storage/server/api/files.ts +++ b/plugins/storage/server/api/files.ts @@ -72,34 +72,32 @@ router.get( async (ctx: APIContext) => { const actor = ctx.state.auth.user; const key = getKeyFromContext(ctx); + const forceDownload = !!ctx.input.query.download; const isSignedRequest = !!ctx.input.query.sig; const { isPublicBucket, fileName } = AttachmentHelper.parseKey(key); const skipAuthorize = isPublicBucket || isSignedRequest; const cacheHeader = "max-age=604800, immutable"; + let contentType = + (fileName ? mime.lookup(fileName) : undefined) || + "application/octet-stream"; - if (skipAuthorize) { - ctx.set("Cache-Control", cacheHeader); - ctx.set( - "Content-Type", - (fileName ? mime.lookup(fileName) : undefined) || - "application/octet-stream" - ); - ctx.attachment(fileName); - } else { + if (!skipAuthorize) { const attachment = await Attachment.findOne({ where: { key }, rejectOnEmpty: true, }); - authorize(actor, "read", attachment); - - ctx.set("Cache-Control", cacheHeader); - ctx.set("Content-Type", attachment.contentType); - ctx.attachment(attachment.name, { - type: FileStorage.getContentDisposition(attachment.contentType), - }); + contentType = attachment.contentType; } + ctx.set("Cache-Control", cacheHeader); + ctx.set("Content-Type", contentType); + ctx.attachment(fileName, { + type: forceDownload + ? "attachment" + : FileStorage.getContentDisposition(contentType), + }); + // Handle byte range requests // https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests const stats = await (FileStorage as LocalStorage).stat(key); diff --git a/plugins/storage/server/api/schema.ts b/plugins/storage/server/api/schema.ts index 2dea96d68..e0320f865 100644 --- a/plugins/storage/server/api/schema.ts +++ b/plugins/storage/server/api/schema.ts @@ -24,6 +24,7 @@ export const FilesGetSchema = z.object({ .optional() .transform((val) => (val ? ValidateKey.sanitize(val) : undefined)), sig: z.string().optional(), + download: z.string().optional(), }) .refine((obj) => !(isEmpty(obj.key) && isEmpty(obj.sig)), { message: "One of key or sig is required",