aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
89 stars 77 forks source link

Opening up access via @auth rule fails in giving read access for unAuth role #1763

Closed dorontal closed 1 year ago

dorontal commented 1 year ago

How did you install the Amplify CLI?

npm -g

If applicable, what version of Node.js are you using?

v18.13.0

Amplify CLI Version

12.2.3

What operating system are you using?

Debian 12

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

No manual changes - using all auto-generated resolvers in this issue, for example.

Describe the bug

This issue may be related to #1760, it may even be the same issue.

I have tried to open up an API model for access by unAuth role by adding { allow: private, operations: [read] } to the model @auth section but I still get an exception when trying to list projects, which says:

Query failed: UnknownException {
  "message": "unable to send GraphQLRequest to client.",
  "underlyingException": "SignedOutException {\n  \"message\": \"No user is currently signed in\"\n}"
}

I have verified first that the listProjects() query works when a user is the owner of the projects and is signed in. But when nobody is signed in it should still work with the above permissions but doesn't.

Here's the model for which I tried to list all instances:

type Project
  @model(subscriptions: { level: off })
  @auth(
    rules: [
      { allow: owner }
      # allow anyone not signed-in can read this model - we will add a filter
      # that only lets public model instances through, in the resolver code
      { allow: public, provider: iam, operations: [read] }
      # allow anyone signed-in can read this model - we will add a filter
      # that only lets public model instances through, in the resolver code
      { allow: private, operations: [read] }
    ]
  ) {
  id: ID!
  owner: ID @index(name: "projectsByOwner", sortKeyFields: ["updatedAt"])
            @auth(rules: [
              { allow: owner, operations: [create, read, delete] }
              { allow: public, provider: iam, operations: [read] }
              { allow: private, operations: [read] }
            ])
  user: User! @belongsTo(fields: ["owner"])
  title: String!
  visibility: ProjectVisibility!
  license: ProjectLicense!
  description: String
  artist: String
  coverartUrl: String
  parentProjectId: ID @index(name: "projectsByParentProject", sortKeyFields: ["updatedAt"])
  forks: [Project] @hasMany(indexName: "projectsByParentProject", fields: ["id"])
  nPlays: Int
  nTracks: Int
  nForks: Int
  duration: Float
  tracks: [Track] @hasMany(indexName: "tracksByProject", fields: ["id"])
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
}

and here is the listProjects() code:

  Future<List<Project>> listProjects() async {
    log('listProjects()');
    try {
      final request = ModelQueries.list(Project.classType);
      final response = await Amplify.API.query(request: request).response;

      final projects = response.data?.items;
      if (projects == null) {
        log('errors: ${response.errors}');
        return const [];
      }
      List<Project> returnedProjects = [];
      for (var project in projects) {
        if (project != null) returnedProjects.add(project);
      }
      return returnedProjects;
    } on ApiException catch (e) {
      log('Query failed: $e');
      rethrow;
    }
  }

Expected behavior

After adding the @auth rule above I expected unAuth role to be able to list projects, i.e, I expected to be able to successfully execute the listProjects query with some results even when no user has yet signed into the application.

Reproduction steps

  1. Use a similar schema model, perhaps a simplified one - the important thing is to use the same @auth rules
  2. Create a few instances of the model by some user
  3. Try to list all model instances while nobody is signed in

Project Identifier

860b46330dca368b2540f2cdb87a3485

Log output

``` # Put your logs below this line ```

Additional information

No response

Before submitting, please confirm:

dorontal commented 1 year ago

Hi @AnilMaktala - just trying to provide more info here: I noticed this other issue that may be related here. Please keep in mind that currently the default authorization mode is Cognito and also that the search table queries do work. Also: many other queries have worked for me with this schema and with Cognito as the default authorization mode. This issue is for the case when an unauth IAM user tries to do a listProjects query. Thanks!

dorontal commented 1 year ago

@AnilMaktala the following is quite interesting / related: I have been trying to "open up" the above schema so that I can continue working on my project, by removing all field level authorization rules and by providing, in the same schema, the rules that open every operation to public and private and owner (except that I did not touch the Search model because it was not related) -- after making all of these permissions changes and opening things up, the same exact issue remains:

