fix: add ETag and headers to 304 Not Modified responses
Deploy FileDrop / deploy (push) Failing after 14m18s
Deploy FileDrop / deploy (push) Failing after 14m18s
AWS SDK requires ETag header in 304 responses. Without it, the SDK throws UnknownError despite receiving a valid 304 status code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -477,6 +477,7 @@ const handleGetObject = async (
|
|||||||
|
|
||||||
// H3: Conditional headers — If-Match / If-None-Match
|
// H3: Conditional headers — If-Match / If-None-Match
|
||||||
const etag = `"${file.fileHash || nanoid(16)}"`;
|
const etag = `"${file.fileHash || nanoid(16)}"`;
|
||||||
|
const lastModified = file.createdAt instanceof Date ? file.createdAt : new Date(file.createdAt);
|
||||||
const ifMatch = headers['if-match'];
|
const ifMatch = headers['if-match'];
|
||||||
if (ifMatch && ifMatch !== '*' && ifMatch !== etag) {
|
if (ifMatch && ifMatch !== '*' && ifMatch !== etag) {
|
||||||
return s3ErrorResponse(
|
return s3ErrorResponse(
|
||||||
@@ -489,22 +490,33 @@ const handleGetObject = async (
|
|||||||
}
|
}
|
||||||
const ifNoneMatch = headers['if-none-match'];
|
const ifNoneMatch = headers['if-none-match'];
|
||||||
if (ifNoneMatch && ifNoneMatch === etag) {
|
if (ifNoneMatch && ifNoneMatch === etag) {
|
||||||
return new Response(null, { status: 304, headers: s3Headers(reqId, {
|
return new Response(null, {
|
||||||
etag, 'content-type': file.mimeType, 'content-length': String(file.sizeBytes),
|
status: 304,
|
||||||
'last-modified': lastModified.toUTCString(), 'x-amz-version-id': 'null',
|
headers: s3Headers(reqId, {
|
||||||
}) });
|
etag,
|
||||||
|
'content-type': file.mimeType,
|
||||||
|
'content-length': String(file.sizeBytes),
|
||||||
|
'last-modified': lastModified.toUTCString(),
|
||||||
|
'x-amz-version-id': 'null',
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// H3: Conditional headers — If-Modified-Since / If-Unmodified-Since
|
// H3: Conditional headers — If-Modified-Since / If-Unmodified-Since
|
||||||
const lastModified = file.createdAt instanceof Date ? file.createdAt : new Date(file.createdAt);
|
|
||||||
const ifModifiedSince = headers['if-modified-since'];
|
const ifModifiedSince = headers['if-modified-since'];
|
||||||
if (ifModifiedSince) {
|
if (ifModifiedSince) {
|
||||||
const since = new Date(ifModifiedSince);
|
const since = new Date(ifModifiedSince);
|
||||||
if (!Number.isNaN(since.getTime()) && lastModified.getTime() <= since.getTime()) {
|
if (!Number.isNaN(since.getTime()) && lastModified.getTime() <= since.getTime()) {
|
||||||
return new Response(null, { status: 304, headers: s3Headers(reqId, {
|
return new Response(null, {
|
||||||
etag, 'content-type': file.mimeType, 'content-length': String(file.sizeBytes),
|
status: 304,
|
||||||
'last-modified': lastModified.toUTCString(), 'x-amz-version-id': 'null',
|
headers: s3Headers(reqId, {
|
||||||
}) });
|
etag,
|
||||||
|
'content-type': file.mimeType,
|
||||||
|
'content-length': String(file.sizeBytes),
|
||||||
|
'last-modified': lastModified.toUTCString(),
|
||||||
|
'x-amz-version-id': 'null',
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ifUnmodifiedSince = headers['if-unmodified-since'];
|
const ifUnmodifiedSince = headers['if-unmodified-since'];
|
||||||
@@ -733,6 +745,7 @@ const handleHeadObject = async (
|
|||||||
|
|
||||||
// H3: Conditional headers for HEAD — If-Match / If-None-Match
|
// H3: Conditional headers for HEAD — If-Match / If-None-Match
|
||||||
const etag = `"${file.fileHash || nanoid(16)}"`;
|
const etag = `"${file.fileHash || nanoid(16)}"`;
|
||||||
|
const lastModified = file.createdAt instanceof Date ? file.createdAt : new Date(file.createdAt);
|
||||||
const ifMatch = headers['if-match'];
|
const ifMatch = headers['if-match'];
|
||||||
if (ifMatch && ifMatch !== '*' && ifMatch !== etag) {
|
if (ifMatch && ifMatch !== '*' && ifMatch !== etag) {
|
||||||
return s3ErrorResponse(
|
return s3ErrorResponse(
|
||||||
@@ -745,22 +758,33 @@ const handleHeadObject = async (
|
|||||||
}
|
}
|
||||||
const ifNoneMatch = headers['if-none-match'];
|
const ifNoneMatch = headers['if-none-match'];
|
||||||
if (ifNoneMatch && ifNoneMatch === etag) {
|
if (ifNoneMatch && ifNoneMatch === etag) {
|
||||||
return new Response(null, { status: 304, headers: s3Headers(reqId, {
|
return new Response(null, {
|
||||||
etag, 'content-type': file.mimeType, 'content-length': String(file.sizeBytes),
|
status: 304,
|
||||||
'last-modified': lastModified.toUTCString(), 'x-amz-version-id': 'null',
|
headers: s3Headers(reqId, {
|
||||||
}) });
|
etag,
|
||||||
|
'content-type': file.mimeType,
|
||||||
|
'content-length': String(file.sizeBytes),
|
||||||
|
'last-modified': lastModified.toUTCString(),
|
||||||
|
'x-amz-version-id': 'null',
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// H3: Conditional headers for HEAD — If-Modified-Since / If-Unmodified-Since
|
// H3: Conditional headers for HEAD — If-Modified-Since / If-Unmodified-Since
|
||||||
const lastModified = file.createdAt instanceof Date ? file.createdAt : new Date(file.createdAt);
|
|
||||||
const ifModifiedSince = headers['if-modified-since'];
|
const ifModifiedSince = headers['if-modified-since'];
|
||||||
if (ifModifiedSince) {
|
if (ifModifiedSince) {
|
||||||
const since = new Date(ifModifiedSince);
|
const since = new Date(ifModifiedSince);
|
||||||
if (!Number.isNaN(since.getTime()) && lastModified.getTime() <= since.getTime()) {
|
if (!Number.isNaN(since.getTime()) && lastModified.getTime() <= since.getTime()) {
|
||||||
return new Response(null, { status: 304, headers: s3Headers(reqId, {
|
return new Response(null, {
|
||||||
etag, 'content-type': file.mimeType, 'content-length': String(file.sizeBytes),
|
status: 304,
|
||||||
'last-modified': lastModified.toUTCString(), 'x-amz-version-id': 'null',
|
headers: s3Headers(reqId, {
|
||||||
}) });
|
etag,
|
||||||
|
'content-type': file.mimeType,
|
||||||
|
'content-length': String(file.sizeBytes),
|
||||||
|
'last-modified': lastModified.toUTCString(),
|
||||||
|
'x-amz-version-id': 'null',
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ifUnmodifiedSince = headers['if-unmodified-since'];
|
const ifUnmodifiedSince = headers['if-unmodified-since'];
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ export const verifySignature = async (
|
|||||||
const hashedCanonicalRequest = await sha256Hex(canonicalRequest);
|
const hashedCanonicalRequest = await sha256Hex(canonicalRequest);
|
||||||
|
|
||||||
// M1: Fall back to Date header if x-amz-date is missing
|
// M1: Fall back to Date header if x-amz-date is missing
|
||||||
const amzDate = headers['x-amz-date'] || headers['date'] || '';
|
const amzDate = headers['x-amz-date'] || headers.date || '';
|
||||||
|
|
||||||
// H5: Validate request freshness (clock skew / replay protection)
|
// H5: Validate request freshness (clock skew / replay protection)
|
||||||
if (amzDate) {
|
if (amzDate) {
|
||||||
|
|||||||
Reference in New Issue
Block a user