aws-amplify / amplify-codegen

Amplify Codegen is a JavaScript toolkit library for frontend and mobile developers building Amplify applications.
Apache License 2.0
60 stars 61 forks source link

Groups authorization not working as expected for GraphQL transformer v2 - Not Authorized #387

Open malcomm opened 2 years ago

malcomm commented 2 years ago

Before opening, please confirm:

How did you install the Amplify CLI?

npm

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

v16.13.2

Amplify CLI Version

7.6.12

What operating system are you using?

macos

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

no

Amplify Categories

auth, api

Amplify Commands

Not applicable

Describe the bug

Getting:

Not Authorized to access listFoo on type Query

When performing a list on the model

this.apiService.ListFoo();

Expected behavior

Group authentication should work as documented

Reproduction steps

  1. auth rule for groups
  2. cognito user with admin
  3. try to access model data restricting to admin group

GraphQL schema(s)

```graphql # Put schemas below this line type Foo @model @auth(rules: [ { allow: groups, groups: ["admin"]}, { allow: groups, groupsField: "groupsCanAccess", operations: [read] } ]) { name: String! groupsCanAccess: [String] } ```

Log output

``` # Put your logs below this line error: Error: Uncaught (in promise): Object: {"data":{"listFoos":null},"errors":[{"path":["listFoos"],"data":null,"errorType":"Unauthorized","errorInfo":null,"locations":[{"line":2,"column":3,"sourceName":null}],"message":"Not Authorized to access listFoos on type Query"}]} at qe (polyfills.js:1:150135) at polyfills.js:1:149161 at c (main.js:1:251763) at S.invoke (polyfills.js:1:140059) at Object.onInvoke (main.js:1:147119) at S.invoke (polyfills.js:1:139998) at S.run (polyfills.js:1:135092) at polyfills.js:1:150973 at S.invokeTask (polyfills.js:1:140745) at Object.onInvokeTask (main.js:1:146929) ```

Additional information

No response

danielleadams commented 2 years ago

@malcomm - can you give more specifics with how you set up your auth and user groups? Also, how are you making your mutations/create requests? This feature works as expected, so I think you are setting up auth and/or creating records with the incorrect access. Thank you.

malcomm commented 2 years ago

@danielleadams - note: this project was previously a transformer v1 implementation.

My setup:

this.apiService.ListFoo();

malcomm commented 2 years ago

@danielleadams - I even deleted my environment and started over from a brand new environment. Still not working. I've tried multiple models to query and no matter what I do the groups auth is not work.

danielleadams commented 2 years ago

@malcomm What client library are using and is this a mocked or deployed server?

malcomm commented 2 years ago

@danielleadams - I'm using Angular on the frontend and calling the generated APIService for the graphql/DynamoDB backend. I have deployed servers that I'm hitting via the standard amplify push. Here's my amplify status:

> amplify status

    Current Environment: devthree

┌───────────┬─────────────────────────┬───────────┬───────────────────┐
│ Category  │ Resource name           │ Operation │ Provider plugin   │
├───────────┼─────────────────────────┼───────────┼───────────────────┤
│ Auth      │ foof6ac14a6.            │ No Change │ awscloudformation │
├───────────┼─────────────────────────┼───────────┼───────────────────┤
│ Api       │ fooapi                  │ No Change │ awscloudformation │
├───────────┼─────────────────────────┼───────────┼───────────────────┤
│ Analytics │ fooui                   │ No Change │ awscloudformation │
└───────────┴─────────────────────────┴───────────┴───────────────────┘

Foo is not the name on the resources, just omitted those to keep it generic for the ticket. I'm happy to supply the specifics of my environments via other channels.

Also, here's a very simple model that I'm experiencing this error:

type IssueComment
  @model
  @auth(rules: [
    { allow: groups, groups: ["admin", "practitioner"] }
  ])
  # @key(name: "byIssue", fields: ["issueID"])
{
  id: ID!
  issueID: ID! @index(name: "byIssue")
  createdAt: AWSDateTime
  updatedAt: AWSDateTime
  author: String
  comment: String
}
malcomm commented 2 years ago

@danielleadams - also of note: it looks like the permissions are setup correctly from the API perspective:

