multimeric / AmplifyCountDirective

Count the number of items in your DynamoDB tables using Amplify
12 stars 1 forks source link

Unable to use count inside a lambda function #10

Open Tedsterh opened 2 years ago

Tedsterh commented 2 years ago

Hi, sorry its me again,

So I am able to call searchTraits and listTraits from inside the function but I cannot call countTraits, I am also unable to use the an apiKey to call the count even though my model has the correct auth on it.

I have tried to remove permissions and add them again, but no luck.

multimeric commented 2 years ago

Can you please post the full error message you get from the lambda?

multimeric commented 2 years ago

When I create a lambda using Amplify, it automatically gets given a policy called amplify-lambda-execution-policy which allows it access to all Query endpoints, so I don't see how the countTraits would be excluded from this.

Tedsterh commented 2 years ago

I just get an unauthorised message

{
      path: [Array],
      data: null,
      errorType: 'Unauthorized',
      errorInfo: null,
      locations: [Array],
      message: 'Not Authorized to access countTrait on type Query'
 }

The auth rules for the model

type Trait @model @searchable @count
  @auth(rules: [
    {allow: owner, ownerField: "userID", operations: [read]},
    {allow: private, provider: iam},
    {allow: public, operations: [read], provider: apiKey},
    {allow: groups, groups: ["User"], operations: [read]},
    {allow: groups, groups: ["Admin"]},
  ]) {}
Tedsterh commented 2 years ago
{
    "Fn::Join": [
        "",
        [
            "arn:aws:appsync:",
            {
                "Ref": "AWS::Region"
            },
            ":",
            {
                "Ref": "AWS::AccountId"
            },
            ":apis/",
            {
                "Ref": "apianimalGraphQLAPIIdOutput"
            },
            "/types/Query/*"
        ]
    ]
}

It has all the right permissions in its policy

multimeric commented 2 years ago

Hi, can you please use code blocks with the correct (graphql) syntax highlighting please? https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks

Can you also post the lambda role document, by finding the lambda function in the console, opening "Configuration", then "Permissions", then "View role document"

Tedsterh commented 2 years ago

Sorry just gone back and edited those Heres the Statements from the role document

"statements": [
        {
            "action": "appsync:GraphQL",
            "effect": "Allow",
            "resource": "arn:aws:appsync:us-east-2:<APP-ID>:apis/<API-ID>/types/Query/*",
            "service": "appsync",
            "source": {
                "index": "0",
                "policyName": "amplify-lambda-execution-policy",
                "policyType": "inline"
            }
        },
        {
            "action": "appsync:GraphQL",
            "effect": "Allow",
            "resource": "arn:aws:appsync:us-east-2:<APP-ID>:apis/<API-ID>/types/Mutation/*",
            "service": "appsync",
            "source": {
                "index": "0",
                "policyName": "amplify-lambda-execution-policy",
                "policyType": "inline"
            }
        }
    ]
multimeric commented 2 years ago

I'm a bit stumped by this, actually. Because this part of the docs suggests that it's the lambda role policy that grants it access to the API, and we can see from what you've posted that it should have access to all Query types.

multimeric commented 2 years ago

Maybe consider asking on one of the Amplify forums and if you find anything I can try to fix it here.

Tedsterh commented 2 years ago

Yes it's very bizarre, I shall open a ticket with them. It's weird because it also doesn't let me me call it with the API key either, only a user pool token

Tedsterh commented 2 years ago

I have the same problem with my queries that have lambda functions attached using the @function

multimeric commented 2 years ago

Oh, if the API key isn't working either than that's a bit less surprising. Can you see if the cognito pool has any policies that might explain why it works properly?

Tedsterh commented 2 years ago

How can I check what permissions a user from a userPool might have? Theres nothing in its CloudFormation about the api

multimeric commented 2 years ago

Try opening the AppSync API in the console and clicking Settings

Tedsterh commented 2 years ago
Screenshot 2022-03-18 at 14 44 26

It looks like it defaults to allow?

multimeric commented 2 years ago

