Skip to content

Commit b2a3f25

Browse files
authored
feat(api-rest): add rest default auth mode (#14525)
* feat: add rest auth mode * feat: renamed parameter and added library option * fix: change parameter name and type * test: add test for local override global * fix: syntax error * fix: linting issue * feat: add utils to resolve library options
1 parent 8fa81f0 commit b2a3f25

File tree

7 files changed

+152
-4
lines changed

7 files changed

+152
-4
lines changed

packages/api-rest/__tests__/apis/common/publicApis.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,5 +681,113 @@ describe('public APIs', () => {
681681
expect(result).toEqual({ retryable: false });
682682
});
683683
});
684+
685+
describe('defaultAuthMode option', () => {
686+
it('should skip credential resolution when defaultAuthMode is "none"', async () => {
687+
mockFetchAuthSession.mockClear();
688+
689+
await fn(mockAmplifyInstance, {
690+
apiName: 'restApi1',
691+
path: '/public',
692+
options: {
693+
defaultAuthMode: 'none',
694+
},
695+
}).response;
696+
697+
expect(mockFetchAuthSession).not.toHaveBeenCalled();
698+
expect(mockUnauthenticatedHandler).toHaveBeenCalled();
699+
expect(mockAuthenticatedHandler).not.toHaveBeenCalled();
700+
});
701+
702+
it('should resolve credentials when defaultAuthMode is "iam"', async () => {
703+
mockFetchAuthSession.mockResolvedValue({
704+
credentials: {
705+
accessKeyId: 'test-key',
706+
secretAccessKey: 'test-secret',
707+
},
708+
});
709+
710+
await fn(mockAmplifyInstance, {
711+
apiName: 'restApi1',
712+
path: '/private',
713+
options: {
714+
defaultAuthMode: 'iam',
715+
},
716+
}).response;
717+
718+
expect(mockFetchAuthSession).toHaveBeenCalled();
719+
expect(mockAuthenticatedHandler).toHaveBeenCalled();
720+
});
721+
722+
it('should maintain default behavior when no defaultAuthMode specified', async () => {
723+
mockFetchAuthSession.mockResolvedValue({
724+
credentials: null,
725+
});
726+
727+
await fn(mockAmplifyInstance, {
728+
apiName: 'restApi1',
729+
path: '/endpoint',
730+
}).response;
731+
732+
expect(mockFetchAuthSession).toHaveBeenCalled();
733+
expect(mockUnauthenticatedHandler).toHaveBeenCalled();
734+
});
735+
736+
it('should use global defaultAuthMode configuration when no local defaultAuthMode is specified', async () => {
737+
const mockAmplifyWithGlobalConfig = {
738+
...mockAmplifyInstance,
739+
libraryOptions: {
740+
...mockAmplifyInstance.libraryOptions,
741+
API: {
742+
...mockAmplifyInstance.libraryOptions?.API,
743+
REST: {
744+
defaultAuthMode: 'none' as const,
745+
},
746+
},
747+
},
748+
} as any as AmplifyClassV6;
749+
750+
mockFetchAuthSession.mockClear();
751+
752+
await fn(mockAmplifyWithGlobalConfig, {
753+
apiName: 'restApi1',
754+
path: '/public',
755+
}).response;
756+
757+
expect(mockFetchAuthSession).not.toHaveBeenCalled();
758+
expect(mockUnauthenticatedHandler).toHaveBeenCalled();
759+
expect(mockAuthenticatedHandler).not.toHaveBeenCalled();
760+
});
761+
762+
it('should override global defaultAuthMode with local defaultAuthMode configuration', async () => {
763+
const mockAmplifyWithGlobalConfig = {
764+
...mockAmplifyInstance,
765+
libraryOptions: {
766+
...mockAmplifyInstance.libraryOptions,
767+
API: {
768+
...mockAmplifyInstance.libraryOptions?.API,
769+
REST: {
770+
defaultAuthMode: 'none' as const,
771+
},
772+
},
773+
},
774+
} as any as AmplifyClassV6;
775+
776+
mockFetchAuthSession.mockClear();
777+
mockFetchAuthSession.mockResolvedValue({ credentials });
778+
779+
await fn(mockAmplifyWithGlobalConfig, {
780+
apiName: 'restApi1',
781+
path: '/private',
782+
options: {
783+
defaultAuthMode: 'iam',
784+
},
785+
}).response;
786+
787+
expect(mockFetchAuthSession).toHaveBeenCalled();
788+
expect(mockAuthenticatedHandler).toHaveBeenCalled();
789+
expect(mockUnauthenticatedHandler).not.toHaveBeenCalled();
790+
});
791+
});
684792
});
685793
});

