Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ packages/**/esm/
packages/**/cjs/
**/.DS_Store
.vscode
.kiro
.idea
*.log
.npm/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { getHashedPayload } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload';
import { UNSIGNED_PAYLOAD } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/constants';

describe('getHashedPayload', () => {
test('returns empty hash if nullish', () => {
Expand Down Expand Up @@ -34,4 +35,10 @@ describe('getHashedPayload', () => {
expect(getHashedPayload(scalar as any)).toStrictEqual('UNSIGNED-PAYLOAD');
}
});

test('returns UNSIGNED_PAYLOAD constant without hashing when passed as body', () => {
expect(getHashedPayload(UNSIGNED_PAYLOAD as any)).toStrictEqual(
'UNSIGNED-PAYLOAD',
);
});
});
5 changes: 4 additions & 1 deletion packages/core/src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export {
PresignUrlOptions,
SignRequestOptions,
} from './middleware/signing/signer/signatureV4';
export { EMPTY_HASH as EMPTY_SHA256_HASH } from './middleware/signing/signer/signatureV4/constants';
export {
EMPTY_HASH as EMPTY_SHA256_HASH,
UNSIGNED_PAYLOAD,
} from './middleware/signing/signer/signatureV4/constants';
export { extendedEncodeURIComponent } from './middleware/signing/utils/extendedEncodeURIComponent';
export {
signingMiddlewareFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export const getHashedPayload = (body: HttpRequest['body']): string => {
};

const isSourceData = (body: HttpRequest['body']): body is SourceData =>
typeof body === 'string' || ArrayBuffer.isView(body) || isArrayBuffer(body);
// Exclude UNSIGNED_PAYLOAD constant to prevent it from being hashed as a string
body !== UNSIGNED_PAYLOAD &&
(typeof body === 'string' || ArrayBuffer.isView(body) || isArrayBuffer(body));

const isArrayBuffer = (arg: any): arg is ArrayBuffer =>
(typeof ArrayBuffer === 'function' && arg instanceof ArrayBuffer) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { presignUrl } from '@aws-amplify/core/internals/aws-client-utils';
import {
UNSIGNED_PAYLOAD,
presignUrl,
} from '@aws-amplify/core/internals/aws-client-utils';

import { getPresignedGetObjectUrl } from '../../../../../../src/providers/s3/utils/client/s3data';

Expand Down Expand Up @@ -42,9 +45,6 @@ describe('serializeGetObjectRequest', () => {
);
expect(actualUrl.pathname).toEqual('/key');
expect(actualUrl.searchParams.get('X-Amz-Expires')).toEqual('900');
expect(actualUrl.searchParams.get('x-amz-content-sha256')).toEqual(
expect.any(String),
);
expect(actualUrl.searchParams.get('x-amz-user-agent')).toEqual('UA');
});

Expand Down Expand Up @@ -99,9 +99,6 @@ describe('serializeGetObjectRequest', () => {
);

expect(actual.searchParams.get('X-Amz-Expires')).toBe('900');
expect(actual.searchParams.get('x-amz-content-sha256')).toEqual(
expect.any(String),
);
expect(actual.searchParams.get('response-content-disposition')).toBe(
'attachment; filename="filename.jpg"',
);
Expand All @@ -110,4 +107,32 @@ describe('serializeGetObjectRequest', () => {
);
expect(actual.searchParams.get('x-amz-user-agent')).toBe('UA');
});

it('should use UNSIGNED-PAYLOAD for presigned URLs', async () => {
mockPresignUrl.mockClear();

const result = await getPresignedGetObjectUrl(
{
...defaultConfigWithStaticCredentials,
signingRegion: defaultConfigWithStaticCredentials.region,
signingService: 's3',
expiration: 900,
},
{
Bucket: 'bucket',
Key: 'key',
},
);

// Verify UNSIGNED-PAYLOAD is used in the signature calculation
expect(mockPresignUrl).toHaveBeenCalledWith(
expect.objectContaining({
body: UNSIGNED_PAYLOAD,
}),
expect.anything(),
);

// Verify x-amz-content-sha256 is NOT in the presigned URL query parameters
expect(result.searchParams.get('x-amz-content-sha256')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// SPDX-License-Identifier: Apache-2.0

import {
EMPTY_SHA256_HASH,
Endpoint,
HttpRequest,
HttpResponse,
PresignUrlOptions,
UNSIGNED_PAYLOAD,
UserAgentOptions,
parseMetadata,
presignUrl,
Expand All @@ -18,7 +18,6 @@ import {
} from '@aws-amplify/core/internals/utils';

import {
CONTENT_SHA256_HEADER,
assignStringVariables,
buildStorageServiceError,
deserializeBoolean,
Expand Down Expand Up @@ -172,10 +171,6 @@ export const getPresignedGetObjectUrl = async (
const endpoint = defaultConfig.endpointResolver(config, input);
const { url, headers, method } = await getObjectSerializer(input, endpoint);

// TODO: set content sha256 query parameter with value of UNSIGNED-PAYLOAD instead of empty hash.
// It requires changes in presignUrl. Without this change, the generated url still works,
// but not the same as other tools like AWS SDK and CLI.
url.searchParams.append(CONTENT_SHA256_HEADER, EMPTY_SHA256_HASH);
if (config.userAgentValue) {
url.searchParams.append(
config.userAgentHeader ?? USER_AGENT_HEADER,
Expand All @@ -199,7 +194,7 @@ export const getPresignedGetObjectUrl = async (
}

return presignUrl(
{ method, url, body: undefined },
{ method, url, body: UNSIGNED_PAYLOAD },
{
signingService: defaultConfig.service,
signingRegion: config.region,
Expand Down
Loading