Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting error "Not Authorized to access deleteUserData on type Mutation" for cleaning up user data #13796

Closed
3 tasks done
lafeer opened this issue Sep 10, 2024 · 7 comments
Closed
3 tasks done
Assignees
Labels
GraphQL Related to GraphQL API issues pending-maintainer-response Issue is pending a response from the Amplify team. question General question

Comments

@lafeer
Copy link

lafeer commented Sep 10, 2024

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Authentication, GraphQL API, DataStore

Amplify Version

v6

Amplify Categories

auth, storage, function, api

Backend

Amplify CLI

Environment information

# Put output below this line
System:
    OS: Windows 11 10.0.22631
    CPU: (8) x64 Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
    Memory: 1.56 GB / 15.79 GB
  Binaries:
    Node: 19.4.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.19.1 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 9.2.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 128.0.6613.120
    Edge: Chromium (127.0.2651.74)
    Internet Explorer: 11.0.22621.3527
  npmPackages:
    @aws-amplify/datastore-storage-adapter: ^2.1.16 => 2.1.16
    @aws-amplify/react-native: ^1.0.16 => 1.0.16
    @azure/core-asynciterator-polyfill: ^1.0.2 => 1.0.2
    @babel/core: ^7.24.0 => 7.25.2
    @expo/react-native-action-sheet: ^4.0.1 => 4.0.1
    @gorhom/bottom-sheet: ^4.6.1 => 4.6.1
    @react-native-async-storage/async-storage: 1.23.1 => 1.23.1
    @react-native-community/netinfo: 11.3.1 => 11.3.1
    @react-native-masked-view/masked-view: 0.3.1 => 0.3.1
    @react-navigation/bottom-tabs: ^6.5.12 => 6.5.12
    @react-navigation/elements: ^1.3.22 => 1.3.22
    @react-navigation/native: ^6.1.10 => 6.1.10
    @react-navigation/native-stack: ^6.9.18 => 6.9.18
    @shopify/flash-list: 1.6.4 => 1.6.4
    HelloWorld:  0.0.1
    aws-amplify: ^6.0.16 => 6.0.16
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    axios: ^1.6.8 => 1.6.8
    expo: ^51.0.31 => 51.0.31
    expo-blur: ~13.0.2 => 13.0.2
    expo-dev-client: ~4.0.25 => 4.0.25
    expo-font: ~12.0.9 => 12.0.9
    expo-haptics: ~13.0.1 => 13.0.1
    expo-image-picker: ~15.0.7 => 15.0.7
    expo-linear-gradient: ~13.0.2 => 13.0.2
    expo-linking: ~6.3.1 => 6.3.1
    expo-sharing: ~12.0.1 => 12.0.1
    expo-splash-screen: ~0.27.5 => 0.27.5
    expo-status-bar: ~1.12.1 => 1.12.1
    nanoid: ^5.0.5 => 5.0.5 (3.3.7)
    node-html-parser: ^6.1.12 => 6.1.12
    react: 18.2.0 => 18.2.0
    react-native: 0.74.5 => 0.74.5
    react-native-gesture-handler: ~2.16.1 => 2.16.2
    react-native-get-random-values: ~1.11.0 => 1.11.0
    react-native-reanimated: ~3.10.1 => 3.10.1
    react-native-safe-area-context: 4.10.5 => 4.10.5
    react-native-screens: 3.31.1 => 3.31.1
    react-native-svg: 15.2.0 => 15.2.0
    typescript: ^5.3.0 => 5.3.3
  npmGlobalPackages:
    @ant-design/pro-cli: 3.0.1
    @aws-amplify/cli: 12.10.1
    corepack: 0.15.2
    create-expo-app: 2.1.1
    eas-cli: 7.6.2
    gatsby-cli: 5.4.0
    nodemon: 2.0.20
    npm: 9.2.0

Describe the bug

Getting error "Not Authorized to access deleteUserData on type Mutation" for cleaning up user data when following user data cleanup instructions as described here: Steps for cleaning user data for owner based auth schema

schema.graphql

type User
  @model
  @auth(
    rules: [
      { allow: owner }
      { allow: private, operations: [read] }
      { allow: public, operations: [read] }
    ]
  ) {
  userID: ID! @primaryKey
  givenName: String
  familyName: String
  avatar: AWSURL
  avatarColor: String
  followedWishlists: [Wishlist] @manyToMany(relationName: "WishlistUser")
}

