Skip to content

Commit 7ff6000

Browse files
authored
fix(api): OIDC/Lambda Fixes (#862)
* Clean up pinpoint * Clean up pinpoint dart * Update iOS script * Update CI order * Add Dart lints to API * Apply Android/iOS lints to API * Rename uuid * Small changes * Fix scripts * Clean up * Fix unit tests * Continue impl * Fix android unit tests * Remove duplicate lint check * Fix analytics app * Adjust java options * Bump java RAM * Remove concurrency * Disable gradle daemon * Update gradle properties * Update gradle config * Revert "Update gradle config" This reverts commit a43ad29. * Revert gradle changes * Disable gradle daemon * Add kotlin style flags * Disable gradle daemon * Bump JVM memory * Change daemon setting * Adjust JVM memory * Lint debug only * Fix API tests * Bump deps and fix coverage script * Fix Gradle version * Revert unnecessary changes * Update melos postclean files * Enable fatal infos * Fix analyze scope * Fix postbootstrap * Fix missing sample app * Fix order in CI * Revert add sample app * Revert order change * Merge branch 'datastore/multiauth' into chore/api-lints * Revert multiauth * Fix updateTokens * Fix GraphQL error decoding * Fix formatting * Clean up * Clean up * Clean up * Fix analysis for Flutter 2.5.0
1 parent d288ea4 commit 7ff6000

File tree

14 files changed

+278
-38
lines changed

14 files changed

+278
-38
lines changed

melos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ scripts:
6868
6969
analyze:
7070
run: melos exec -c 1 --fail-fast -- \
71-
flutter analyze
71+
flutter analyze --no-fatal-infos
7272
description: >
7373
Analyzes all packages and fails if there are any errors.
7474
select-package:

packages/amplify_api/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApiPlugin.kt

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.annotation.NonNull
2222
import androidx.annotation.VisibleForTesting
2323
import com.amazonaws.amplify.amplify_api.auth.FlutterAuthProviders
2424
import com.amazonaws.amplify.amplify_api.rest_api.FlutterRestApi
25+
import com.amazonaws.amplify.amplify_core.cast
2526
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.createSerializedUnrecognizedError
2627
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.handleAddPluginException
2728
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.postExceptionToFlutterChannel
@@ -105,16 +106,7 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
105106
// Update tokens if included with request
106107
val tokens = arguments["tokens"] as? List<*>
107108
if (tokens != null && tokens.isNotEmpty()) {
108-
for (authToken in tokens as List<Map<*, *>>) {
109-
val token = authToken["token"] as? String?
110-
val tokenType = authToken["type"] as? String ?: throw invalidTokenType()
111-
val authType: AuthorizationType = try {
112-
AuthorizationType.from(tokenType)
113-
} catch (e: Exception) {
114-
throw invalidTokenType(tokenType)
115-
}
116-
FlutterAuthProviders.setToken(authType, token)
117-
}
109+
updateTokens(tokens)
118110
}
119111

120112
when (call.method) {
@@ -131,6 +123,17 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
131123
arguments,
132124
graphqlSubscriptionStreamHandler
133125
)
126+
"updateTokens" -> {
127+
if (tokens == null || tokens.isEmpty()) {
128+
throw ApiException(
129+
"Invalid token map provided",
130+
"Provide tokens in the \"tokens\" field"
131+
)
132+
}
133+
134+
// Tokens already updated
135+
result.success(null)
136+
}
134137
else -> result.notImplemented()
135138
}
136139
} catch (e: Exception) {
@@ -159,6 +162,19 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
159162
}
160163
}
161164

165+
private fun updateTokens(tokens: List<*>) {
166+
for (authToken in tokens.cast<Map<String, Any?>>()) {
167+
val token = authToken["token"] as? String?
168+
val tokenType = authToken["type"] as? String ?: throw invalidTokenType()
169+
val authType: AuthorizationType = try {
170+
AuthorizationType.from(tokenType)
171+
} catch (e: Exception) {
172+
throw invalidTokenType(tokenType)
173+
}
174+
FlutterAuthProviders.setToken(authType, token)
175+
}
176+
}
177+
162178
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
163179
channel.setMethodCallHandler(null)
164180
}

