aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

S3 Object upload with AppSync - "Missing credentials in config" #3476

Closed elliott-king closed 5 years ago

elliott-king commented 5 years ago

Which Category is your question related to? GraphQL AppSync, and S3 Uploads with Complex Objects.

What AWS Services are you utilizing? AWS Amplify with:

Describe the bug I am attempting to follow the add complex objects tutorial for amplify & appsync. However, I am getting an error: Network error: GraphQL error: Missing credentials in config.

To Reproduce You may notice that I had a previous issue (#3243). The code is mostly the same, although my debugging steps are different.

Relevant section of schema.graphql:

type Photo @model {
    photo_id: ID!
    location_id: ID
    description: String
    file: S3Object
}

type S3Object {
    bucket: String!
    region: String!
    key: String!
}

When I amplify push this, it correctly creates the CreatePhotoInput in the Appsync Graphql API:

input CreatePhotoInput {
    photo_id: ID
    location_id: ID!
    description: String
    file: S3ObjectInput
}

input S3ObjectInput {
    bucket: String!
    region: String!
    key: String!
}

Here is my auth handler:

export function createClient() {
    return new AWSAppSyncClient({
        url: aws_config.aws_appsync_graphqlEndpoint,
        region: aws_config.aws_appsync_region,
        auth: {
            type: aws_config.aws_appsync_authenticationType,
            jwtToken: async () => (await Auth.currentSession()).getAccessToken().getJwtToken(),
        },
        complexObjectCredentials: () => Auth.currentCredentials()
    });
}

And the bit where I actually call the mutation Note that previously, the client was instantiated in the function. It is now passed into the component as a prop. I was instantiating multiple clients at a time in my code, and I think the fix to my previous issue did not like that (probably for the best).

(async () => {
            let file;
            let photo_id;

            if (this.imageInput.current.files.length > 0) {
                let img_file = this.imageInput.current.files[0];
                console.log('file to upload:', img_file);

                // NOTE: Attempting to use the AppSync API w/ complex objects uploading
                const { name: filename, type: mimeType } = img_file;
                const [, , , extension] = /([^.]+)(\.(\w+))?$/.exec(filename);
                console.log('filename, extension', filename, extension);

                const bucket = aws_config.aws_user_files_s3_bucket;
                const region = aws_config.aws_user_files_s3_bucket_region;
                // Have tried with visibility='private'
                const visibility = 'public';
                const {identityId} = await Auth.currentCredentials();

                photo_id = uuid();
                const key = `${visibility}/${identityId}/${photo_id}${extension && '.'}${extension}`;

                file = {
                    bucket, 
                    key,
                    region,
                    mimeType,
                    localUri: img_file,
                };
                console.log('identity id:', identityId);
                console.log('file object:', file);
                console.log('targeting location:', this.props.name);
                console.log('location has id:', this.props.id);
            }

            // From issue 300 for aws-appsync
            // Need some more npm packages? See https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/300
            const who = await this.props.client.query({
                query: gql`query Q {
                    whoAmI
                }`,
                variables: {},
                fetchPolicy: 'network-only',
            });

            const { data: { whoAmI } } = who;
            console.log("who am i:\n", JSON.stringify(JSON.parse(whoAmI), null, 2));

            this.props.client.mutate({
                mutation: gql(createPhoto),
                variables: {
                    input: {
                        photo_id: photo_id,
                        location_id: this.props.id,
                        description: 'First test: this is a photo file',
                        file: file
                    }
                },
                // authmode: 'AMAZON_COGNITO_USER_POOLS'
            })
            .then( result => console.log('result of image upload:', result))
            .catch(err => console.error('error uploading image:', err));
        })();

The catch catches: Error: "Network error: GraphQL error: Missing credentials in config"

NPM dependencies

{
  "name": "freedom-js-app",
  "version": "1.0.0",
  "description": "AWS Freedom JavaScript Example",
  "dependencies": {
    "aws-amplify": "^1.1.28",
    "aws-amplify-react": "^2.3.6",
    "aws-appsync": "file:aws-appsync-1.8.0.tgz",
    "babel-polyfill": "^6.26.0",
    "es6-promise": "^4.2.8",
    "graphql": "^14.3.1",
    "graphql-tag": "^2.10.0",
    "isomorphic-fetch": "^2.2.1",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "uuid": "^3.3.2"
  },
  "devDependencies": {
    "@babel/core": "^7.4.3",
    "@babel/preset-env": "^7.4.3",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "copy-webpack-plugin": "^4.5.2",
    "css-loader": "^2.1.1",
    "react-select": "^2.4.3",
    "style-loader": "^0.23.1",
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.0",
    "webpack-dev-server": "^3.3.1"
  },
  "scripts": {
    "start": "webpack && webpack-dev-server --mode development",
    "build": "webpack"
  }
}

Note that the file:aws-appsync dependency implements the fix for my previous issue.

Expected behavior I expect a file to go to S3, and and corresponding entry in the DynamoDB table connected to the createPhoto mutation.

Desktop (please complete the following information):

Debugging steps I took You can see some of the initial steps I took in my previous issue. In addition, I did the following:

I removed then replaced the auth api and storage aspects, and pushed new ones.

I also updated amplify itself globally.

If I comment out the file field in the mutation, it uploads to DynamoDB but not S3.

I tried adding authmode: 'AMAZON_COGNITO_USER_POOLS', to the mutation. No effect.

(An issue in the aws-appsync-sdk-js repo)[https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/300] also mentions having different versions of aws-sdk. Following the advice there, I changed all sub-dependencies in package-lock to 2.329.0. This also showed no change.

Also possibly related to #1716 and #2706, although the former is old, and the latter is less specific.

Also from the above issue, I created a graphql query for 'whois.' The output is not quite the same as @manueliglesias, but I think it is using the Cognito auth:

who am i:
 {
  "sub": "XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "issuer": "https://cognito-idp.us-east-1.amazonaws.com/[my aws_user_pools_id]",
  "username": "[same as sub]",
  "claims": {
    "sub": "[same val again]",
    "event_id": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY",
    "token_use": "access",
    "scope": "aws.cognito.signin.user.admin",
    "auth_time": 1560722765,
    "iss": "https://cognito-idp.us-east-1.amazonaws.com/[my aws_user_pools_id]",
    "exp": 1560794406,
    "iat": 1560790806,
    "jti": "ZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ",
    "client_id": "XXXX",
    "username": "[same as sub again]"
  },
  "sourceIp": [
    "XXX.XXX.XXX.XXX"
  ],
  "defaultAuthStrategy": "ALLOW",
  "groups": null
}

I hope this is readable, y'all were super helpful last time. Thank you for your time.

elliott-king commented 5 years ago

Stack trace:

error uploading image: Error: "Network error: GraphQL error: Missing credentials in config"
    ApolloError webpack:///./node_modules/apollo-client/errors/ApolloError.js?:40
    error webpack:///./node_modules/apollo-client/core/QueryManager.js?:217
    notifySubscription webpack:///./node_modules/zen-observable/lib/Observable.js?:134
    onNotify webpack:///./node_modules/zen-observable/lib/Observable.js?:165
    error webpack:///./node_modules/zen-observable/lib/Observable.js?:224
    notifySubscription webpack:///./node_modules/zen-observable/lib/Observable.js?:134
    onNotify webpack:///./node_modules/zen-observable/lib/Observable.js?:165
    error webpack:///./node_modules/zen-observable/lib/Observable.js?:224
    discard webpack:///./node_modules/aws-appsync/lib/link/offline-link.js?:397
    discard webpack:///./node_modules/aws-appsync/lib/store.js?:142
    discard webpack:///./node_modules/aws-appsync/lib/store.js?:98
    node_modules Redux
    Babel 11
        tryCatch
        invoke
        method
        step
        default
        Promise
        default
        node_modules
        run
        notify
        flush
elliott-king commented 5 years ago

I resolved this by not using the S3Object AppSync method.

Instead, I simply took two steps:

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.