packages/api-rest/src/apis/common/transferHandler.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import {
1111
import {
1212
AWSCredentials,
1313
DocumentType,
14+
RESTAuthMode,
1415
RetryStrategy,
1516
} from '@aws-amplify/core/internals/utils';
1617

1718
import {
1819
logger,
1920
parseRestApiServiceError,
2021
parseSigningInfo,
22+
resolveLibraryOptions,
2123
} from '../../utils';
2224
import { resolveHeaders } from '../../utils/resolveHeaders';
2325
import { RestApiResponse, SigningServiceInfo } from '../../types';
@@ -30,6 +32,7 @@ type HandlerOptions = Omit<HttpRequest, 'body' | 'headers'> & {
3032
headers?: Headers;
3133
withCredentials?: boolean;
3234
retryStrategy?: RetryStrategy;
35+
defaultAuthMode?: RESTAuthMode;
3336
};
3437

3538
type RetryDecider = RetryOptions['retryDecider'];
@@ -75,19 +78,29 @@ export const transferHandler = async (
7578
method,
7679
body: resolvedBody,
7780
};
81+
const {
82+
retryStrategy: libraryRetryStrategy,
83+
defaultAuthMode: libraryDefaultAuthMode,
84+
} = resolveLibraryOptions(amplify);
7885
const baseOptions = {
7986
retryDecider: getRetryDeciderFromStrategy(
80-
retryStrategy ?? amplify?.libraryOptions?.API?.REST?.retryStrategy,
87+
retryStrategy ?? libraryRetryStrategy,
8188
),
8289
computeDelay: jitteredBackoff,
8390
withCrossDomainCredentials: withCredentials,
8491
abortSignal,
8592
};
8693

87-
const isIamAuthApplicable = iamAuthApplicable(request, signingServiceInfo);
94+
const defaultAuthMode = options.defaultAuthMode ?? libraryDefaultAuthMode;
95+
96+
let credentials: AWSCredentials | null = null;
97+
if (defaultAuthMode !== 'none') {
98+
credentials = await resolveCredentials(amplify);
99+
}
88100

89101
let response: RestApiResponse;
90-
const credentials = await resolveCredentials(amplify);
102+
const isIamAuthApplicable = iamAuthApplicable(request, signingServiceInfo);
103+
91104
if (isIamAuthApplicable && credentials) {
92105
const signingInfoFromUrl = parseSigningInfo(url);
93106
const signingService =

packages/api-rest/src/types/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import { DocumentType, RetryStrategy } from '@aws-amplify/core/internals/utils';
3+
import {
4+
DocumentType,
5+
RESTAuthMode,
6+
RetryStrategy,
7+
} from '@aws-amplify/core/internals/utils';
48

59
export type GetInput = ApiInput<RestApiOptionsBase>;
610
export type PostInput = ApiInput<RestApiOptionsBase>;
@@ -41,6 +45,7 @@ export interface RestApiOptionsBase {
4145
* @default ` { strategy: 'jittered-exponential-backoff' } `
4246
*/
4347
retryStrategy?: RetryStrategy;
48+
defaultAuthMode?: RESTAuthMode;
4449
/**
4550
* custom timeout in milliseconds.
4651
*/

packages/api-rest/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export { createCancellableOperation } from './createCancellableOperation';
55
export { parseSigningInfo } from './parseSigningInfo';
66
export { parseRestApiServiceError } from './serviceError';
77
export { resolveApiUrl } from './resolveApiUrl';
8+
export { resolveLibraryOptions } from './resolveLibraryOptions';
89
export { logger } from './logger';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { AmplifyClassV6 } from '@aws-amplify/core';
5+
6+
/**
7+
* @internal
8+
*/
9+
export const resolveLibraryOptions = (amplify: AmplifyClassV6) => {
10+
const retryStrategy = amplify.libraryOptions?.API?.REST?.retryStrategy;
11+
const defaultAuthMode = amplify.libraryOptions?.API?.REST?.defaultAuthMode;
12+
13+
return { retryStrategy, defaultAuthMode };
14+
};

packages/core/src/libraryUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export {
4848
AssociationHasOne,
4949
DocumentType,
5050
GraphQLAuthMode,
51+
RESTAuthMode,
5152
ModelFieldType,
5253
NonModelFieldType,
5354
ModelIntrospectionSchema,

packages/core/src/singleton/API/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export interface LibraryAPIOptions {
2525
* @default ` { strategy: 'jittered-exponential-backoff' } `
2626
*/
2727
retryStrategy?: RetryStrategy;
28+
/**
29+
* Default auth mode for REST API calls when no explicit auth is provided.
30+
*/
31+
defaultAuthMode?: RESTAuthMode;
2832
/**
2933
* custom timeout in milliseconds configurable for given REST service, or/and method.
3034
*/
@@ -142,6 +146,8 @@ export type GraphQLAuthMode =
142146
| 'lambda'
143147
| 'none';
144148

149+
export type RESTAuthMode = 'none' | 'iam';
150+
145151
/**
146152
* Type representing a plain JavaScript object that can be serialized to JSON.
147153
*/

0 commit comments

Comments
 (0)