Closed gpavlov2016 closed 4 days ago
Hi @gpavlov2016 👋 it seems that this may be intended behavior. When I try to deploy the same schema, I get this error:
InvalidDirectiveError: 'listen' operations are specified in addition to 'read'. Either remove 'read' to limit access only to 'listen' or only keep 'read' to grant all get,list,search,listen,sync access.
read
encompasses listen
/subscribe permissions. Can you let us know what your use case is?
For example, do you want the user to be able to subscribe without being able to perform list
queries?
Thanks for looking into this @chrisbonifacio. My scenario is:
When I try with read
access only for public auth, the queries on Android work but the subscriptions fail.
I did some digging into the Android amplify library and it seems like it is related to this comment
hi @gpavlov2016 the read
operation includes listen
access. So the listen
operation in the list here is a no-op
. So this might be a red herring.
Another common reason we see why subscription "come across as failing" is because the GraphQL selection set of the mutation must include all the fields that the subscriber is looking for. Can you share the code where you trigger the mutation and the Android code where you listen to the subscription?
The subscription fails at the authentication stage before I even try to do any mutation, and based on the logs it's trying to use Cognito for authentication. Query operation with the same code succeeds. Here is how the model is defined in the React client:
const schema = a.schema({
Video: a
.model({
title: a.string(),
timeOfDayStart: a.time(),
timeOfDayEnd: a.time(),
dateStart: a.date(),
dateEnd: a.date(),
impressionsTarget: a.integer(),
zipCode: a.string(),
s3Key: a.string(),
thumbnail: a.string(),
isRunning: a.boolean(),
})
.authorization([
a.allow.public().to(['read']),
a.allow.owner().to(['create', 'read', 'update', 'delete']),
]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
apiKeyAuthorizationMode: {}
},
});
And here is how the model is defined in the Android client (autogenerated from using amplify config file).
@ModelConfig(pluralName = "Videos", type = Model.Type.USER, version = 1, authRules = {
@AuthRule(allow = AuthStrategy.PUBLIC, operations = { ModelOperation.READ }),
@AuthRule(allow = AuthStrategy.OWNER, ownerField = "owner", identityClaim = "cognito:username", provider = "userPools", operations = { ModelOperation.CREATE, ModelOperation.READ, ModelOperation.UPDATE, ModelOperation.DELETE })
}, hasLazySupport = true)
public final class Video implements Model {
...
}
And this is the code that calls the subscribe method:
val onCreateSubscription = Amplify.API.subscribe(
ModelSubscription.onCreate(Video::class.java),
{ Log.i("ApiQuickStart", "Subscription established - onCreate") },
{
Log.i("ApiQuickStart", "Video create subscription received: ${(it.data as Video).title}")
addVideoToPlaylist(it.data)
},
{ Log.e("ApiQuickStart", "Subscription failed - onCreate", it) },
{ Log.i("ApiQuickStart", "Subscription completed - onCreate") }
)
For reference, this is the query call that works with the same settings:
Amplify.API.query(
ModelQuery.list(Video::class.java, Video.IS_RUNNING.eq(true)),
{ response ->
val page = response.data
initVideoUris(page.items.toList())
Log.d("refreshItems", page.toString())
Log.i("MyAmplifyApp", "Queried items: $page")
},
{ Log.e("MyAmplifyApp", "Query failure", it) }
)
Error message from logcat:
Subscription failed - onCreate ApiAuthException{message=Token is null, cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.} at com.amplifyframework.api.aws.sigv4.DefaultCognitoUserPoolsAuthProvider.fetchToken(DefaultCognitoUserPoolsAuthProvider.java:81) at com.amplifyframework.api.aws.sigv4.DefaultCognitoUserPoolsAuthProvider.getLatestAuthToken(DefaultCognitoUserPoolsAuthProvider.java:87) at com.amplifyframework.api.aws.auth.AuthRuleRequestDecorator.getAuthToken(AuthRuleRequestDecorator.java:226) at com.amplifyframework.api.aws.auth.AuthRuleRequestDecorator.getIdentityValue(AuthRuleRequestDecorator.java:152) at com.amplifyframework.api.aws.auth.AuthRuleRequestDecorator.decorate(AuthRuleRequestDecorator.java:121) at com.amplifyframework.api.aws.AWSApiPlugin.buildSubscriptionOperation(AWSApiPlugin.java:636) at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:315) at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:295) at com.amplifyframework.api.ApiCategory.subscribe(ApiCategory.java:91) at com.example.androidamplifygen2.MainActivity.subscribe(MainActivity.kt:208) at com.example.androidamplifygen2.MainActivity.onCreate(MainActivity.kt:106) at android.app.Activity.performCreate(Activity.java:8000) at android.app.Activity.performCreate(Activity.java:7984) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 2024-04-08 18:24:07.756 6648-6700 MyAmplifyApp com.example.androidamplifygen2 E Query failure ApiException{message=OkHttp client failed to make a successful request., cause=ApiAuthException{message=Failed to retrieve auth token from Cognito provider., cause=ApiAuthException{message=Token is null, cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}, recoverySuggestion=Check the application logs for details.}, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.} at com.amplifyframework.api.aws.AppSyncGraphQLOperation.dispatchRequest(AppSyncGraphQLOperation.java:109) at com.amplifyframework.api.aws.AppSyncGraphQLOperation.$r8$lambda$s0tPt9Vu7puSi2-I-7S0nxLOkUY(Unknown Source:0) at com.amplifyframework.api.aws.AppSyncGraphQLOperation$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923) Caused by: ApiAuthException{message=Failed to retrieve auth token from Cognito provider., cause=ApiAuthException{message=Token is null, cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.}, recoverySuggestion=Check the application logs for details.} at com.amplifyframework.api.aws.auth.ApiRequestDecoratorFactory.forAuthType(ApiRequestDecoratorFactory.java:127) at com.amplifyframework.api.aws.auth.ApiRequestDecoratorFactory.fromGraphQLRequest(ApiRequestDecoratorFactory.java:100) at com.amplifyframework.api.aws.AppSyncGraphQLOperation.dispatchRequest(AppSyncGraphQLOperation.java:93) at com.amplifyframework.api.aws.AppSyncGraphQLOperation.$r8$lambda$s0tPt9Vu7puSi2-I-7S0nxLOkUY(Unknown Source:0) at com.amplifyframework.api.aws.AppSyncGraphQLOperation$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923) Caused by: ApiAuthException{message=Token is null, cause=null, recoverySuggestion=Sorry, we don't have a suggested fix for this error yet.} at com.amplifyframework.api.aws.sigv4.DefaultCognitoUserPoolsAuthProvider.fetchToken(DefaultCognitoUserPoolsAuthProvider.java:81) at com.amplifyframework.api.aws.sigv4.DefaultCognitoUserPoolsAuthProvider.getLatestAuthToken(DefaultCognitoUserPoolsAuthProvider.java:87) at com.amplifyframework.api.aws.auth.ApiRequestDecoratorFactory.forAuthType(ApiRequestDecoratorFactory.java:125) at com.amplifyframework.api.aws.auth.ApiRequestDecoratorFactory.fromGraphQLRequest(ApiRequestDecoratorFactory.java:100) at com.amplifyframework.api.aws.AppSyncGraphQLOperation.dispatchRequest(AppSyncGraphQLOperation.java:93) at com.amplifyframework.api.aws.AppSyncGraphQLOperation.$r8$lambda$s0tPt9Vu7puSi2-I-7S0nxLOkUY(Unknown Source:0) at com.amplifyframework.api.aws.AppSyncGraphQLOperation$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923)
Hi @gpavlov2016 because the issue seems to be with auth in the android sdk, I am transferring this issue over to the amplify/android repo for better support.
Hi @gpavlov2016, this is amplify android, our team will take a look into the issue
Hey @gpavlov2016 thanks for your patience. I'll take a look into this.
Thanks @mattcreaser ! The title is probably need to change since it's not about the access permissions in amplify-js but about support of api key auth in android based on the triage of the js team
Definitely looks like the issue would be in the Multi-auth subscription operation, but we will know more after we investigate.
Hi @gpavlov2016. I've been looking into this issue and have some updates to share.
The reason Amplify is trying to use the user pool is because that is the default authorization mode for your API. Normally you would get around this by choosing a new auth mode for your request. The current way to do this for Gen2 is using this builder API (we'll be adding a more convenient way to set this soon):
val request = ModelSubscription.onCreate(Video::class.java) as AppSyncGraphQLRequest
val apiKeyRequest = request.newBuilder()
.authorizationType(AuthorizationType.API_KEY)
.build<Video>()
val onCreateSubscription = Amplify.API.subscribe(apiKeyRequest, ...)
However, while testing this out I actually found a bug in Amplify's handling of multiple auth rules for subscriptions, so the above is not working as expected. I'll work on a fix for that, but in the meantime you can actually get the desired behaviour by requesting a multi-auth subscription:
val request = ModelSubscription.onCreate(Video::class.java) as AppSyncGraphQLRequest
val multiAuthRequest = request.newBuilder()
.requestAuthorizationStrategyType(AuthModeStrategyType.MULTIAUTH)
.build<Video>()
val onCreateSubscription - Amplify.API.subscribe(multiAuthRequest, ...)
That should allow you to subscribe to the video model without logging in. I'll update this issue again once the fix to directly use API_KEY in this situation becomes available.
The bug mentioned above was fixed in Amplify Android 2.20.0.
We also have an in-progress feature to improve the experience for setting the auth mode for a request that will be included in a future release.
Closing this issue now!
This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one.
Before opening, please confirm:
JavaScript Framework
Next.js
Amplify APIs
Authentication, GraphQL API
Amplify Version
v6
Amplify Categories
auth
Backend
Amplify Gen 2 (Preview)
Environment information
Describe the bug
Combining both
read
andlisten
permissions doesn't work.Either
listen
orread
on their own do workExpected behavior
After saving the file with sandbox running the model is deployed
Reproduction steps
a.allow.public().to(['read', 'listen'])
to amplify data\resource.tsCode Snippet
Log output
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
No response
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
No response