From 2d430676daa90f140f3b77f485dd743413f4fbe1 Mon Sep 17 00:00:00 2001 From: Nika Hassani Date: Mon, 22 Jul 2024 13:49:59 -0700 Subject: [PATCH 1/2] chore(api): update endpoint config to use ApiOutputs instead of AWSApiConfig type --- .../config/amplify_outputs/api_outputs.dart | 20 +++ .../amplify_outputs/data/data_outputs.dart | 14 +- .../rest_api/rest_api_outputs.dart | 17 +- .../amplify_outputs_mapping_test.dart | 3 + .../lib/src/api_plugin_impl.dart | 48 +++--- .../decorators/authorize_http_request.dart | 8 +- .../src/decorators/web_socket_auth_utils.dart | 14 +- .../web_socket/blocs/web_socket_bloc.dart | 4 +- .../web_socket/state/web_socket_state.dart | 8 +- .../web_socket/types/web_socket_types.dart | 4 +- .../lib/src/util/amplify_api_config.dart | 12 +- .../amplify_authorization_rest_client.dart | 6 +- .../test/amplify_api_config_test.dart | 37 +++-- .../test/authorize_http_request_test.dart | 147 ++++++++++-------- packages/api/amplify_api_dart/test/util.dart | 25 +-- 15 files changed, 221 insertions(+), 146 deletions(-) create mode 100644 packages/amplify_core/lib/src/config/amplify_outputs/api_outputs.dart diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/api_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/api_outputs.dart new file mode 100644 index 0000000000..c8e1e5c53b --- /dev/null +++ b/packages/amplify_core/lib/src/config/amplify_outputs/api_outputs.dart @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; + +/// {@template amplify_core.amplify_outputs.data_outputs} +/// The Rest API and GraphQL category Outputs. +/// {@endtemplate} +abstract interface class ApiOutputs { + String get awsRegion; + String get url; + String? get apiKey; + APIAuthorizationType get authorizationType; + ApiType get apiType; +} + +enum ApiType { + rest, + graphQL, +} diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart index 1ae78248c5..ad2402b65e 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; part 'data_outputs.g.dart'; @@ -10,7 +11,8 @@ part 'data_outputs.g.dart'; /// {@endtemplate} @zAmplifyOutputsSerializable class DataOutputs - with AWSEquatable, AWSSerializable, AWSDebuggable { + with AWSEquatable, AWSSerializable, AWSDebuggable + implements ApiOutputs { /// {@macro amplify_core.amplify_outputs.data_outputs} const DataOutputs({ required this.awsRegion, @@ -24,12 +26,15 @@ class DataOutputs _$DataOutputsFromJson(json); /// The AWS region of Amazon AppSync resources. + @override final String awsRegion; /// The AppSync endpoint URL. + @override final String url; /// The AppSync API Key. + @override final String? apiKey; /// The default authorization type for AWS AppSync. @@ -38,6 +43,13 @@ class DataOutputs /// List of supported authorization types for AWS AppSync. final List authorizationTypes; + @override + ApiType get apiType => ApiType.graphQL; + + @override + APIAuthorizationType get authorizationType => + defaultAuthorizationType; + @override List get props => [ awsRegion, diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart index 85028f5be1..0774f64d7c 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; part 'rest_api_outputs.g.dart'; @@ -13,7 +14,8 @@ part 'rest_api_outputs.g.dart'; /// {@endtemplate} @zAmplifyOutputsSerializable class RestApiOutputs - with AWSEquatable, AWSSerializable, AWSDebuggable { + with AWSEquatable, AWSSerializable, AWSDebuggable + implements ApiOutputs { /// {@macro amplify_core.amplify_outputs.rest_api_outputs} const RestApiOutputs({ required this.awsRegion, @@ -25,18 +27,25 @@ class RestApiOutputs factory RestApiOutputs.fromJson(Map json) => _$RestApiOutputsFromJson(json); - /// The AWS region of Amazon AWS Gateway resources. + /// The AWS region of Amazon API Gateway resources. + @override final String awsRegion; - /// The AWS Gateway endpoint URL. + /// The Amazon API Gateway endpoint URL. + @override final String url; - /// The AppSync API Key. + /// The Amazon API Gateway API Key. + @override final String? apiKey; /// The authorization type. + @override final APIAuthorizationType authorizationType; + @override + ApiType get apiType => ApiType.rest; + @override List get props => [ awsRegion, diff --git a/packages/amplify_core/test/config/amplify_outputs_mapping/amplify_outputs_mapping_test.dart b/packages/amplify_core/test/config/amplify_outputs_mapping/amplify_outputs_mapping_test.dart index c5c57c588d..5e2f4ddc75 100644 --- a/packages/amplify_core/test/config/amplify_outputs_mapping/amplify_outputs_mapping_test.dart +++ b/packages/amplify_core/test/config/amplify_outputs_mapping/amplify_outputs_mapping_test.dart @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; diff --git a/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart b/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart index 36f83e993f..1cda540419 100644 --- a/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart +++ b/packages/api/amplify_api_dart/lib/src/api_plugin_impl.dart @@ -15,6 +15,8 @@ import 'package:amplify_api_dart/src/util/amplify_api_config.dart'; import 'package:amplify_api_dart/src/util/amplify_authorization_rest_client.dart'; import 'package:amplify_core/amplify_core.dart'; // ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; +// ignore: implementation_imports import 'package:amplify_core/src/config/amplify_outputs/data/data_outputs.dart'; // ignore: implementation_imports import 'package:amplify_core/src/config/amplify_outputs/rest_api/rest_api_outputs.dart'; @@ -162,7 +164,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { /// Use [apiName] if there are multiple endpoints of the same type. @visibleForTesting AWSHttpClient getHttpClient( - EndpointType type, { + ApiType type, { String? apiName, APIAuthorizationType? authorizationMode, }) { @@ -181,8 +183,8 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { ); } - EndpointConfig _getEndpointConfig(EndpointType type, String? apiName) { - if (type == EndpointType.graphQL) { + EndpointConfig _getEndpointConfig(ApiType type, String? apiName) { + if (type == ApiType.graphQL) { if (_dataConfig == null) { throw ConfigurationError( 'No GraphQL API endpoint found.', @@ -210,16 +212,10 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { } return EndpointConfig( apiName, - AWSApiConfig( - region: config.awsRegion, - endpoint: config.url, - endpointType: EndpointType.graphQL, - authorizationType: config.defaultAuthorizationType, - apiKey: config.apiKey, - ), + config, ); } - if (type == EndpointType.rest) { + if (type == ApiType.rest) { if (_restConfig == null) { throw ConfigurationError( 'No REST API endpoint found.', @@ -247,13 +243,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { } return EndpointConfig( apiName, - AWSApiConfig( - region: config.awsRegion, - endpoint: config.url, - endpointType: EndpointType.rest, - authorizationType: config.authorizationType, - apiKey: config.apiKey, - ), + config, ); } throw ConfigurationError( @@ -263,7 +253,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { WebSocketBloc _webSocketBloc({String? apiName}) { final endpoint = _getEndpointConfig( - EndpointType.graphQL, + ApiType.graphQL, apiName, ); @@ -292,7 +282,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { Uri _getGraphQLUri(String? apiName) { final endpoint = _getEndpointConfig( - EndpointType.graphQL, + ApiType.graphQL, apiName, ); return endpoint.getUri(); @@ -304,7 +294,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { Map? queryParameters, ) { final endpoint = _getEndpointConfig( - EndpointType.rest, + ApiType.rest, apiName, ); return endpoint.getUri(path: path, queryParameters: queryParameters); @@ -317,7 +307,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { @override GraphQLOperation query({required GraphQLRequest request}) { final graphQLClient = getHttpClient( - EndpointType.graphQL, + ApiType.graphQL, apiName: request.apiName, authorizationMode: request.authorizationMode, ); @@ -333,7 +323,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { @override GraphQLOperation mutate({required GraphQLRequest request}) { final graphQLClient = getHttpClient( - EndpointType.graphQL, + ApiType.graphQL, apiName: request.apiName, authorizationMode: request.authorizationMode, ); @@ -366,7 +356,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { String? apiName, }) { final uri = _getRestUri(path, apiName, queryParameters); - final client = getHttpClient(EndpointType.rest, apiName: apiName); + final client = getHttpClient(ApiType.rest, apiName: apiName); return RestOperation.fromHttpOperation( AWSStreamedHttpRequest.delete( uri, @@ -384,7 +374,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { String? apiName, }) { final uri = _getRestUri(path, apiName, queryParameters); - final client = getHttpClient(EndpointType.rest, apiName: apiName); + final client = getHttpClient(ApiType.rest, apiName: apiName); return RestOperation.fromHttpOperation( AWSHttpRequest.get( uri, @@ -401,7 +391,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { String? apiName, }) { final uri = _getRestUri(path, apiName, queryParameters); - final client = getHttpClient(EndpointType.rest, apiName: apiName); + final client = getHttpClient(ApiType.rest, apiName: apiName); return RestOperation.fromHttpOperation( AWSHttpRequest.head( uri, @@ -419,7 +409,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { String? apiName, }) { final uri = _getRestUri(path, apiName, queryParameters); - final client = getHttpClient(EndpointType.rest, apiName: apiName); + final client = getHttpClient(ApiType.rest, apiName: apiName); return RestOperation.fromHttpOperation( AWSStreamedHttpRequest.patch( uri, @@ -438,7 +428,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { String? apiName, }) { final uri = _getRestUri(path, apiName, queryParameters); - final client = getHttpClient(EndpointType.rest, apiName: apiName); + final client = getHttpClient(ApiType.rest, apiName: apiName); return RestOperation.fromHttpOperation( AWSStreamedHttpRequest.post( uri, @@ -457,7 +447,7 @@ class AmplifyAPIDart extends APIPluginInterface with AWSDebuggable { String? apiName, }) { final uri = _getRestUri(path, apiName, queryParameters); - final client = getHttpClient(EndpointType.rest, apiName: apiName); + final client = getHttpClient(ApiType.rest, apiName: apiName); return RestOperation.fromHttpOperation( AWSStreamedHttpRequest.put( uri, diff --git a/packages/api/amplify_api_dart/lib/src/decorators/authorize_http_request.dart b/packages/api/amplify_api_dart/lib/src/decorators/authorize_http_request.dart index 4085676190..b2271ef4a7 100644 --- a/packages/api/amplify_api_dart/lib/src/decorators/authorize_http_request.dart +++ b/packages/api/amplify_api_dart/lib/src/decorators/authorize_http_request.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'package:amplify_api_dart/src/graphql/providers/app_sync_api_key_auth_provider.dart'; import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:meta/meta.dart'; /// Transforms an HTTP request according to auth providers that match the endpoint @@ -12,7 +14,7 @@ import 'package:meta/meta.dart'; @internal Future authorizeHttpRequest( AWSBaseHttpRequest request, { - required AWSApiConfig endpointConfig, + required ApiOutputs endpointConfig, required AmplifyAuthProviderRepository authProviderRepo, APIAuthorizationType? authorizationMode, }) async { @@ -49,7 +51,7 @@ Future authorizeHttpRequest( .getAuthProvider(APIAuthorizationType.iam.authProviderToken), authType, ); - final isGraphQL = endpointConfig.endpointType == EndpointType.graphQL; + final isGraphQL = endpointConfig.apiType == ApiType.graphQL; final service = isGraphQL ? AWSService.appSync : AWSService.apiGatewayManagementApi; // resolves to "execute-api" @@ -60,7 +62,7 @@ Future authorizeHttpRequest( final authorizedRequest = await authProvider.authorizeRequest( request, options: IamAuthProviderOptions( - region: endpointConfig.region, + region: endpointConfig.awsRegion, service: service, serviceConfiguration: serviceConfiguration, ), diff --git a/packages/api/amplify_api_dart/lib/src/decorators/web_socket_auth_utils.dart b/packages/api/amplify_api_dart/lib/src/decorators/web_socket_auth_utils.dart index f0ce2969fc..2eb8660a33 100644 --- a/packages/api/amplify_api_dart/lib/src/decorators/web_socket_auth_utils.dart +++ b/packages/api/amplify_api_dart/lib/src/decorators/web_socket_auth_utils.dart @@ -9,6 +9,8 @@ import 'dart:convert'; import 'package:amplify_api_dart/src/decorators/authorize_http_request.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:meta/meta.dart'; const _appSyncHostPortion = 'appsync-api'; @@ -31,7 +33,7 @@ const _emptyBody = {}; /// /// See https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#handshake-details-to-establish-the-websocket-connection= Future generateConnectionUri( - AWSApiConfig config, + ApiOutputs config, AmplifyAuthProviderRepository authRepo, ) async { // First, generate auth query parameters. @@ -48,7 +50,7 @@ Future generateConnectionUri( 'payload': base64.encode(utf8.encode(json.encode(_emptyBody))), }; // Conditionally format the URI for a) AppSync domain b) custom domain. - var endpointUriHost = Uri.parse(config.endpoint).host; + var endpointUriHost = Uri.parse(config.url).host; String path; if (endpointUriHost.contains(_appSyncHostPortion) && endpointUriHost.endsWith(_appSyncHostSuffix)) { @@ -78,7 +80,7 @@ Future generateConnectionUri( /// See https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#subscription-registration-message Future generateSubscriptionRegistrationMessage( - AWSApiConfig config, { + ApiOutputs config, { required String id, required AmplifyAuthProviderRepository authRepo, required GraphQLRequest request, @@ -113,21 +115,21 @@ Future /// the HTTP request are reformatted and returned. This logic applies for all auth /// modes as determined by [authRepo] parameter. Future> _generateAuthorizationHeaders( - AWSApiConfig config, { + ApiOutputs config, { required bool isConnectionInit, required AmplifyAuthProviderRepository authRepo, required Map body, APIAuthorizationType? authorizationMode, Map? customHeaders, }) async { - final endpointHost = Uri.parse(config.endpoint).host; + final endpointHost = Uri.parse(config.url).host; // Create canonical HTTP request to authorize but never send. // // The canonical request URL is a little different depending on if authorizing // connection URI or start message (subscription registration). final maybeConnect = isConnectionInit ? '/connect' : ''; final canonicalHttpRequest = AWSStreamedHttpRequest.post( - Uri.parse('${config.endpoint}$maybeConnect'), + Uri.parse('${config.url}$maybeConnect'), headers: { ...?customHeaders, ..._requiredHeaders, diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart index 3ea504efba..9f679d5076 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart @@ -12,6 +12,8 @@ import 'package:amplify_api_dart/src/graphql/web_socket/types/connectivity_platf import 'package:amplify_api_dart/src/graphql/web_socket/types/subscriptions_event.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart' hide SubscriptionEvent; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:async/async.dart'; import 'package:meta/meta.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -26,7 +28,7 @@ part '../types/web_socket_event.dart'; class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { /// {@macro api.web_socket_bloc} WebSocketBloc({ - required AWSApiConfig config, + required ApiOutputs config, required AmplifyAuthProviderRepository authProviderRepo, required WebSocketService wsService, required GraphQLSubscriptionOptions subscriptionOptions, diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/state/web_socket_state.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/state/web_socket_state.dart index 58fb10f51b..b5d2414059 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/state/web_socket_state.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/state/web_socket_state.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'package:amplify_api_dart/src/graphql/web_socket/blocs/subscriptions_bloc.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/services/web_socket_service.dart'; import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:async/async.dart'; /// Base [WebSocketState] containing the discrete state for a websocket @@ -22,8 +24,8 @@ abstract class WebSocketState { this.options, ); - /// AWS Config - final AWSApiConfig config; + /// API Outputs + final ApiOutputs config; /// Amplify Auth Provider final AmplifyAuthProviderRepository authProviderRepo; @@ -44,7 +46,7 @@ abstract class WebSocketState { final GraphQLSubscriptionOptions options; /// Poll URI - Uri get pollUri => Uri.parse(config.endpoint).replace(path: 'ping'); + Uri get pollUri => Uri.parse(config.url).replace(path: 'ping'); /// Move state to [ConnectingState] ConnectingState connecting({ diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_types.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_types.dart index b10eb2f68f..fbbd8a2b60 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_types.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/types/web_socket_types.dart @@ -9,6 +9,8 @@ library amplify_api.graphql.ws.web_socket_types; import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -100,7 +102,7 @@ class SubscriptionRegistrationPayload extends WebSocketMessagePayload { required this.authorizationHeaders, }); final GraphQLRequest request; - final AWSApiConfig config; + final ApiOutputs config; final Map authorizationHeaders; @override diff --git a/packages/api/amplify_api_dart/lib/src/util/amplify_api_config.dart b/packages/api/amplify_api_dart/lib/src/util/amplify_api_config.dart index 0ad17e1473..c2e0a2f045 100644 --- a/packages/api/amplify_api_dart/lib/src/util/amplify_api_config.dart +++ b/packages/api/amplify_api_dart/lib/src/util/amplify_api_config.dart @@ -2,23 +2,23 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:meta/meta.dart'; const _slash = '/'; /// Allows formatting the URL from the config with new paths/query params. @internal - -// TODO(nikahsn): refactor EndpointConfig to not use AWSApiConfig type class EndpointConfig with AWSEquatable { // ignore: public_member_api_docs const EndpointConfig(this.name, this.config); - /// The key used in the Amplify configuration file for this config entry. + /// The key used in the Amplify Outputs for this config entry. final String name; - /// The value in the Amplify configuration file which as config details. - final AWSApiConfig config; + /// The value in the Amplify Outputs file which has the config details. + final ApiOutputs config; @override List get props => [name, config]; @@ -26,7 +26,7 @@ class EndpointConfig with AWSEquatable { /// Gets the host with environment path prefix from Amplify config and combines /// with [path] and [queryParameters] to return a full [Uri]. Uri getUri({String? path, Map? queryParameters}) { - final parsed = Uri.parse(config.endpoint); + final parsed = Uri.parse(config.url); final pathSegments = path != null ? [...parsed.pathSegments, ..._getSegments(path)] : null; diff --git a/packages/api/amplify_api_dart/lib/src/util/amplify_authorization_rest_client.dart b/packages/api/amplify_api_dart/lib/src/util/amplify_authorization_rest_client.dart index 76ce900c0f..b2b4ac26d5 100644 --- a/packages/api/amplify_api_dart/lib/src/util/amplify_authorization_rest_client.dart +++ b/packages/api/amplify_api_dart/lib/src/util/amplify_authorization_rest_client.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'package:amplify_api_dart/src/decorators/authorize_http_request.dart'; import 'package:amplify_core/amplify_core.dart'; +// ignore: implementation_imports +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; import 'package:meta/meta.dart'; /// Implementation of [AWSHttpClient] that authorizes HTTP requests with @@ -24,7 +26,7 @@ class AmplifyAuthorizationRestClient extends AWSBaseHttpClient { final AmplifyAuthProviderRepository authProviderRepo; /// Determines how requests with this client are authorized. - final AWSApiConfig endpointConfig; + final ApiOutputs endpointConfig; /// The authorization mode to use for requests with this client. /// @@ -56,7 +58,7 @@ class AmplifyAuthorizationRestClient extends AWSBaseHttpClient { AWSBaseHttpResponse response, ) async { // For REST endpoints, throw [HttpStatusException] on non-successful responses. - if (endpointConfig.endpointType == EndpointType.rest && + if (endpointConfig.apiType == ApiType.rest && (response.statusCode < 200 || response.statusCode >= 300)) { final responseForException = switch (response) { AWSStreamedHttpResponse _ => await response.read(), diff --git a/packages/api/amplify_api_dart/test/amplify_api_config_test.dart b/packages/api/amplify_api_dart/test/amplify_api_config_test.dart index 5fe67174d5..17bf095faa 100644 --- a/packages/api/amplify_api_dart/test/amplify_api_config_test.dart +++ b/packages/api/amplify_api_dart/test/amplify_api_config_test.dart @@ -3,6 +3,9 @@ import 'package:amplify_api_dart/src/util/amplify_api_config.dart'; import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/api_outputs.dart'; +import 'package:amplify_core/src/config/amplify_outputs/data/data_outputs.dart'; +import 'package:amplify_core/src/config/amplify_outputs/rest_api/rest_api_outputs.dart'; import 'package:test/test.dart'; void main() { @@ -34,7 +37,7 @@ void main() { final params = {'foo': 'bar', 'bar': 'baz'}; final endpointConfig = createEndpointConfig( endpoint, - type: EndpointType.rest, + type: ApiType.rest, ); final uri = endpointConfig.getUri(path: path, queryParameters: params); const expected = '$endpoint/$path?foo=bar&bar=baz'; @@ -47,7 +50,7 @@ void main() { final params = {'foo': 'bar', 'bar': 'baz'}; final endpointConfig = createEndpointConfig( endpoint, - type: EndpointType.rest, + type: ApiType.rest, ); final uri = endpointConfig.getUri(path: path, queryParameters: params); const expected = '$endpoint$path?foo=bar&bar=baz'; @@ -58,20 +61,28 @@ void main() { EndpointConfig createEndpointConfig( String endpoint, { - EndpointType type = EndpointType.graphQL, + ApiType type = ApiType.graphQL, APIAuthorizationType authorizationType = APIAuthorizationType.apiKey, }) { const region = 'us-east-1'; const apiKey = 'abc-123'; - - final config = AWSApiConfig( - endpointType: type, - endpoint: endpoint, - region: region, - authorizationType: authorizationType, - apiKey: apiKey, - ); - - final endpointConfig = EndpointConfig('GraphQL', config); + late final ApiOutputs config; + if (type == ApiType.graphQL) { + config = DataOutputs( + url: endpoint, + awsRegion: region, + defaultAuthorizationType: authorizationType, + apiKey: apiKey, + authorizationTypes: [authorizationType], + ); + } else { + config = RestApiOutputs( + url: endpoint, + awsRegion: region, + authorizationType: authorizationType, + apiKey: apiKey, + ); + } + final endpointConfig = EndpointConfig('api-name', config); return endpointConfig; } diff --git a/packages/api/amplify_api_dart/test/authorize_http_request_test.dart b/packages/api/amplify_api_dart/test/authorize_http_request_test.dart index a39dc8b62c..784a92211a 100644 --- a/packages/api/amplify_api_dart/test/authorize_http_request_test.dart +++ b/packages/api/amplify_api_dart/test/authorize_http_request_test.dart @@ -7,6 +7,8 @@ import 'package:amplify_api_dart/src/decorators/authorize_http_request.dart'; import 'package:amplify_api_dart/src/graphql/providers/app_sync_api_key_auth_provider.dart'; import 'package:amplify_api_dart/src/graphql/providers/oidc_function_api_auth_provider.dart'; import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/data/data_outputs.dart'; +import 'package:amplify_core/src/config/amplify_outputs/rest_api/rest_api_outputs.dart'; import 'package:test/test.dart'; import 'util.dart'; @@ -52,13 +54,13 @@ void main() { group('authorizeHttpRequest', () { test('no-op for auth mode NONE', () async { - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.none, - endpoint: _restEndpoint, - endpointType: EndpointType.rest, - region: _region, + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.none, + authorizationTypes: [], + url: _restEndpoint, + awsRegion: _region, ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, @@ -73,13 +75,12 @@ void main() { }); test('no-op for request with Authorization header already set', () async { - const endpointConfig = AWSApiConfig( + const endpointConfig = RestApiOutputs( authorizationType: APIAuthorizationType.userPools, - endpoint: _restEndpoint, - endpointType: EndpointType.rest, - region: _region, + url: _restEndpoint, + awsRegion: _region, ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + final inputRequest = _generateTestRequest(endpointConfig.url); const testAuthValue = 'foo'; inputRequest.headers .putIfAbsent(AWSHeaders.authorization, () => testAuthValue); @@ -97,13 +98,15 @@ void main() { }); test('authorizes request with IAM auth provider', () async { - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.iam, - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, - ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.iam, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.iam, + ], + ); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, @@ -113,18 +116,17 @@ void main() { }); test('does not sign body of POST request with IAM REST API', () async { - const endpointConfig = AWSApiConfig( + const endpointConfig = RestApiOutputs( authorizationType: APIAuthorizationType.iam, - endpoint: _restEndpoint, - endpointType: EndpointType.rest, - region: _region, + url: _restEndpoint, + awsRegion: _region, ); final inputRequest = AWSHttpRequest( method: AWSHttpMethod.post, body: json.encode({ 'foo': 'bar', }).codeUnits, - uri: Uri.parse(endpointConfig.endpoint), + uri: Uri.parse(endpointConfig.url), ); final authorizedRequest = await authorizeHttpRequest( inputRequest, @@ -139,14 +141,13 @@ void main() { test('authorizes request with API key', () async { const testApiKey = 'abc-123-fake-key'; - const endpointConfig = AWSApiConfig( + const endpointConfig = RestApiOutputs( authorizationType: APIAuthorizationType.apiKey, apiKey: testApiKey, - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, + url: _gqlEndpoint, + awsRegion: _region, ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, @@ -159,14 +160,16 @@ void main() { }); test('throws when API key not in config', () async { - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.apiKey, + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.apiKey, // no apiKey value provided - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.apiKey, + ], ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + final inputRequest = _generateTestRequest(endpointConfig.url); await expectLater( authorizeHttpRequest( inputRequest, @@ -178,13 +181,15 @@ void main() { }); test('authorizes with Cognito User Pools auth mode', () async { - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.userPools, - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, - ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.userPools, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.userPools, + ], + ); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, @@ -197,13 +202,15 @@ void main() { }); test('authorizes with OIDC auth mode', () async { - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.oidc, - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, - ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.oidc, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.oidc, + ], + ); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, @@ -216,13 +223,15 @@ void main() { }); test('authorizes with lambda (function) auth mode', () async { - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.function, - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, - ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.function, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.function, + ], + ); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, @@ -237,14 +246,16 @@ void main() { test('authorizes with authorizationMode parameter that overrides config', () async { const testApiKey = 'abc-123-fake-key'; - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.userPools, + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.userPools, apiKey: testApiKey, - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.userPools, + ], ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + final inputRequest = _generateTestRequest(endpointConfig.url); final authorizedRequest = await authorizeHttpRequest( inputRequest, endpointConfig: endpointConfig, @@ -263,14 +274,16 @@ void main() { test('throws when no auth provider found', () async { final emptyAuthRepo = AmplifyAuthProviderRepository(); - const endpointConfig = AWSApiConfig( - authorizationType: APIAuthorizationType.apiKey, + const endpointConfig = DataOutputs( + defaultAuthorizationType: APIAuthorizationType.apiKey, apiKey: 'abc-123-fake-key', - endpoint: _gqlEndpoint, - endpointType: EndpointType.graphQL, - region: _region, + url: _gqlEndpoint, + awsRegion: _region, + authorizationTypes: [ + APIAuthorizationType.apiKey, + ], ); - final inputRequest = _generateTestRequest(endpointConfig.endpoint); + final inputRequest = _generateTestRequest(endpointConfig.url); await expectLater( authorizeHttpRequest( inputRequest, diff --git a/packages/api/amplify_api_dart/test/util.dart b/packages/api/amplify_api_dart/test/util.dart index ada83fec74..f9c0494865 100644 --- a/packages/api/amplify_api_dart/test/util.dart +++ b/packages/api/amplify_api_dart/test/util.dart @@ -12,6 +12,7 @@ import 'package:amplify_api_dart/src/graphql/web_socket/state/web_socket_state.d import 'package:amplify_api_dart/src/graphql/web_socket/types/connectivity_platform.dart'; import 'package:amplify_api_dart/src/graphql/web_socket/types/web_socket_types.dart'; import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_core/src/config/amplify_outputs/data/data_outputs.dart'; import 'package:async/async.dart'; import 'package:aws_common/testing.dart'; import 'package:aws_signature_v4/aws_signature_v4.dart'; @@ -68,19 +69,23 @@ void validateSignedRequest(AWSBaseHttpRequest request) { ); } -const testApiKeyConfig = AWSApiConfig( - endpointType: EndpointType.graphQL, - endpoint: 'https://abc123.appsync-api.us-east-1.amazonaws.com/graphql', - region: 'us-east-1', - authorizationType: APIAuthorizationType.apiKey, +const testApiKeyConfig = DataOutputs( + url: 'https://abc123.appsync-api.us-east-1.amazonaws.com/graphql', + awsRegion: 'us-east-1', + defaultAuthorizationType: APIAuthorizationType.apiKey, apiKey: 'abc-123', + authorizationTypes: [ + APIAuthorizationType.apiKey, + ], ); -const testApiKeyConfigCustomDomain = AWSApiConfig( - endpointType: EndpointType.graphQL, - endpoint: 'https://foo.bar.aws.dev/graphql ', - region: 'us-east-1', - authorizationType: APIAuthorizationType.apiKey, +const testApiKeyConfigCustomDomain = DataOutputs( + url: 'https://foo.bar.aws.dev/graphql ', + awsRegion: 'us-east-1', + defaultAuthorizationType: APIAuthorizationType.apiKey, apiKey: 'abc-123', + authorizationTypes: [ + APIAuthorizationType.apiKey, + ], ); const expectedApiKeyWebSocketConnectionUrl = From 21625fffa7e039d2d03dd806ac1749f5d715c663 Mon Sep 17 00:00:00 2001 From: Nika Hassani Date: Tue, 23 Jul 2024 11:57:30 -0700 Subject: [PATCH 2/2] address comments --- .../amplify_outputs/data/data_outputs.dart | 2 ++ .../rest_api/rest_api_outputs.dart | 1 + .../test/amplify_api_config_test.dart | 34 +++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart index ad2402b65e..a72b3d1fa2 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/data/data_outputs.dart @@ -43,9 +43,11 @@ class DataOutputs /// List of supported authorization types for AWS AppSync. final List authorizationTypes; + /// The GraphQL Api type. @override ApiType get apiType => ApiType.graphQL; + /// The default authorization type. @override APIAuthorizationType get authorizationType => defaultAuthorizationType; diff --git a/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart b/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart index 0774f64d7c..4ba69d001b 100644 --- a/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart +++ b/packages/amplify_core/lib/src/config/amplify_outputs/rest_api/rest_api_outputs.dart @@ -43,6 +43,7 @@ class RestApiOutputs @override final APIAuthorizationType authorizationType; + /// The Rest Api type. @override ApiType get apiType => ApiType.rest; diff --git a/packages/api/amplify_api_dart/test/amplify_api_config_test.dart b/packages/api/amplify_api_dart/test/amplify_api_config_test.dart index 17bf095faa..a133deb96c 100644 --- a/packages/api/amplify_api_dart/test/amplify_api_config_test.dart +++ b/packages/api/amplify_api_dart/test/amplify_api_config_test.dart @@ -66,23 +66,21 @@ EndpointConfig createEndpointConfig( }) { const region = 'us-east-1'; const apiKey = 'abc-123'; - late final ApiOutputs config; - if (type == ApiType.graphQL) { - config = DataOutputs( - url: endpoint, - awsRegion: region, - defaultAuthorizationType: authorizationType, - apiKey: apiKey, - authorizationTypes: [authorizationType], - ); - } else { - config = RestApiOutputs( - url: endpoint, - awsRegion: region, - authorizationType: authorizationType, - apiKey: apiKey, - ); - } - final endpointConfig = EndpointConfig('api-name', config); + final config = switch (type) { + ApiType.graphQL => DataOutputs( + url: endpoint, + awsRegion: region, + defaultAuthorizationType: authorizationType, + apiKey: apiKey, + authorizationTypes: [authorizationType], + ), + ApiType.rest => RestApiOutputs( + url: endpoint, + awsRegion: region, + authorizationType: authorizationType, + apiKey: apiKey, + ) + }; + final endpointConfig = EndpointConfig('api-name', config as ApiOutputs); return endpointConfig; }