Skip to content

Commit 59ca5de

Browse files
authored
fix(datastore): initalSync should be successful in case of unauthorized errors (#1299)
* fix(datastore): initalSync should be successful in case of unauthorized errors * fix(datastore): invalid owner field only when there are no claims * fix(datastore): refactor makeCompletionResult logic
1 parent 0d1eb60 commit 59ca5de

File tree

3 files changed

+65
-11
lines changed

3 files changed

+65
-11
lines changed

AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/AuthRuleDecorator.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Amplify
1111
public typealias IdentityClaimsDictionary = [String: AnyObject]
1212

1313
public enum AuthRuleDecoratorInput {
14-
case subscription(GraphQLSubscriptionType, IdentityClaimsDictionary)
14+
case subscription(GraphQLSubscriptionType, IdentityClaimsDictionary?)
1515
case mutation
1616
case query
1717
}
@@ -77,17 +77,30 @@ public struct AuthRuleDecorator: ModelBasedGraphQLDocumentDecorator {
7777
selectionSet = appendOwnerFieldToSelectionSetIfNeeded(selectionSet: selectionSet, ownerField: ownerField)
7878

7979
if case let .subscription(_, claims) = input,
80+
let tokenClaims = claims,
8081
authRule.isReadRestrictingOwner() &&
81-
isNotInReadRestrictingStaticGroup(jwtTokenClaims: claims,
82+
isNotInReadRestrictingStaticGroup(jwtTokenClaims: tokenClaims,
8283
readRestrictingStaticGroups: readRestrictingStaticGroups) {
8384
var inputs = document.inputs
8485
let identityClaimValue = resolveIdentityClaimValue(identityClaim: authRule.identityClaimOrDefault(),
85-
claims: claims)
86+
claims: tokenClaims)
8687
if let identityClaimValue = identityClaimValue {
8788
inputs[ownerField] = GraphQLDocumentInput(type: "String!", value: .scalar(identityClaimValue))
8889
}
8990
return document.copy(inputs: inputs, selectionSet: selectionSet)
9091
}
92+
93+
// TODO: Subscriptions always require an `owner` field.
94+
// We're sending an invalid owner value to receive a proper response from AppSync,
95+
// when there's no authenticated user.
96+
// We should be instead failing early and don't send the request.
97+
// See: https://github.com/aws-amplify/amplify-ios/issues/1291
98+
if case let .subscription(_, claims) = input, authRule.isReadRestrictingOwner(), claims == nil {
99+
var inputs = document.inputs
100+
inputs[ownerField] = GraphQLDocumentInput(type: "String!", value: .scalar(""))
101+
return document.copy(inputs: inputs, selectionSet: selectionSet)
102+
}
103+
91104
return document.copy(selectionSet: selectionSet)
92105
}
93106

AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory {
171171
operationType: .subscription)
172172
documentBuilder.add(decorator: DirectiveNameDecorator(type: subscriptionType))
173173
documentBuilder.add(decorator: ConflictResolutionDecorator())
174+
documentBuilder.add(decorator: AuthRuleDecorator(.subscription(subscriptionType, nil)))
174175
let document = documentBuilder.build()
175176

176177
let awsPluginOptions = AWSPluginOptions(authType: authType)

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator {
122122
"",
123123
dataStoreError)
124124
self.syncErrors.append(syncError)
125+
126+
if self.isUnauthorizedError(syncError) {
127+
self.initialSyncOrchestratorTopic.send(.finished(modelName: modelSchema.name))
128+
}
125129
}
126130
self.initialSyncOperationSinks.removeValue(forKey: modelSchema.name)
127131
self.onReceiveCompletion()
@@ -150,15 +154,16 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator {
150154
}
151155

152156
private func makeCompletionResult() -> Result<Void, DataStoreError> {
153-
guard syncErrors.isEmpty else {
154-
let allMessages = syncErrors.map { String(describing: $0) }
155-
let syncError = DataStoreError.sync(
156-
"One or more errors occurred syncing models. See below for detailed error description.",
157-
allMessages.joined(separator: "\n")
158-
)
159-
return .failure(syncError)
157+
if syncErrors.isEmpty || syncErrors.allSatisfy(isUnauthorizedError) {
158+
return .successfulVoid
160159
}
161-
return .successfulVoid
160+
161+
let allMessages = syncErrors.map { String(describing: $0) }
162+
let syncError = DataStoreError.sync(
163+
"One or more errors occurred syncing models. See below for detailed error description.",
164+
allMessages.joined(separator: "\n")
165+
)
166+
return .failure(syncError)
162167
}
163168

164169
private func dispatchSyncQueriesStarted(for modelNames: [String]) {
@@ -180,3 +185,38 @@ extension AWSInitialSyncOrchestrator: Resettable {
180185
onComplete()
181186
}
182187
}
188+
189+
@available(iOS 13.0, *)
190+
extension AWSInitialSyncOrchestrator {
191+
private typealias ResponseType = PaginatedList<AnyModel>
192+
private func graphqlErrors(from error: GraphQLResponseError<ResponseType>?) -> [GraphQLError]? {
193+
if case let .error(errors) = error {
194+
return errors
195+
}
196+
return nil
197+
}
198+
199+
private func errorTypeValueFrom(graphQLError: GraphQLError) -> String? {
200+
guard case let .string(errorTypeValue) = graphQLError.extensions?["errorType"] else {
201+
return nil
202+
}
203+
return errorTypeValue
204+
}
205+
206+
private func isUnauthorizedError(_ error: DataStoreError) -> Bool {
207+
guard case let .sync(_, _, underlyingError) = error,
208+
let datastoreError = underlyingError as? DataStoreError
209+
else {
210+
return false
211+
}
212+
213+
if case let .api(apiError, _) = datastoreError,
214+
let responseError = apiError as? GraphQLResponseError<ResponseType>,
215+
let graphQLError = graphqlErrors(from: responseError)?.first,
216+
let errorTypeValue = errorTypeValueFrom(graphQLError: graphQLError),
217+
case .unauthorized = AppSyncErrorType(errorTypeValue) {
218+
return true
219+
}
220+
return false
221+
}
222+
}

0 commit comments

Comments
 (0)