Closed skim037 closed 1 year ago
Hello @skim037 field level auth is not support in DataStore. Defining field level auth may cause DataStore subscription failing which probably the reason you don't receive any event from the observe. Is there any specific use case that you have to use field auth?
Hi @HuiSF I was following a recommendation from this document where is says
To prevent an owner from reassigning their record to another user, protect the owner field (by default owner: String) with a field-level authorization rule. For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob.
I noticed that this document is for GraphQL. Is this not an issue when working with DataStore? Just wanted to make sure there are no security holes with my service.
I am still having issues with subscription even when I got rid of field-level authorization.
I reverted my changes and redeployed with @auth(rules: [{allow: public}])
for all models. But subscription fails with Unauthorized exception as long as I have Amazon Cognito User Pool
as a default authorization strategy. When I reverted back to use API key as a default strategy, I was able to subscribe and get notifications.
I noticed that this document is for GraphQL. Is this not an issue when working with DataStore?
Per my understanding within DataStore scope, model mutation is handled by the underlying sync engine, which have restrict rules setting owner
field. In addition, if you don't define owner
field overrides in your schema, the owner
field won't be exposed in the model gen generated model classes as well, which should be able to avoid accident reassigning of this field.
If you have multiple auth rules defined on a model, I believe you need to configure DataStore to use multi-auth mode, have you tried this?
I'm only using @auth(rules: [{allow: public}])
in schema.graphql
. This is not using multi-auth mode. Is it?
I reverted my changes and redeployed with @auth(rules: [{allow: public}]) for all models. But subscription fails with Unauthorized exception as long as I have Amazon Cognito User Pool as a default authorization strategy.
So you haven't ran amplify update api
to change the default authorization back to API key after your reverted back to {allow: public}
. I think that's expected as the documentation indicates as below
By default, DataStore uses your API's default authorization type specified in the amplifyconfiguration.json/.dart/aws-exports.js file. Every network request sent through DataStore uses that authorization type, regardless of the model's @auth rule. To change the default authorization type, run amplify update api.
If you'd like the DataStore to pick up an appropriate auth strategy from your configuration (for your use case, userpool as default, API key as an addition), I think you'd need to configure the DataStore to multi-auth mode.
Hi @HuiSF
I have enabled multi-auth mode and tried few combinations.
When customers request to delete their account, the request is written to DDB which triggers Lambda function. Lambda function then deletes the user from Cognito pool and update DDB with completed status. The app listens to the DDB stream for completed status and shows delete success message to the customer when this event is received.
Case | Default Auth Type | Additional Auth Types | Auth annotation in schema.graphql | Delete request from App saved to DDB | Delete complete status from Lambda Function saved to DDB | Delete completed event from DDB is notified to App |
---|---|---|---|---|---|---|
1 | Cognito User Pool | API Key, IAM |
@auth(rules: [ { allow: private, provider: userPools }, { allow: private, provider: iam } ]) |
Success | Success | Success |
2 | Cognito User Pool | API Key, IAM |
@auth(rules: [{ allow: private, provider: iam }]) | DataStore failed to perform selective sync (See Error 1) | ||
3 | Cognito User Pool | API Key, IAM |
@auth(rules: [ { allow: owner }, { allow: private, provider: iam } ]) |
Success(but shows warning 1) | Success | Fail. New record is in DDB, but no event fired. |
So the only combination that satisfied my use case was case 1. I added IAM auth type so that I can update DDB from Lambda function using the approach shown here (https://docs.amplify.aws/cli/function/#iam-authorization).
Question 1
Regarding the error in case 2, It looks like DataStore selective sync doesn't work without userPools
provider. Is this expected?
Question 2 Regarding the warning in case 3, Can I safely ignore this warning since DataStore doesn't support field-level auth?
Per my understanding within DataStore scope, model mutation is handled by the underlying sync engine, which have restrict rules setting owner field. In addition, if you don't define owner field overrides in your schema, the owner field won't be exposed in the model gen generated model classes as well, which should be able to avoid accident reassigning of this field.
Question 3
I want to use use owner
auth strategy. But it seems impossible to use this auth strategy in DataStore with subscription since it doesn't support field-level auth. Can you please confirm if this is really the case? If so, is this by design?
To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all required fields, make the other fields nullable, or disable subscriptions by setting it to public or off. https://docs.amplify.aws/cli/graphql/authorization-rules/#field-level-authorization-rules
W/amplify:aws-api( 4372): Websocket connection failed.
W/amplify:aws-api( 4372): A subscription error occurred.
W/amplify:aws-api( 4372): ApiException{message=Connection failed., cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}
W/amplify:aws-api( 4372): at com.amplifyframework.api.aws.SubscriptionEndpoint.requestSubscription(SubscriptionEndpoint.java:145)
W/amplify:aws-api( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation.dispatchRequest(MutiAuthSubscriptionOperation.java:113)
W/amplify:aws-api( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation.$r8$lambda$iziEcYpvlINdYbit2it7fDbbt8A(Unknown Source:0)
W/amplify:aws-api( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation$$ExternalSyntheticLambda4.run(Unknown Source:2)
W/amplify:aws-api( 4372): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
W/amplify:aws-api( 4372): at java.util.concurrent.FutureTask.run(FutureTask.java:264)
W/amplify:aws-api( 4372): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
W/amplify:aws-api( 4372): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
W/amplify:aws-api( 4372): at java.lang.Thread.run(Thread.java:1012)
W/3beagles.chummy( 4372): Long monitor contention with owner pool-26-thread-11 (5017) at void com.amplifyframework.api.aws.SubscriptionEndpoint.requestSubscription(com.amplifyframework.api.graphql.GraphQLRequest, com.amplifyframework.api.aws.AuthorizationType, com.amplifyframework.core.Consumer, com.amplifyframework.core.Consumer, com.amplifyframework.core.Consumer, com.amplifyframework.core.Action)(SubscriptionEndpoint.java:146) waiters=0 in void com.amplifyframework.api.aws.SubscriptionEndpoint.requestSubscription(com.amplifyframework.api.graphql.GraphQLRequest, com.amplifyframework.api.aws.AuthorizationType, com.amplifyframework.core.Consumer, com.amplifyframework.core.Consumer, com.amplifyframework.core.Consumer, com.amplifyframework.core.Action) for 10.203s
E/amplify:aws-datastore( 4372): Failure encountered while attempting to start API sync.
E/amplify:aws-datastore( 4372): DataStoreException{message=DataStore subscriptionProcessor failed to start., cause=DataStoreException{message=Error during subscription., cause=ApiException{message=Connection failed., cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}, recoverySuggestion=Evaluate details.}, recoverySuggestion=Check your internet.}
E/amplify:aws-datastore( 4372): at com.amplifyframework.datastore.syncengine.Orchestrator.lambda$startApiSync$3$com-amplifyframework-datastore-syncengine-Orchestrator(Orchestrator.java:303)
E/amplify:aws-datastore( 4372): at com.amplifyframework.datastore.syncengine.Orchestrator$$ExternalSyntheticLambda5.subscribe(Unknown Source:2)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.operators.completable.CompletableCreate.subscribeActual(CompletableCreate.java:40)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.core.Completable.subscribe(Completable.java:2850)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.operators.completable.CompletablePeek.subscribeActual(CompletablePeek.java:51)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.core.Completable.subscribe(Completable.java:2850)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.operators.completable.CompletablePeek.subscribeActual(CompletablePeek.java:51)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.core.Completable.subscribe(Completable.java:2850)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.operators.completable.CompletablePeek.subscribeActual(CompletablePeek.java:51)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.core.Completable.subscribe(Completable.java:2850)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.run(CompletableSubscribeOn.java:64)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:614)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
E/amplify:aws-datastore( 4372): at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56)
E/amplify:aws-datastore( 4372): at java.util.concurrent.FutureTask.run(FutureTask.java:264)
E/amplify:aws-datastore( 4372): at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
E/amplify:aws-datastore( 4372): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
E/amplify:aws-datastore( 4372): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
E/amplify:aws-datastore( 4372): at java.lang.Thread.run(Thread.java:1012)
E/amplify:aws-datastore( 4372): Caused by: DataStoreException{message=Error during subscription., cause=ApiException{message=Connection failed., cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}, recoverySuggestion=Evaluate details.}
E/amplify:aws-datastore( 4372): at com.amplifyframework.datastore.appsync.AppSyncClient.lambda$subscription$3(AppSyncClient.java:331)
E/amplify:aws-datastore( 4372): at com.amplifyframework.datastore.appsync.AppSyncClient$$ExternalSyntheticLambda1.accept(Unknown Source:4)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation.emitErrorAndCancelSubscription(MutiAuthSubscriptionOperation.java:178)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation.lambda$dispatchRequest$2$com-amplifyframework-api-aws-MutiAuthSubscriptionOperation(MutiAuthSubscriptionOperation.java:135)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation$$ExternalSyntheticLambda0.accept(Unknown Source:4)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.SubscriptionEndpoint.requestSubscription(SubscriptionEndpoint.java:144)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation.dispatchRequest(MutiAuthSubscriptionOperation.java:113)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation.$r8$lambda$iziEcYpvlINdYbit2it7fDbbt8A(Unknown Source:0)
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.MutiAuthSubscriptionOperation$$ExternalSyntheticLambda4.run(Unknown Source:2)
E/amplify:aws-datastore( 4372): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
E/amplify:aws-datastore( 4372): at java.util.concurrent.FutureTask.run(FutureTask.java:264)
E/amplify:aws-datastore( 4372): ... 3 more
E/amplify:aws-datastore( 4372): Caused by: ApiException{message=Connection failed., cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}
E/amplify:aws-datastore( 4372): at com.amplifyframework.api.aws.SubscriptionEndpoint.requestSubscription(SubscriptionEndpoint.java:145)
E/amplify:aws-datastore( 4372): ... 8 more
W/amplify:aws-datastore( 4372): API sync failed - transitioning to LOCAL_ONLY.
W/amplify:aws-datastore( 4372): DataStoreException{message=DataStore subscriptionProcessor failed to start., cause=DataStoreException{message=Error during subscription., cause=ApiException{message=Connection failed., cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}, recoverySuggestion=Evaluate details.}, recoverySuggestion=Check your internet.}
⚠️ WARNING: owners may reassign ownership for the following model(s) and role(s): ActivityTimestamp: [owner], AppBadgeCount: [owner], BlockedProfile: [owner], Conversation: [owner], Delete: [owner], DeviceRegistration: [owner], Filter: [owner], Message: [owner], MessageQueue: [owner], MoreProfile: [owner], NotificationSetting: [owner], Photo: [owner], Profile: [owner], ProfileInterest: [owner], MembershipType: [owner], PreferredLocation: [owner], PushNotificationSetting: [owner], ReportedProfile: [owner], Username: [owner], UserSearchProperty: [owner], Verification: [owner], ViewProfile: [owner]. If this is not intentional, you may want to apply field-level authorization rules to these fields. To read more: https://docs.amplify.aws/cli/graphql/authorization-rules/#per-user--owner-based-data-access.
Hello @skim037 thanks for the details.
Question 1 Regarding the error in case 2, It looks like DataStore selective sync doesn't work without userPools provider. Is this expected?
Looking at the exception in Error 1
, it failed to create subscription. Selective sync is a functionality with in the sync process though, and the sync expression you specified is not involved creating subscription. The exception indicates that it may be caused by network connection recoverySuggestion=Check your internet.
I'm not sure if it's relevant to your auth configuration.
Question 2 Regarding the warning in case 3, Can I safely ignore this warning since DataStore doesn't support field-level auth?
Per my understanding within DataStore scope, model mutation is handled by the underlying sync engine, which have restrict rules setting owner field. In addition, if you don't define owner field overrides in your schema, the owner field won't be exposed in the model gen generated model classes as well, which should be able to avoid accident reassigning of this field.
Yes within DataStore you can ignore this warning. I suggest not to override the owner field in schema in order to hide the owner filed from the public interface.
Question 3 I want to use use owner auth strategy. But it seems impossible to use this auth strategy in DataStore with subscription since it doesn't support field-level auth. Can you please confirm if this is really the case? If so, is this by design?
Yes DataStore doesn't support field-level at this moment.
hi @HuiSF
The auth mode I tried is
default: Cognito User Pool
Additional: API Key, IAM
My model is configured to use
@auth(rules: [{ allow: public }])
And I have enabled muti-auth in my code
AmplifyDataStore(
modelProvider: ModelProvider.instance,
authModeStrategy: AuthModeStrategy.multiAuth,
When I run the app, I get No authorization
error. It looks like API Key
access is not allowed when using Cognito User Pool
as a default auth mode. Is this a correct statement?
Problem with this approach is that, in order to use owner
auth annotation, Cognito User Pool
has to be the default auth mode (see issue I reported before).
So this makes it impossible to use owner
auth annotation for one model and public
auth annotation for another model.
Is this the proper intention?
Hi, sorry for lacking activities on this issue. I've tested DataStore multi-auth based on a few similar issues in the repo, and found that both amplify-swift (v1) and amplify-android (v1+v2) have issues around multi-auth. This causes common issue that models with public permission cannot work correctly without signing in a user.
I'm going to close this issue as a duplicate, so we can track the progress in https://github.com/aws-amplify/amplify-flutter/issues/1693.
Description
My usecase is to subscribe to a model and process the data when Lambda Function creates a new record. When I apply DataStore authorization, I don't get notification for the records I'm listening to.
Model
Subscription
When the app starts, it subscribes to listen to DDB changes.
Repository
Lambda Function
I'm creating new User record from Lambda function following this guide. https://docs.amplify.aws/cli/function/#iam-authorization
After reading https://docs.amplify.aws/cli/graphql/authorization-rules/#field-level-authorization-rules, I tried something like this.
But it gives me following error.
When the app starts, I do see lots of unauthorized errors.
Categories
Steps to Reproduce
Test Scenario
observeUser
withuserId
12345.userId
12345.Expected behavior
User
model allows read operation for authenticated users. So my expectation is that when Lambda function creates a record withuserId
the app is listening to, the app should be notified.Actual behavior
The app doesn't get notified when Lambda creates a record with
userId
the app is listening to.Screenshots
No response
Platforms
Android Device/Emulator API Level
No response
Environment
Dependencies
Device
iPhone 12
OS
iOS 16.1.2
Deployment Method
Amplify CLI
CLI Version
10.5.2
Additional Context
No response
Amplify Config
NA