type Wishlist
  @model
  @auth(
    rules: [
      { allow: owner, ownerField: "userID" }
      { allow: private, operations: [read] }
      { allow: public, operations: [read] }
    ]
  ) {
  nanoid: ID! @primaryKey
  userID: String!
  name: String!
  description: String
  isPrivate: Boolean!
  items: [Item] @hasMany(indexName: "byWishlist", fields: ["nanoid"])
  followers: [User] @manyToMany(relationName: "WishlistUser")
}

type Item
  @model
  @auth(
    rules: [
      { allow: owner, ownerField: "userID" }
      { allow: private, operations: [read] }
      { allow: public, operations: [read] }
    ]
  ) {
  userID: String!
  id: ID!
  name: String!
  description: String
  price: Float
  imageUrl: AWSURL
  itemUrl: AWSURL
  platform: String
  reservedBy: String
    @auth(
      rules: [
        { allow: owner, ownerField: "userID" }
        { allow: private, operations: [read, update] }
        { allow: public, operations: [read] }
      ]
    )
  purchasedBy: String
    @auth(
      rules: [
        { allow: owner, ownerField: "userID" }
        { allow: private, operations: [read, update] }
        { allow: public, operations: [read] }
      ]
    )
  wishlistID: ID! @index(name: "byWishlist")
}

type Mutation {
  deleteUserData: Boolean!
    @auth(rules: [{ allow: owner }])
    @function(name: "deleteUserData-${env}")
}

deleteUserData lambda function

/* Amplify Params - DO NOT EDIT
	API_WISHWELL_GRAPHQLAPIIDOUTPUT
	API_WISHWELL_ITEMTABLE_ARN
	API_WISHWELL_ITEMTABLE_NAME
	API_WISHWELL_USERTABLE_ARN
	API_WISHWELL_USERTABLE_NAME
	API_WISHWELL_WISHLISTTABLE_ARN
	API_WISHWELL_WISHLISTTABLE_NAME
	ENV
	REGION
	STORAGE_S3WISHWELL_BUCKETNAME
Amplify Params - DO NOT EDIT */

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */

const AWS = require("aws-sdk");
AWS.config.update({ region: process.env.REGION });

const dynamodb = new AWS.DynamoDB.DocumentClient();
const tableName = process.env.API_WISHWELL_ITEMTABLE_NAME;

const THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60;

exports.handler = async (event) => {
  console.log(`EVENT: ${JSON.stringify(event)}`);
  const ownerField = "owner"; // owner is default value but if you specified ownerField on auth rule, that must be specified here
  const identityClaim = "username"; // username is default value but if you specified identityField on auth rule, that must be specified here

  var condition = {
    [ownerField]: {
      ComparisonOperator: "EQ",
      AttributeValueList: [event.identity.claims[identityClaim]],
    },
    _deleted: {
      ComparisonOperator: "NE",
      AttributeValueList: [true],
    },
  };

  await new Promise(async (res) => {
    let LastEvaluatedKey;

    do {
      let queryParams = {
        TableName: tableName,
        ScanFilter: condition,
        ExclusiveStartKey: LastEvaluatedKey,
      };

      const items = await new Promise((resolve) => {
        dynamodb.scan(queryParams, (err, data) => {
          if (err) {
            console.log({ error: "Could not load items: " + err });
            resolve([]);
          } else {
            LastEvaluatedKey = data.LastEvaluatedKey;
            resolve(data.Items);
          }
        });
      });

      const dateNow = new Date();

      if (items.length > 0) {
        let deleteParams = {
          RequestItems: {
            [tableName]: items.map((item) => {
              return {
                PutRequest: {
                  Item: {
                    ...item,
                    _deleted: true,
                    _ttl: dateNow.getTime() / 1000 + THIRTY_DAYS_IN_SECONDS,
                    _version: item._version + 1,
                    _lastChangedAt: dateNow.getTime(),
                    updatedAt: dateNow.toISOString(),
                  },
                },
              };
            }),
          },
        };

        await new Promise((resolve) => {
          dynamodb.batchWrite(deleteParams, (err, data) => {
            resolve();
          });
        });
      }
    } while (LastEvaluatedKey);

    res();
  });

  return true;
};

calling deleteUserData

  const client = generateClient();
  
  const deletedUser = await client.graphql({
      query: deleteUserData,
      variables: {},
    });

Expected behavior

Expecting the deleteUserData function to be successfully called

Reproduction steps

  1. Follow instructions as outlined in: https://gist.github.com/aws-amplify-ops/27954c421bd72930874d48c15c284807
  2. Call deleteUserData as shown in Describe the bug section above.

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

