Thumbnail upload PUT sends the signed Content-MD5 (closes #238)
check / check (push) Successful in 5m34s
check / check (push) Successful in 5m34s
`/files/upload-url` signs the content MD5 into the presigned URL, so storage refused the PUT that `putFile` sent without it. `putFile` now takes the MD5 given to `getUploadURL` and sends it. The thumbnail test's fake storage refuses a PUT without the signed header. Model: opus-5-5
This commit was merged in pull request #249.
This commit is contained in:
1 parent
7878ff3812
commit
1bf18baf00
5 files changed
+40
-9
No files matched your search
+10
-4
@@ -780,7 +780,7 @@ describe("ApiClient timeouts", () => {
|
||||
|
||||
await client.getJSON("/a");
|
||||
await client.postJSON("/b", {});
|
||||
await client.putFile("https://s3.example/x", new Uint8Array([1]));
|
||||
await client.putFile("https://s3.example/x", new Uint8Array([1]), "m");
|
||||
await client.putJSON("/c", {});
|
||||
|
||||
for (const call of calls) {
|
||||
@@ -825,7 +825,8 @@ describe("ApiClient timeouts", () => {
|
||||
[
|
||||
"putFile",
|
||||
() => new Response(null, { status: 200 }),
|
||||
(c) => c.putFile("https://s3.example/x", new Uint8Array([1])),
|
||||
(c) =>
|
||||
c.putFile("https://s3.example/x", new Uint8Array([1]), "m"),
|
||||
],
|
||||
[
|
||||
"getFileStream",
|
||||
@@ -1049,7 +1050,7 @@ describe("ApiClient error typing", () => {
|
||||
const client = new ApiClient({ fetch, retry: noWait });
|
||||
|
||||
const err: unknown = await client
|
||||
.putFile("https://s3.example/obj", new Uint8Array([1, 2]))
|
||||
.putFile("https://s3.example/obj", new Uint8Array([1, 2]), "m")
|
||||
.catch((e: unknown) => e);
|
||||
|
||||
expect(err).toBeInstanceOf(ApiError);
|
||||
@@ -1067,7 +1068,11 @@ describe("ApiClient error typing", () => {
|
||||
);
|
||||
const client = new ApiClient({ fetch, retry: noWait });
|
||||
|
||||
await client.putFile("https://s3.example/obj", new Uint8Array([1, 2]));
|
||||
await client.putFile(
|
||||
"https://s3.example/obj",
|
||||
new Uint8Array([1, 2]),
|
||||
"m",
|
||||
);
|
||||
expect(calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -1277,6 +1282,7 @@ describe("ApiClient onRequest", () => {
|
||||
await client.putFile(
|
||||
"https://s3.example/obj?X-Amz-Signature=abc",
|
||||
new Uint8Array([1, 2]),
|
||||
"m",
|
||||
);
|
||||
|
||||
expect(requests).toEqual([
|
||||
|
||||
+13
-2
@@ -80,13 +80,20 @@ describe("ApiClient.putFile", () => {
|
||||
const client = new ApiClient({ fetch, authToken: "secret-tok" });
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
await client.putFile("https://s3.example.com/presigned?sig=abc", data);
|
||||
await client.putFile(
|
||||
"https://s3.example.com/presigned?sig=abc",
|
||||
data,
|
||||
"md5base64==",
|
||||
);
|
||||
|
||||
expect(calls[0]!.url).toBe("https://s3.example.com/presigned?sig=abc");
|
||||
expect(calls[0]!.init?.method).toBe("PUT");
|
||||
const headers = new Headers(calls[0]!.init?.headers as HeadersInit);
|
||||
expect(headers.get("Content-Type")).toBe("application/octet-stream");
|
||||
expect(headers.get("Content-Length")).toBe("5");
|
||||
// The server signs the MD5 into the URL; storage refuses the PUT
|
||||
// without it.
|
||||
expect(headers.get("Content-MD5")).toBe("md5base64==");
|
||||
// Must NOT leak auth headers to S3
|
||||
expect(headers.has("X-Auth-Token")).toBe(false);
|
||||
expect(headers.has("X-Client-Package")).toBe(false);
|
||||
@@ -99,7 +106,11 @@ describe("ApiClient.putFile", () => {
|
||||
const client = new ApiClient({ fetch });
|
||||
|
||||
await expect(
|
||||
client.putFile("https://s3.example.com/bad", new Uint8Array(10)),
|
||||
client.putFile(
|
||||
"https://s3.example.com/bad",
|
||||
new Uint8Array(10),
|
||||
"md5base64==",
|
||||
),
|
||||
).rejects.toThrow(/403/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user