mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-07-22 12:21:03 +02:00
fix: properly handle x-amz-meta-updated-at
This commit is contained in:
@@ -329,7 +329,16 @@ export class SyncService implements OnModuleInit {
|
||||
Metadata: metadata,
|
||||
});
|
||||
|
||||
const url = await getSignedUrl(this.s3Client, command, { expiresIn });
|
||||
const metadataHeaders = new Set(
|
||||
Object.keys(metadata ?? {}).map((name) => `x-amz-meta-${name}`),
|
||||
);
|
||||
const url = await getSignedUrl(this.s3Client, command, {
|
||||
expiresIn,
|
||||
// The AWS presigner otherwise hoists user metadata into the query string.
|
||||
// The client echoes the response metadata as headers, so those headers
|
||||
// must remain in the request and be covered by SignedHeaders.
|
||||
unhoistableHeaders: metadataHeaders,
|
||||
});
|
||||
|
||||
// Report profile usage after upload presign if key is under profiles/
|
||||
if (ctx.mode === "cloud" && dto.key.startsWith("profiles/")) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
interface PresignResponse {
|
||||
url: string;
|
||||
expiresAt: string;
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface ListResponse {
|
||||
@@ -34,6 +35,7 @@ interface StatResponse {
|
||||
exists: boolean;
|
||||
size?: number;
|
||||
lastModified?: string;
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
describe("SyncController (e2e)", () => {
|
||||
@@ -112,6 +114,65 @@ describe("SyncController (e2e)", () => {
|
||||
expect(body.url).toContain("test/upload-key.txt");
|
||||
expect(body.expiresAt).toBeDefined();
|
||||
});
|
||||
|
||||
it("should sign and persist echoed object metadata", async () => {
|
||||
const testKey = `vpns/metadata-${Date.now()}.json`;
|
||||
const updatedAt = Math.floor(Date.now() / 1000).toString();
|
||||
|
||||
try {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/v1/objects/presign-upload")
|
||||
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
|
||||
.send({
|
||||
key: testKey,
|
||||
contentType: "application/json",
|
||||
metadata: {
|
||||
"updated-at": updatedAt,
|
||||
ignored: "not-allowed",
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const body = response.body as PresignResponse;
|
||||
expect(body.metadata).toEqual({ "updated-at": updatedAt });
|
||||
|
||||
const uploadUrl = new URL(body.url);
|
||||
const signedHeaders =
|
||||
uploadUrl.searchParams.get("X-Amz-SignedHeaders")?.split(";") ?? [];
|
||||
expect(signedHeaders).toContain("x-amz-meta-updated-at");
|
||||
expect(uploadUrl.searchParams.has("x-amz-meta-updated-at")).toBe(false);
|
||||
|
||||
const uploadResult = await fetch(body.url, {
|
||||
method: "PUT",
|
||||
body: "{}",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-amz-meta-updated-at": updatedAt,
|
||||
},
|
||||
});
|
||||
if (!uploadResult.ok) {
|
||||
throw new Error(
|
||||
`Metadata upload failed with status ${uploadResult.status}: ${await uploadResult.text()}`,
|
||||
);
|
||||
}
|
||||
|
||||
const statResponse = await request(app.getHttpServer())
|
||||
.post("/v1/objects/stat")
|
||||
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
|
||||
.send({ key: testKey })
|
||||
.expect(200);
|
||||
|
||||
const statBody = statResponse.body as StatResponse;
|
||||
expect(statBody.exists).toBe(true);
|
||||
expect(statBody.metadata?.["updated-at"]).toBe(updatedAt);
|
||||
} finally {
|
||||
await request(app.getHttpServer())
|
||||
.post("/v1/objects/delete")
|
||||
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
|
||||
.send({ key: testKey })
|
||||
.expect(200);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /v1/objects/presign-download", () => {
|
||||
|
||||
Reference in New Issue
Block a user