From 9ef12345520edfcf8723796cee995f63155c5404 Mon Sep 17 00:00:00 2001 From: Jim Blanchard Date: Thu, 3 Oct 2024 14:43:00 -0500 Subject: [PATCH 1/2] chore: Add internal getUrl API. --- .../__tests__/internals/apis/getUrl.test.ts | 75 +++++++++++++++++++ packages/storage/src/internals/apis/getUrl.ts | 44 +++++++++++ packages/storage/src/internals/index.ts | 3 + .../storage/src/internals/types/inputs.ts | 11 +++ .../storage/src/internals/types/outputs.ts | 10 ++- .../src/providers/s3/apis/internal/getUrl.ts | 11 +-- 6 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 packages/storage/__tests__/internals/apis/getUrl.test.ts create mode 100644 packages/storage/src/internals/apis/getUrl.ts diff --git a/packages/storage/__tests__/internals/apis/getUrl.test.ts b/packages/storage/__tests__/internals/apis/getUrl.test.ts new file mode 100644 index 00000000000..5ec4909fa16 --- /dev/null +++ b/packages/storage/__tests__/internals/apis/getUrl.test.ts @@ -0,0 +1,75 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AmplifyClassV6 } from '@aws-amplify/core'; + +import { getUrl as advancedGetUrl } from '../../../src/internals'; +import { getUrl as getUrlInternal } from '../../../src/providers/s3/apis/internal/getUrl'; + +jest.mock('../../../src/providers/s3/apis/internal/getUrl'); +const mockedGetUrlInternal = jest.mocked(getUrlInternal); + +const MOCK_URL = new URL('https://s3.aws/mock-presigned-url'); +const MOCK_DATE = new Date(); +MOCK_DATE.setMonth(MOCK_DATE.getMonth() + 1); + +describe('getUrl (internal)', () => { + beforeEach(() => { + mockedGetUrlInternal.mockResolvedValue({ + url: MOCK_URL, + expiresAt: MOCK_DATE, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should pass through advanced options to the internal getUrl', async () => { + const useAccelerateEndpoint = true; + const validateObjectExistence = false; + const expiresIn = 300; // seconds + const contentDisposition = 'inline; filename="example.jpg"'; + const contentType = 'image/jpeg'; + const bucket = { bucketName: 'bucket', region: 'us-east-1' }; + const locationCredentialsProvider = async () => ({ + credentials: { + accessKeyId: 'akid', + secretAccessKey: 'secret', + sessionToken: 'token', + expiration: new Date(), + }, + }); + const result = await advancedGetUrl({ + path: 'input/path/to/mock/object', + options: { + useAccelerateEndpoint, + bucket, + validateObjectExistence, + expiresIn, + contentDisposition, + contentType, + locationCredentialsProvider, + }, + }); + expect(mockedGetUrlInternal).toHaveBeenCalledTimes(1); + expect(mockedGetUrlInternal).toHaveBeenCalledWith( + expect.any(AmplifyClassV6), + { + path: 'input/path/to/mock/object', + options: { + useAccelerateEndpoint, + bucket, + validateObjectExistence, + expiresIn, + contentDisposition, + contentType, + locationCredentialsProvider, + }, + }, + ); + expect(result).toEqual({ + url: MOCK_URL, + expiresAt: MOCK_DATE, + }); + }); +}); diff --git a/packages/storage/src/internals/apis/getUrl.ts b/packages/storage/src/internals/apis/getUrl.ts new file mode 100644 index 00000000000..01670a96215 --- /dev/null +++ b/packages/storage/src/internals/apis/getUrl.ts @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Amplify } from '@aws-amplify/core'; + +import { getUrl as getUrlInternal } from '../../providers/s3/apis/internal/getUrl'; +import { GetUrlInput } from '../types/inputs'; +import { GetUrlOutput } from '../types/outputs'; + +/** + * Get a temporary presigned URL to download the specified S3 object. + * The presigned URL expires when the associated role used to sign the request expires or + * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. + * + * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` + * to true, this method will verify the given object already exists in S3 before returning a presigned + * URL, and will throw `StorageError` if the object does not exist. + * + * @param input - The `GetUrlWithPathInput` object. + * @returns Presigned URL and timestamp when the URL may expire. + * @throws service: `S3Exception` - thrown when checking for existence of the object + * @throws validation: `StorageValidationErrorCode` - Validation errors + * thrown either username or key are not defined. + * + * @internal + */ +export function getUrl(input: GetUrlInput) { + return getUrlInternal(Amplify, { + path: input.path, + options: { + useAccelerateEndpoint: input?.options?.useAccelerateEndpoint, + bucket: input?.options?.bucket, + validateObjectExistence: input?.options?.validateObjectExistence, + expiresIn: input?.options?.expiresIn, + contentDisposition: input?.options?.contentDisposition, + contentType: input?.options?.contentType, + + // Advanced options + locationCredentialsProvider: input?.options?.locationCredentialsProvider, + }, + // Type casting is necessary because `getPropertiesInternal` supports both Gen1 and Gen2 signatures, but here + // given in input can only be Gen2 signature, the return can only ben Gen2 signature. + }) as Promise; +} diff --git a/packages/storage/src/internals/index.ts b/packages/storage/src/internals/index.ts index 6ca2bdba6dd..7b5ceaee8eb 100644 --- a/packages/storage/src/internals/index.ts +++ b/packages/storage/src/internals/index.ts @@ -12,17 +12,20 @@ export { GetDataAccessInput, ListCallerAccessGrantsInput, GetPropertiesInput, + GetUrlInput, CopyInput, } from './types/inputs'; export { GetDataAccessOutput, ListCallerAccessGrantsOutput, GetPropertiesOutput, + GetUrlOutput, } from './types/outputs'; export { getDataAccess } from './apis/getDataAccess'; export { listCallerAccessGrants } from './apis/listCallerAccessGrants'; export { getProperties } from './apis/getProperties'; +export { getUrl } from './apis/getUrl'; /* CredentialsStore exports diff --git a/packages/storage/src/internals/types/inputs.ts b/packages/storage/src/internals/types/inputs.ts index 8e813c28e4a..5cd322147fb 100644 --- a/packages/storage/src/internals/types/inputs.ts +++ b/packages/storage/src/internals/types/inputs.ts @@ -9,6 +9,7 @@ import { import { CopyWithPathInput, GetPropertiesWithPathInput, + GetUrlWithPathInput, } from '../../providers/s3'; import { CredentialsProvider, ListLocationsInput } from './credentials'; @@ -47,6 +48,16 @@ export type GetPropertiesInput = ExtendInputWithAdvancedOptions< } >; +/** + * @internal + */ +export type GetUrlInput = ExtendInputWithAdvancedOptions< + GetUrlWithPathInput, + { + locationCredentialsProvider?: CredentialsProvider; + } +>; + /** * @internal */ diff --git a/packages/storage/src/internals/types/outputs.ts b/packages/storage/src/internals/types/outputs.ts index 0a47381ff69..130c1e7f3e5 100644 --- a/packages/storage/src/internals/types/outputs.ts +++ b/packages/storage/src/internals/types/outputs.ts @@ -1,7 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { GetPropertiesWithPathOutput } from '../../providers/s3/types'; +import { + GetPropertiesWithPathOutput, + GetUrlWithPathOutput, +} from '../../providers/s3/types'; import { ListLocationsOutput, LocationCredentials } from './credentials'; @@ -19,3 +22,8 @@ export type GetDataAccessOutput = LocationCredentials; * @internal */ export type GetPropertiesOutput = GetPropertiesWithPathOutput; + +/** + * @internal + */ +export type GetUrlOutput = GetUrlWithPathOutput; diff --git a/packages/storage/src/providers/s3/apis/internal/getUrl.ts b/packages/storage/src/providers/s3/apis/internal/getUrl.ts index 98e7a198cc3..6897a9a5d64 100644 --- a/packages/storage/src/providers/s3/apis/internal/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/internal/getUrl.ts @@ -4,12 +4,7 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { - GetUrlInput, - GetUrlOutput, - GetUrlWithPathInput, - GetUrlWithPathOutput, -} from '../../types'; +import { GetUrlInput, GetUrlOutput, GetUrlWithPathOutput } from '../../types'; import { StorageValidationErrorCode } from '../../../../errors/types/validation'; import { getPresignedGetObjectUrl } from '../../utils/client/s3data'; import { @@ -23,12 +18,14 @@ import { STORAGE_INPUT_KEY, } from '../../utils/constants'; import { constructContentDisposition } from '../../utils/constructContentDisposition'; +// TODO: Remove this interface when we move to public advanced APIs. +import { GetUrlInput as GetUrlWithPathInputWithAdvancedOptions } from '../../../../internals'; import { getProperties } from './getProperties'; export const getUrl = async ( amplify: AmplifyClassV6, - input: GetUrlInput | GetUrlWithPathInput, + input: GetUrlInput | GetUrlWithPathInputWithAdvancedOptions, ): Promise => { const { options: getUrlOptions } = input; const { s3Config, keyPrefix, bucket, identityId } = From 0ee6afffac46de8256e4bc56e90beb0122ec65d4 Mon Sep 17 00:00:00 2001 From: Jim Blanchard Date: Thu, 3 Oct 2024 14:44:38 -0500 Subject: [PATCH 2/2] Remove docstring to discourage use. --- packages/storage/src/internals/apis/getUrl.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/storage/src/internals/apis/getUrl.ts b/packages/storage/src/internals/apis/getUrl.ts index 01670a96215..40a8ee2d0d1 100644 --- a/packages/storage/src/internals/apis/getUrl.ts +++ b/packages/storage/src/internals/apis/getUrl.ts @@ -8,20 +8,6 @@ import { GetUrlInput } from '../types/inputs'; import { GetUrlOutput } from '../types/outputs'; /** - * Get a temporary presigned URL to download the specified S3 object. - * The presigned URL expires when the associated role used to sign the request expires or - * the option `expiresIn` is reached. The `expiresAt` property in the output object indicates when the URL MAY expire. - * - * By default, it will not validate the object that exists in S3. If you set the `options.validateObjectExistence` - * to true, this method will verify the given object already exists in S3 before returning a presigned - * URL, and will throw `StorageError` if the object does not exist. - * - * @param input - The `GetUrlWithPathInput` object. - * @returns Presigned URL and timestamp when the URL may expire. - * @throws service: `S3Exception` - thrown when checking for existence of the object - * @throws validation: `StorageValidationErrorCode` - Validation errors - * thrown either username or key are not defined. - * * @internal */ export function getUrl(input: GetUrlInput) {