> amplify status api -acm IssueComment

userPools:staticGroup:admin
  ┌───────────┬────────┬──────┬────────┬────────┐
  │  (index)  │ create │ read │ update │ delete │
  ├───────────┼────────┼──────┼────────┼────────┤
  │    id     │  true  │ true │  true  │  true  │
  │  issueID  │  true  │ true │  true  │  true  │
  │ createdAt │  true  │ true │  true  │  true  │
  │ updatedAt │  true  │ true │  true  │  true  │
  │  author   │  true  │ true │  true  │  true  │
  │  comment  │  true  │ true │  true  │  true  │
  └───────────┴────────┴──────┴────────┴────────┘
userPools:staticGroup:practitioner
  ┌───────────┬────────┬──────┬────────┬────────┐
  │  (index)  │ create │ read │ update │ delete │
  ├───────────┼────────┼──────┼────────┼────────┤
  │    id     │  true  │ true │  true  │  true  │
  │  issueID  │  true  │ true │  true  │  true  │
  │ createdAt │  true  │ true │  true  │  true  │
  │ updatedAt │  true  │ true │  true  │  true  │
  │  author   │  true  │ true │  true  │  true  │
  │  comment  │  true  │ true │  true  │  true  │
  └───────────┴────────┴──────┴────────┴────────┘
danielleadams commented 2 years ago

Hey @malcomm - I've been doing some testing on my end, and it appears the ACM is correct. I'm not able to reproduce your issue with the auth setup you've shared. Do you mind running the queries in AppSync to see if we can isolate the issue to either the front end or the backend?

malcomm commented 2 years ago

@danielleadams - I was able to run the query from AppSync when logged into Cognito.

I'm looking through my application now to make sure everything is configured correctly.

malcomm commented 2 years ago

@danielleadams - I looked at my config and setup and it's all pretty standard. The one thing I forgot to mention:

    "aws-amplify": "^4.3.13",

I'm also not using the @aws-amplify/ui-angular package. Instead, I'm handling authentication using my own authentication service. But in the end, it does the same thing:

import { Auth } from 'aws-amplify';

...

  async login(username: string, password: string): Promise<CognitoUser> {
    const user = await Auth.signIn(username, password);
    this.currentUser = user;
    return user;
  }

This was all working great under Transformer v1. Unless I'm doing something just silly, this seems like there's a bug?

danielleadams commented 2 years ago

@malcomm I'm going to transfer this to amplify-js repo so you can chat with that team. Unfortunately, I don't have much knowledge on the library.

chrisbonifacio commented 2 years ago

Hi @malcomm 👋 a couple things to check:

  1. Add this line to your project, where you are calling Amplify.configure and share the logs from the console
Amplify.Logger.LOG_LEVEL = 'DEBUG';
  1. Are you certain the user you are authenticating and making the call with is assigned to one of the user groups specified in the auth rule?

  2. Do you see a difference in behavior/responses between the client and AppSync console?

malcomm commented 2 years ago

@chrisbonifacio

  1. Added the line for debug and I'm seeing the debug output. Here are the lines right before the error:
    [DEBUG] 05:57.7 Credentials - credentials not changed and not expired, directly return
    ConsoleLogger.js:127 [DEBUG] 05:57.8 AuthClass - getting current authenticated user
    ConsoleLogger.js:127 [DEBUG] 05:57.8 AuthClass - get current authenticated userpool user
    ConsoleLogger.js:127 [DEBUG] 05:57.8 Credentials - removing aws-amplify-federatedInfo from storage
  2. Yes, the user has the admin group in Cognito
  3. Yes. My angular client app is throwing the Not Authorized to access listIssueComments on type Query. AppSync console is not.
chrisbonifacio commented 2 years ago

Can you share what the code for the query looks like as well as the network request/response? Want to check if Amplify is including an auth token in the request.

Otherwise, what are the default and additional authentication types for your AppSync API?

malcomm commented 2 years ago

@chrisbonifacio - as for auth types, I'm not sure what you mean by that exactly. I can say this: we are using only Cognito and I was able to login as the same user in AppSync and issue a query. The same query via client/angular is throwing the error.