Query failed: UnknownException { "message": "unable to send GraphQLRequest to client.", "underlyingException": "SignedOutException {\n \"message\": \"No user is currently signed in\"\n}" }

i.e, same response to the listQueries() call shown above.

dorontal commented 1 year ago

@AnilMaktala I have done one more test to try and open up the authentication restrictions so that I can continue to work with the UX, but even that new test did not succeed. It is illuminating, however, so I'll describe it. In short, removed all authentication code from the VTL resolver -- still, a IAM (unAuth role) cannot listProjects - with this same issue ...

There are 4 resolvers associated with Query.listProjects:

> ls amplify/backend/api/tracktunes/build/resolvers/Query.listProjects.*
amplify/backend/api/tracktunes/build/resolvers/Query.listProjects.auth.1.req.vtl
amplify/backend/api/tracktunes/build/resolvers/Query.listProjects.postAuth.1.req.vtl
amplify/backend/api/tracktunes/build/resolvers/Query.listProjects.req.vtl
amplify/backend/api/tracktunes/build/resolvers/Query.listProjects.res.vtl

I modified the contents of Query.listProjects.auth.1.req.vtl to remove authentication and put the new file in

amplify/backend/api/tracktunes/resolvers/Query.listProjects.auth.1.req.vtl

The new code in that file is just this:

## [Start] Authorization Steps. **
$util.qr($ctx.stash.put("hasAuth", true))
#set( $isAuthorized = true )
#set( $primaryFieldMap = {} )
$util.toJson({"version":"2018-05-29","payload":{}})
## [End] Authorization Steps. **

Still, I get the exact same exception thrown as described above!

This suggests that the problem is probably not with the VTL code, it probably is in the Dart Amplify library code.

dorontal commented 1 year ago

@AnilMaktala I solved the issue and it is no longer an issue! Here's the summary of the solution:

This was not a problem with any VTL resolver code.

This was not a problem with @auth directives in the schema - they were correct.

It was a wrong setup problem with amplifyconfiguration.dart - a new API block has to be created in amplifyconfiguration.dart when using multi-auth -- just copy the block in there with authorizationType: AMAZON_COGNITO_USER_POOLS into a new block in the same API section and change just the authorizationType value in the new block to be "authorizationType": "AWS_IAM", and also change the name of the entire block at the top of it to something different (in this case it was changed to tracktunesGuest). Here are the changes from the OLD section in amplifyconfiguration.dart:

            "awsAPIPlugin": {
                "tracktunes": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://sbtbwxoahfhfviwomoz3eg5t2e.appsync-api.us-east-1.amazonaws.com/graphql",
                    "region": "us-east-1",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS"
                },

to:

            "awsAPIPlugin": {
                "tracktunes": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://sbtbwxoahfhfviwomoz3eg5t2e.appsync-api.us-east-1.amazonaws.com/graphql",
                    "region": "us-east-1",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS"
                },
                "tracktunesGuest": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://sbtbwxoahfhfviwomoz3eg5t2e.appsync-api.us-east-1.amazonaws.com/graphql",
                    "region": "us-east-1",
                    "authorizationType": "AWS_IAM"
                },

Then in your code instead of using

final request = ModelQueries.list(Project.classType);

as in the code above, change it to:

 final request = ModelQueries.list(Project.classType,
          authorizationMode: APIAuthorizationType.iam,
          apiName: 'tracktunesGuest');

And now multi-auth is working allowing guests to read while giving more permissions to signed in users.

This blog helped me solve this issue - extending a thank you to FullstackPho!

tiyberius commented 11 months ago

Thank you @dorontal for the great write-up, this just saved me HOURS of work!! 🙌

ph0ph0 commented 10 months ago

@dorontal @tiyberius Glad you found my blog post useful and happy it is still (somewhat?) relevant almost 4 years after writing it! Thanks so much for the shoutout, really makes it worth it! :)