/* eslint-disable */
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "ap-southeast-1",
    "aws_cloud_logic_custom": [
        {
            "name": "getProductDetailsApi",
            "endpoint": "https://xxxxxxx.execute-api.ap-southeast-1.amazonaws.com/dev",
            "region": "ap-southeast-1"
        }
    ],
    "aws_appsync_graphqlEndpoint": "https://xxxxxxxxxxxx.appsync-api.ap-southeast-1.amazonaws.com/graphql",
    "aws_appsync_region": "ap-southeast-1",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "xxx-xxxxxxxxxxxxx",
    "aws_cognito_identity_pool_id": "ap-southeast-1:xxxxxxxxxxxxxxxxxxx",
    "aws_cognito_region": "ap-southeast-1",
    "aws_user_pools_id": "ap-southeast-1_xxxxxxx",
    "aws_user_pools_web_client_id": "xxxxxxxxxxxxxxxxxxx",
    "oauth": {},
    "aws_cognito_username_attributes": [],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "EMAIL"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ],
    "aws_user_files_s3_bucket": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-dev",
    "aws_user_files_s3_bucket_region": "ap-southeast-1"
};


export default awsmobile;

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

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Sep 10, 2024
@cwomack cwomack added the GraphQL Related to GraphQL API issues label Sep 10, 2024
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Sep 10, 2024

Hi @lafeer I think the issue might be using allow: owner as an auth rule. I'm not sure that works on a mutation the same way it does on a model. I'll have to confirm with the team. However, in the meantime, you could try simply allowing authenticated users to perform the mutation (allow: private) and in the lambda logic you are already checking and making use of the event.identity to make sure only to delete the records of the user making the request.

Let me know if that helps!

Also, in case the owner auth rule is expected to work with a custom mutation, what exactly do you expect the auth resolver to do with the owner field before the custom lambda is invoked?

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Sep 10, 2024
@chrisbonifacio chrisbonifacio self-assigned this Sep 10, 2024
@chrisbonifacio chrisbonifacio added question General question pending-community-response Issue is pending a response from the author or community. to-be-reproduced Used in order for Amplify to reproduce said issue and removed pending-triage Issue is pending triage labels Sep 10, 2024
@lafeer
Copy link
Author

lafeer commented Sep 15, 2024

Hi @chrisbonifacio. Thank you for getting back to me on this.

I've tried using allow: private, and I run into the same "Not Authorized to access deleteUserData on type Mutation" as with allow: owner

When I remove allow: owner or allow: private, I end up with the following event object in my lambda, where the identity is null.

{
    "typeName": "Mutation",
    "fieldName": "deleteUserData",
    "arguments": {},
    "identity": null,
    "source": null,
    "request": {
        "headers": {
            "x-forwarded-for": "xxxxxxxxxxxxxxxx",
            "cloudfront-viewer-country": "ID",
            "cloudfront-is-tablet-viewer": "false",
            "x-amzn-requestid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "via": "2.0 xxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)",
            "cloudfront-forwarded-proto": "https",
            "content-length": "75",
            "accept-language": "en-US,en;q=0.9",
            "host": "xxxxxxxxxxxxxxxxx.appsync-api.ap-southeast-1.amazonaws.com",
            "x-forwarded-proto": "https",
            "user-agent": "wishwell/1 CFNetwork/1498.700.2 Darwin/23.6.0",
            "cloudfront-is-mobile-viewer": "false",
            "accept": "*/*",
            "cloudfront-viewer-asn": "xxxxx",
            "cloudfront-is-smarttv-viewer": "false",
            "x-amzn-appsync-is-vpce-request": "false",
            "accept-encoding": "gzip, deflate, br",
            "x-amzn-remote-ip": "103.175.213.136",
            "content-type": "application/json; charset=UTF-8",
            "x-api-key": "xxx-ccccccccccccccccccccccc",
            "x-amz-cf-id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "x-amzn-trace-id": "Root=1-66e66baf-xxxxxxxxxxxxxxxxxxxx",
            "x-amz-user-agent": "aws-amplify/6.0.16 api/1 framework/202",
            "cloudfront-is-desktop-viewer": "true",
            "x-forwarded-port": "443"
        },
        "domainName": null
    },
    "prev": {
        "result": {}
    }
}

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Sep 15, 2024
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Sep 17, 2024

Hi @lafeer the identity will be null because without the allow: private auth rule, you're using an auth mode where an identity doesn't get passed along. Does your schema have a global auth rule at the top?

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Sep 17, 2024
@lafeer
Copy link
Author

lafeer commented Sep 18, 2024

So even with allow: private added like below, I get the "Not Authorized to access deleteUserData on type Mutation".

type Mutation { deleteUserData: Boolean! @auth(rules: [{ allow: private }]) @function(name: "deleteUserData-${env}") }

No, my schema is exactly like it is in the description above.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Sep 18, 2024
@chrisbonifacio
Copy link
Member