As for the code that's causing this:

console.log('comments: ', this.apiService.ListIssueComments());

Which is the generated code for graphql:

  async ListIssueComments(
    filter?: ModelIssueCommentFilterInput,
    limit?: number,
    nextToken?: string
  ): Promise<ListIssueCommentsQuery> {
    const statement = `query ListIssueComments($filter: ModelIssueCommentFilterInput, $limit: Int, $nextToken: String) {
        listIssueComments(filter: $filter, limit: $limit, nextToken: $nextToken) {
          __typename
          items {
            __typename
            id
            issueID
            createdAt
            updatedAt
            author
            comment
          }
          nextToken
        }
      }`;
    const gqlAPIServiceArguments: any = {};
    if (filter) {
      gqlAPIServiceArguments.filter = filter;
    }
    if (limit) {
      gqlAPIServiceArguments.limit = limit;
    }
    if (nextToken) {
      gqlAPIServiceArguments.nextToken = nextToken;
    }
    const response = (await API.graphql(
      graphqlOperation(statement, gqlAPIServiceArguments)
    )) as any;
    return <ListIssueCommentsQuery>response.data.listIssueComments;
  }
chrisbonifacio commented 2 years ago

For your appsync authentication types, you can check either the aws-exports.js or the backend-config.json files generated by the CLI.

For example, this is my backend-config for my AppSync resource:

"authConfig": {
  "defaultAuthentication": {
    "authenticationType": "API_KEY",
    "apiKeyConfig": {
      "apiKeyExpirationDays": 7
    }
  },
  "additionalAuthenticationProviders": [
    {
      "authenticationType": "AMAZON_COGNITO_USER_POOLS",
      "userPoolConfig": {
        "userPoolId": "authissue9535f0ef735e"
      }
    }
  ]
}

But, if your default auth type is Cognito User Pools, then you should not be getting an Unauthorized error.

Can you try adjusting your API.graphql call to include an authMode of "AMAZON_COGNITO_USER_POOLS" to make sure there is a Cognito token being set in the authorization header?

example:

  const response = await API.graphql({
    ...graphqlOperation(statement, gqlAPIServiceArguments),
    authMode: "AMAZON_COGNITO_USER_POOLS",
  });
malcomm commented 2 years ago

@chrisbonifacio - I could not make that change because the generated code doesn't have that as an option (at least not the way you've got it - causes compiler errors). I was trying to make it work, but didn't have time. Feels like it's not the correct way of passing authMode.

malcomm commented 2 years ago

@chrisbonifacio - just to be sure, I don't have a path to move forward here.

chrisbonifacio commented 2 years ago

It works in my angular app, had to change it in the API.service.ts file. But, I think you're right, even if you got it to work, this isn't an ideal way to deal with this. The file would be overwritten every time you push and you'd have to adjust the authMode for each mutation function. The auto-generated functions don't accept an argument for you to set the authMode so the only way around it (that I'm aware of) would be to change your codegen configuration to generate typescript files instead of graphql files (from choosing angular) and running the mutations similar to this:

// app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { API } from 'aws-amplify';
import { IssueComment } from './API.service';
import { createIssueComment } from 'src/graphql/mutations';
import { listIssueComments } from 'src/graphql/queries';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  title = 'amplify-angular-app';
  public createForm: FormGroup;

  /* declare IssueComments variable */
  public IssueComments: Array<IssueComment> = [];

  constructor(private fb: FormBuilder) {
    this.createForm = this.fb.group({
      author: ['', Validators.required],
      comment: ['', Validators.required],
      issueID: ['1234', Validators.required],
    });
  }

  async ngOnInit() {
    /* fetch IssueComments when app loads */
    const event = (await API.graphql({
      query: listIssueComments,
      authMode: 'AMAZON_COGNITO_USER_POOLS',
    })) as any;

    this.IssueComments = event.data.listIssueComments.items as IssueComment[];
  }

  public async onCreate(IssueComment: IssueComment) {
    try {
      await API.graphql({
        query: createIssueComment,
        variables: {
          input: {
            ...IssueComment,
          },
        },
        authMode: 'AMAZON_COGNITO_USER_POOLS',
      });
      console.log('item created!');
      this.createForm.reset();
    } catch (e) {
      console.log('error creating IssueComment...', e);
    }
  }
}
malcomm commented 2 years ago