That might relate to it. Can you also show me the AppSync resolver for your working (e.g. listTrait) queries? They should be in your Amplify app: amplify/backend/api/<resource_name>/build/resolvers

Tedsterh commented 2 years ago

It looks like there is 4 different resolvers for the listTrait

multimeric commented 2 years ago

The auth ones are of interest to me.

Tedsterh commented 2 years ago

Okay so in the auth.1

## [Start] Authorization Steps. **
$util.qr($ctx.stash.put("hasAuth", true))
#set( $isAuthorized = false )
#set( $primaryFieldMap = {} )
#if( $util.authType() == "API Key Authorization" )
  #set( $isAuthorized = true )
#end
#if( $util.authType() == "IAM Authorization" )
  #set( $adminRoles = ["us-east-2_<KEY>_Full-access/CognitoIdentityCredentials","us-east-2_<KEY>_Manage-only/CognitoIdentityCredentials","thenftproject24bf3d5f24bf3d5fPostConfirmation-dev"] )
  #foreach( $adminRole in $adminRoles )
    #if( $ctx.identity.userArn.contains($adminRole) && $ctx.identity.userArn != $ctx.stash.authRole && $ctx.identity.userArn != $ctx.stash.unauthRole )
      #return($util.toJson({}))
    #end
  #end
  #if( !$isAuthorized )
    #if( ($ctx.identity.userArn == $ctx.stash.authRole) || ($ctx.identity.cognitoIdentityPoolId == "us-east-2:<POOL_ID>" && $ctx.identity.cognitoIdentityAuthType == "authenticated") )
      #set( $isAuthorized = true )
    #end
  #end
#end
#if( $util.authType() == "User Pool Authorization" )
  #if( !$isAuthorized )
    #set( $staticGroupRoles = [{"claim":"cognito:groups","entity":"User"},{"claim":"cognito:groups","entity":"Admin"}] )
    #foreach( $groupRole in $staticGroupRoles )
      #set( $groupsInToken = $util.defaultIfNull($ctx.identity.claims.get($groupRole.claim), []) )
      #if( $groupsInToken.contains($groupRole.entity) )
        #set( $isAuthorized = true )
        #break
      #end
    #end
  #end
  #if( !$isAuthorized )
    #set( $authFilter = [] )
    #set( $role0 = $util.defaultIfNull($ctx.identity.claims.get("username"), $util.defaultIfNull($ctx.identity.claims.get("cognito:username"), "___xamznone____")) )
    #if( $role0 != "___xamznone____" )
      $util.qr($authFilter.add({"userID": { "eq": $role0 }}))
    #end
    #if( !$authFilter.isEmpty() )
      $util.qr($ctx.stash.put("authFilter", { "or": $authFilter }))
    #end
  #end
#end
#if( !$isAuthorized && $util.isNull($ctx.stash.authFilter) )
$util.unauthorized()
#end
$util.toJson({"version":"2018-05-29","payload":{}})
## [End] Authorization Steps. **
Tedsterh commented 2 years ago

The postAuth.1

## [Start] Sandbox Mode Disabled. **
#if( !$ctx.stash.get("hasAuth") )
  $util.unauthorized()
#end
$util.toJson({})
## [End] Sandbox Mode Disabled. **
multimeric commented 2 years ago

Okay, then I guess it must be the authorization resolver that is telling your lambda it isn't authorized. But I'm surprised it's doing that, because my resolver never calls $util.unauthorized()

Tedsterh commented 2 years ago

Are your resolvers built in the build folder as well? I can't seem to find them in there

multimeric commented 2 years ago

I guess they aren't (which might suggest they aren't hooking into the same process that handles auth). But you can see them in the AppSync console anyway.

Tedsterh commented 2 years ago

In the count resolver on AppSync it doesn't have anything for apiKey

#set( $isAuthorized = false )
#if( $util.authType() == "API Key Authorization" )

#end

and with the one that list traits

#if( $util.authType() == "API Key Authorization" )
  #set( $isAuthorized = true )
#end
Tedsterh commented 2 years ago