Hi @lafeer it seems like your default auth mode is API_KEY according to the config shared:

 "aws_appsync_authenticationType": "API_KEY",

You should try setting the auth mode on the request like so:

const deletedUser = await client.graphql({
     query: deleteUserData,
     variables: {},
   }, 
   {authMode: 'userPool'}
);

This should set the Authorization header to the current authenticated user's Cognito access token rather than the x-api-key header that is currently being applied as shown in the network logs you shared.

@chrisbonifacio chrisbonifacio added pending-community-response Issue is pending a response from the author or community. and removed pending-maintainer-response Issue is pending a response from the Amplify team. to-be-reproduced Used in order for Amplify to reproduce said issue labels Sep 24, 2024
@chrisbonifacio
Copy link
Member

Hi 👋 Closing this as we have not heard back from you. If you are still experiencing this issue and in need of assistance, please feel free to comment and provide us with any information previously requested by our team members so we can re-open this issue and be better able to assist you.

Thank you!

@github-actions github-actions bot removed the pending-community-response Issue is pending a response from the author or community. label Oct 4, 2024
@Bptmn
Copy link

Bptmn commented Nov 14, 2024

Hi,

I got the same issue "Not Authorized to access deleteUserData on type Mutation".
I well followed the doc from aws: Steps to clean up user data

When I test the function from the lambda console, it works.

When I test from my app or from the AppSync query editor, I got the error.
If I set the "allow: private", it works as well, but not with "allow: owner"

My schema:

type Item @model @auth(rules: [{allow: owner}]) {
  ownerId: ID!
  lastUseDateTime: AWSDateTime!
  contentEncrypted: String!
  itemKeyEncrypted: String!
  itemType: String!
  createdAt: AWSDateTime!
}

type Device @model @auth(rules: [{allow: owner}]) {
  id: ID! @primaryKey
  ownerId: ID!
  name: String!
  brand: String!
  model: String!
  systemName: String!
  deviceType: String!
  createdAt: AWSDateTime!
}

type Mutation {
  deleteUserData: Boolean
    @function(name: "deleteUserDataAfterAccountDelete-${env}")
    @auth(rules: [{allow: owner}])
}

My function:

const AWS = require('aws-sdk');
AWS.config.update({ region: process.env.REGION });

const dynamodb = new AWS.DynamoDB.DocumentClient();
const tableName = process.env.API_SIMPLIPASSAMPLIFYG1_ITEMTABLE_NAME;

const THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60;

exports.handler = async (event) => {
    const ownerField = 'owner'; // Default owner field when using allow: owner
    const identityClaim = 'sub'; // Cognito uses 'sub' for user identification by default
    
    // Define the query condition to find items owned by the user
    var condition = {
        [ownerField]: {
            ComparisonOperator: 'EQ',
            AttributeValueList: [event.identity.claims[identityClaim]]
        },
        '_deleted': {
            ComparisonOperator: 'NE',
            AttributeValueList: [true]
        }
    };

    await new Promise(async (res) => {
        let LastEvaluatedKey;

        do {
            let queryParams = {
                TableName: tableName,
                ScanFilter: condition,
                ExclusiveStartKey: LastEvaluatedKey
            };

            const items = await new Promise(resolve => {
                dynamodb.scan(queryParams, (err, data) => {
                    if (err) {
                        console.log({ error: 'Could not load items: ' + err });
                        resolve([]);
                    } else {
                        LastEvaluatedKey = data.LastEvaluatedKey;
                        resolve(data.Items);
                    }
                });
            });

            const dateNow = new Date();

            if (items.length > 0) {
                let deleteParams = {
                    RequestItems: {
                        [tableName]: items.map(item => {
                            return {
                                PutRequest: {
                                    Item: {
                                        ...item,
                                        _deleted: true,
                                        _ttl: Math.floor(dateNow.getTime() / 1000) + THIRTY_DAYS_IN_SECONDS,
                                        _version: item._version + 1,
                                        _lastChangedAt: dateNow.getTime(),
                                        updatedAt: dateNow.toISOString(),
                                    }
                                }
                            };
                        })
                    }
                };

                await new Promise(resolve => {
                    dynamodb.batchWrite(deleteParams, (err, data) => {
                        if (err) {
                            console.log({ error: 'Could not delete items: ' + err });
                        }
                        resolve();
                    });
                });
            }
        } while (LastEvaluatedKey);
        
        res();
    });

    return true;
};

Any idea?

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Nov 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GraphQL Related to GraphQL API issues pending-maintainer-response Issue is pending a response from the Amplify team. question General question
Projects
None yet
Development

No branches or pull requests

4 participants