@chrisbonifacio - right, this is not how things are supposed to work, so we are agreed. This is a bug.

chrisbonifacio commented 2 years ago

I don't think it's a bug because the codegen is working as intended but it should be updated to add an argument for setting the authMode. I'm going to transfer this over to the CLI repo since this is more of a codegen issue.

yeung-wah commented 2 years ago

Hi @malcomm, can we try one more thing to see if this solves the problem. In the aws-exports.js file, what is the value of the aws_appsync_authenticationType field? I was able to reproduce the same 401 error, and that was caused by aws_appsync_authenticationType being set to other auth types than AMAZON_COGNITO_USER_POOLS. Once I changed the value to AMAZON_COGNITO_USER_POOLS, it starts to work for me. Can you try and see if it works for you?

Doc reference for aws_appsync_authenticationType: https://docs.amplify.aws/lib/graphqlapi/create-or-re-use-existing-backend/q/platform/js/#re-use-existing-appsync-graphql-api

aokoli commented 2 years ago

I was facing a similar situation locally when groups wasn't working when I was using the amplify mock command. For what it's worth, I did this:

npm i -g @aws-amplify/cli  // This performed an upgrade from 7.6.23 → 8.0.3  
npm ci  // deleted and re-installed my node_modules

groups started working again with amplify mock

Mavlarn commented 2 years ago

I also met this problem, after I added "authMode: 'AMAZON_COGNITO_USER_POOLS'", it can query successfully.

nomadev21 commented 1 year ago

As per today I experienced this bug using latest Amplify cli v10.8.1 with React.js v18 I solved it by modifying the aws-export.js file like this post mentioned: https://github.com/aws-amplify/amplify-codegen/issues/387#issuecomment-1092177790 but every time I run amplify push api, the bug comes back

In the AppSync console settings, i tried switching the AMAZON_COGNITO_USER_POOLS as Default authorization mode and put the other method I was using as default in the Additional authorization providers sections. But that did not fix it. Still need to change aws-export.js after each push

Changing the default auth mode with the cli works!

DomGarza commented 1 month ago

It works in my angular app, had to change it in the API.service.ts file. But, I think you're right, even if you got it to work, this isn't an ideal way to deal with this. The file would be overwritten every time you push and you'd have to adjust the authMode for each mutation function. The auto-generated functions don't accept an argument for you to set the authMode so the only way around it (that I'm aware of) would be to change your codegen configuration to generate typescript files instead of graphql files (from choosing angular) and running the mutations similar to this:

// app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { API } from 'aws-amplify';
import { IssueComment } from './API.service';
import { createIssueComment } from 'src/graphql/mutations';
import { listIssueComments } from 'src/graphql/queries';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  title = 'amplify-angular-app';
  public createForm: FormGroup;

  /* declare IssueComments variable */
  public IssueComments: Array<IssueComment> = [];

  constructor(private fb: FormBuilder) {
    this.createForm = this.fb.group({
      author: ['', Validators.required],
      comment: ['', Validators.required],
      issueID: ['1234', Validators.required],
    });
  }

  async ngOnInit() {
    /* fetch IssueComments when app loads */
    const event = (await API.graphql({
      query: listIssueComments,
      authMode: 'AMAZON_COGNITO_USER_POOLS',
    })) as any;

    this.IssueComments = event.data.listIssueComments.items as IssueComment[];
  }

  public async onCreate(IssueComment: IssueComment) {
    try {
      await API.graphql({
        query: createIssueComment,
        variables: {
          input: {
            ...IssueComment,
          },
        },
        authMode: 'AMAZON_COGNITO_USER_POOLS',
      });
      console.log('item created!');
      this.createForm.reset();
    } catch (e) {
      console.log('error creating IssueComment...', e);
    }
  }
}

Thanks for the clarification. This is correct because aws amplify is deny first by default, so you have to call cognito pool for the authorization, then you can perform group specific CRUD stuff. It is safe though because they cannot change their cognito status because its deny first