packages/amplify_api/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/FlutterGraphQLApi.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil
2121
import com.amplifyframework.api.ApiException
2222
import com.amplifyframework.api.aws.GsonVariablesSerializer
2323
import com.amplifyframework.api.graphql.GraphQLOperation
24+
import com.amplifyframework.api.graphql.GraphQLPathSegment
2425
import com.amplifyframework.api.graphql.GraphQLResponse
2526
import com.amplifyframework.api.graphql.SimpleGraphQLRequest
2627
import com.amplifyframework.core.Action
@@ -62,7 +63,7 @@ class FlutterGraphQLApi {
6263

6364
val result: Map<String, Any> = mapOf(
6465
"data" to response.data,
65-
"errors" to response.errors.map { it.message }
66+
"errors" to response.errors.map { it.toMap() }
6667
)
6768
LOG.debug("GraphQL query operation succeeded with response: $result")
6869
handler.post { flutterResult.success(result) }
@@ -138,7 +139,7 @@ class FlutterGraphQLApi {
138139

139140
val result: Map<String, Any> = mapOf(
140141
"data" to response.data,
141-
"errors" to response.errors.map { it.message }
142+
"errors" to response.errors.map { it.toMap() }
142143
)
143144
LOG.debug("GraphQL mutate operation succeeded with response : $result")
144145
handler.post { flutterResult.success(result) }
@@ -223,7 +224,7 @@ class FlutterGraphQLApi {
223224
val responseCallback = Consumer<GraphQLResponse<String>> { response ->
224225
val payload: Map<String, Any> = mapOf(
225226
"data" to response.data,
226-
"errors" to response.errors.map { it.message }
227+
"errors" to response.errors.map { it.toMap() }
227228
)
228229
LOG.debug("GraphQL subscription event received: $payload")
229230
graphqlSubscriptionStreamHandler.sendEvent(
@@ -293,4 +294,17 @@ class FlutterGraphQLApi {
293294
}
294295
}
295296
}
296-
}
297+
}
298+
299+
fun GraphQLResponse.Error.toMap(): Map<String, Any?> = mapOf(
300+
"message" to message,
301+
"locations" to locations?.map { mapOf("line" to it.line, "column" to it.column) },
302+
"path" to path?.map<GraphQLPathSegment, Any?> {
303+
when {
304+
it.isInteger -> it.asInt
305+
it.isString -> it.asString
306+
else -> null
307+
}
308+
},
309+
"extensions" to extensions
310+
)

packages/amplify_api/ios/Classes/FlutterApiResponse.swift

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,45 @@ import AmplifyPlugins
1919

2020
import amplify_core
2121

22+
extension GraphQLError.Location: Encodable {
23+
enum CodingKeys: String, CodingKey {
24+
case line
25+
case column
26+
}
27+
28+
public func encode(to encoder: Encoder) throws {
29+
var container = encoder.container(keyedBy: CodingKeys.self)
30+
try container.encode(line, forKey: .line)
31+
try container.encode(column, forKey: .column)
32+
}
33+
}
34+
35+
extension GraphQLError: Encodable {
36+
enum CodingKeys: String, CodingKey {
37+
case message
38+
case locations
39+
case path
40+
case extensions
41+
}
42+
43+
public func encode(to encoder: Encoder) throws {
44+
var container = encoder.container(keyedBy: CodingKeys.self)
45+
try container.encode(message, forKey: .message)
46+
try container.encode(locations, forKey: .locations)
47+
try container.encode(path, forKey: .path)
48+
try container.encode(extensions, forKey: .extensions)
49+
}
50+
}
51+
52+
extension Encodable {
53+
func asDictionary() -> [String: Any]? {
54+
guard let data = try? JSONEncoder().encode(self) else {
55+
return nil
56+
}
57+
return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
58+
}
59+
}
60+
2261
class FlutterApiResponse {
2362
static func handleGraphQLErrorResponse(
2463
flutterResult: @escaping FlutterResult,
@@ -28,14 +67,14 @@ class FlutterApiResponse {
2867
case .error(let errorList):
2968
let result: [String: Any?] = [
3069
"data": nil,
31-
"errors": errorList.map { $0.message }
70+
"errors": errorList.compactMap { $0.asDictionary() }
3271
]
3372
print("Received a GraphQL response with errors: \(errorList)")
3473
flutterResult(result)
3574
case .partial(let data, let errorList):
3675
let result: [String: Any?] = [
3776
"data": data,
38-
"errors": errorList.map { $0.message }
77+
"errors": errorList.compactMap { $0.asDictionary() }
3978
]
4079
print("Received a partially successful GraphQL response: \(result)")
4180
flutterResult(result)
@@ -65,7 +104,7 @@ class FlutterApiResponse {
65104
case .error(let errorList):
66105
let payload: [String: Any?] = [
67106
"data": nil,
68-
"errors": errorList.map { $0.message }
107+
"errors": errorList.compactMap { $0.asDictionary() }
69108
]
70109
print("Received a GraphQL subscription event with errors: \(errorList)")
71110
graphQLSubscriptionsStreamHandler.sendEvent(
@@ -75,7 +114,7 @@ class FlutterApiResponse {
75114
case .partial(let data, let errorList):
76115
let payload: [String: Any?] = [
77116
"data": data,
78-
"errors": errorList.map { $0.message }
117+
"errors": errorList.compactMap { $0.asDictionary() }
79118
]
80119
print("Received a partially successful GraphQL subscription event: \(payload)")
81120
graphQLSubscriptionsStreamHandler.sendEvent(

packages/amplify_api/ios/Classes/SwiftAmplifyApiPlugin.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,7 @@ public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin {
6464

6565
// Update tokens if included in request.
6666
if let tokens = arguments["tokens"] as? [[String: Any?]] {
67-
for tokenMap in tokens {
68-
guard let type = tokenMap["type"] as? String,
69-
let awsAuthType = AWSAuthorizationType(rawValue: type),
70-
let token = tokenMap["token"] as? String? else {
71-
throw APIError.unknown(
72-
"Invalid arguments",
73-
"A valid AWSAuthorizationType and token entry are required",
74-
nil)
75-
}
76-
FlutterAuthProviders.setToken(type: awsAuthType, token: token)
77-
}
67+
try updateTokens(tokens)
7868
}
7969

8070
try innerHandle(method: method, arguments: arguments, result: result)
@@ -141,6 +131,15 @@ public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin {
141131
request: arguments, bridge: bridge,
142132
graphQLSubscriptionsStreamHandler: graphQLSubscriptionsStreamHandler
143133
)
134+
case "updateTokens":
135+
guard let _ = arguments["tokens"] as? [[String: Any?]] else {
136+
throw APIError.unknown("Invalid token map provided",
137+
"Provide tokens in the \"tokens\" field",
138+
nil)
139+
}
140+
141+
// Tokens already updated
142+
result(nil)
144143
default:
145144
result(FlutterMethodNotImplemented)
146145
}
@@ -160,4 +159,18 @@ public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin {
160159
details: "Operation does not exist"))
161160
}
162161
}
162+
163+
private func updateTokens(_ tokens: [[String: Any?]]) throws {
164+
for tokenMap in tokens {
165+
guard let type = tokenMap["type"] as? String,
166+
let awsAuthType = AWSAuthorizationType(rawValue: type),
167+
let token = tokenMap["token"] as? String? else {
168+
throw APIError.unknown(
169+
"Invalid arguments",
170+
"A valid AWSAuthorizationType and token entry are required",
171+
nil)
172+
}
173+
FlutterAuthProviders.setToken(type: awsAuthType, token: token)
174+
}
175+
}
163176
}

packages/amplify_api/lib/amplify_api.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class AmplifyAPI extends APIPluginInterface {
4141
}
4242

4343
@override
44-
Future<void> addPlugin() async {
44+
Future<APIAuthProviderRefresher> addPlugin() async {
4545
return _instance.addPlugin();
4646
}
4747

packages/amplify_api/lib/method_channel_api.dart

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ class AmplifyAPIMethodChannel extends AmplifyAPI {
4141
final Map<APIAuthorizationType, APIAuthProvider> _authProviders = {};
4242

4343
@override
44-
Future<void> addPlugin() async {
44+
Future<APIAuthProviderRefresher> addPlugin() async {
4545
try {
46-
return await _channel.invokeMethod('addPlugin');
46+
await _channel.invokeMethod<void>('addPlugin');
47+
return _authProviderRefresher;
4748
} on PlatformException catch (e) {
4849
if (e.code == 'AmplifyAlreadyConfiguredException') {
4950
throw const AmplifyAlreadyConfiguredException(
@@ -63,13 +64,45 @@ class AmplifyAPIMethodChannel extends AmplifyAPI {
6364
_authProviders[authProvider.type] = authProvider;
6465
}
6566

67+
/// A token refresher which can be used outside of this plugin (i.e. in DataStore)
68+
/// without exposing the auth providers themselves or their tokens.
69+
Future<void> _authProviderRefresher([
70+
APIAuthorizationType? authType,
71+
]) {
72+
if (_authProviders.isEmpty) {
73+
return SynchronousFuture(null);
74+
}
75+
Future<List<Map<String, dynamic>>> _tokensFuture;
76+
if (authType != null) {
77+
final provider = _authProviders[authType];
78+
if (provider == null) {
79+
throw ApiException(
80+
'No provider registered for type: $authType',
81+
recoverySuggestion:
82+
'Make sure to call addPlugin with a list of auth providers.',
83+
);
84+
}
85+
_tokensFuture = provider.authToken.then((token) => [token]);
86+
} else {
87+
_tokensFuture = _getLatestAuthTokens();
88+
}
89+
return _tokensFuture.then(_updateAuthTokens);
90+
}
91+
6692
/// Retrieves the latest tokens for all registered [_authProviders].
6793
Future<List<Map<String, dynamic>>> _getLatestAuthTokens() {
6894
return Future.wait(_authProviders.values.map(
6995
(authProvider) => authProvider.authToken,
7096
));
7197
}
7298

99+
/// Updates authorization tokens on the platform side.
100+
Future<void> _updateAuthTokens(List<Map<String, dynamic>> tokens) {
101+
return _channel.invokeMethod('updateTokens', {
102+
_authTokensMapKey: tokens,
103+
});
104+
}
105+
73106
@override
74107
GraphQLOperation<T> query<T>({required GraphQLRequest<T> request}) {
75108
Future<GraphQLResponse<T>> response =
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'package:amplify_api/amplify_api.dart';
2+
import 'package:flutter/services.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
void main() {
6+
TestWidgetsFlutterBinding.ensureInitialized();
7+
8+
const apiChannel = MethodChannel('com.amazonaws.amplify/api');
9+
final api = AmplifyAPI();
10+
11+
test('GraphQL Error Decoding', () async {
12+
const message = 'Not Authorized';
13+
const locations = [
14+
{'line': 2, 'column': 3},
15+
{'line': 4, 'column': 5}
16+
];
17+
const path = ['a', 1, 'b'];
18+
const extensions = {
19+
'a': 'blah',
20+
'b': {'c': 'd'}
21+
};
22+
const expected = GraphQLResponseError(
23+
message: message,
24+
locations: [
25+
GraphQLResponseErrorLocation(2, 3),
26+
GraphQLResponseErrorLocation(4, 5),
27+
],
28+
path: <dynamic>[...path],
29+
extensions: <String, dynamic>{...extensions},
30+
);
31+
32+
apiChannel.setMockMethodCallHandler((MethodCall call) async {
33+
return {
34+
'data': null,
35+
'errors': [
36+
{
37+
'message': message,
38+
'locations': locations,
39+
'path': path,
40+
'extensions': extensions,
41+
},
42+
]
43+
};
44+
});
45+
46+
final resp = await api
47+
.query(
48+
request: GraphQLRequest<String>(document: ''),
49+
)
50+
.response;
51+
52+
expect(resp.data, equals(''));
53+
expect(resp.errors.single, equals(expected));
54+
});
55+
}

packages/amplify_api_plugin_interface/lib/amplify_api_plugin_interface.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export 'src/types.dart';
2525
abstract class APIPluginInterface extends AmplifyPluginInterface {
2626
APIPluginInterface({required Object token}) : super(token: token);
2727

28-
Future<void> addPlugin() async {
28+
Future<APIAuthProviderRefresher> addPlugin() async {
2929
throw UnimplementedError('addPlugin() has not been implemented.');
3030
}
3131

0 commit comments

Comments
 (0)