And for the IAM on it is slightly different than the list one

#if( $util.authType() == "IAM Authorization" )
  #set( $adminRoles = ["us-east-2_<KEY>_Full-access/CognitoIdentityCredentials","us-east-2_<KEY>_Manage-only/CognitoIdentityCredentials","thenftproject24bf3d5f24bf3d5fPostConfirmation-dev] )
  #foreach( $adminRole in $adminRoles )
    #if( $ctx.identity.userArn.contains($adminRole) && $ctx.identity.userArn != $ctx.stash.authRole && $ctx.identity.userArn != $ctx.stash.unauthRole )
      #return($context.source.traitCount)
    #end
  #end
$util.unauthorized()
#end

which has got this

#if( $util.authType() == "IAM Authorization" )
  #set( $adminRoles = ["us-east-2_<KEY>_Full-access/CognitoIdentityCredentials","us-east-2_<KEY>_Manage-only/CognitoIdentityCredentials","thenftproject24bf3d5f24bf3d5fPostConfirmation-dev"] )
  #foreach( $adminRole in $adminRoles )
    #if( $ctx.identity.userArn.contains($adminRole) && $ctx.identity.userArn != $ctx.stash.authRole && $ctx.identity.userArn != $ctx.stash.unauthRole )
      #return($util.toJson({}))
    #end
  #end
  #if( !$isAuthorized )
    #if( ($ctx.identity.userArn == $ctx.stash.authRole) || ($ctx.identity.cognitoIdentityPoolId == "us-east-2:<POOL_ID>" && $ctx.identity.cognitoIdentityAuthType == "authenticated") )
      #set( $isAuthorized = true )
    #end
  #end
#end
multimeric commented 2 years ago

Okay well it looks like both of them just call unauthorized() no matter what, which explains why this is happening. I wasn't aware that AppSync did this, because those above resolvers were not written by me, they must be auto generated. And also it seems they don't get generated unless you have auth enabled, so I didn't even know they existed.

Tedsterh commented 2 years ago

Ah no worries, is there a way to overwrite those resolvers? Is this something that can be done?

multimeric commented 2 years ago

In theory it should be fixable. I think what's happened is that the @auth directive generates its own resolvers, but it's not propertly doing it for my custom resolver. So I have to work out why.

Tedsterh commented 2 years ago

The function directive made by amplify uses this to handle the auth

        // Create the GraphQL resolvers.
        const resolverId = ResolverResourceIDs.ResolverResourceID(config.resolverTypeName, config.resolverFieldName);
        let resolver = createdResources.get(resolverId);

        const requestTemplate: Array<Expression> = [
          qref(`$ctx.stash.put("typeName", "${config.resolverTypeName}")`),
          qref(`$ctx.stash.put("fieldName", "${config.resolverFieldName}")`),
        ];
        const authModes = [context.authConfig.defaultAuthentication, ...(context.authConfig.additionalAuthenticationProviders || [])].map(
          mode => mode?.authenticationType,
        );
        if (authModes.includes(AuthorizationType.IAM)) {
          const authRoleParameter = (context.stackManager.getParameter(IAM_AUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString;
          const unauthRoleParameter = (context.stackManager.getParameter(IAM_UNAUTH_ROLE_PARAMETER) as cdk.CfnParameter).valueAsString;
          requestTemplate.push(
            qref(
              `$ctx.stash.put("authRole", "arn:aws:sts::${
                cdk.Stack.of(context.stackManager.rootStack).account
              }:assumed-role/${authRoleParameter}/CognitoIdentityCredentials")`,
            ),
            qref(
              `$ctx.stash.put("unauthRole", "arn:aws:sts::${
                cdk.Stack.of(context.stackManager.rootStack).account
              }:assumed-role/${unauthRoleParameter}/CognitoIdentityCredentials")`,
            ),
          );
        }
        requestTemplate.push(obj({}));

Would this be able to be used in the resolver so we can use this is a lambda?

multimeric commented 2 years ago

Hmm, this is part of it, but not all of it, I think.