aws-samples / aws-marketplace-serverless-saas-integration

Example of serverless integration for SaaS products listed on the AWS Marketplace.
MIT No Attribution
131 stars 76 forks source link

Doing a resolveCustomer() call using temporary credentials. #10

Open slimandslam opened 3 years ago

slimandslam commented 3 years ago

This might be useful for sites that want to have unauthenticated users doing the resolveCustomer() call.

I am successfully generating an AccessKey, SecretKey, and SessionToken associated with a Role that permits the temporary user to make resolveCustomer() calls.

I can prove that these credentials can successfully do a resolveCustomer() call by setting env vars: (without setting these, it can't find credentials).

export AWS_ACCESS_KEY_ID=xxxxx
export AWS_SECRET_ACCESS_KEY=yyyyy
export AWS_SESSION_TOKEN=zzzz

And then doing the relevant CLI call:

aws meteringmarketplace resolve-customer --registration-token asdfa --region us-west-2

This should return an "InvalidToken" error.

Works great. However, I want to do this using the JavaScript SDK v3.

My attempt looks like this:

const tempCredentials =  {
   accessKeyId: AccessKeyId,
   secretAccessKey: SecretAccessKey,
   sessionToken: SessionToken
 };

  const marketplacemetering = new MarketplaceMeteringClient({
    region: 'us-west-2',
    credentials: tempCredentials
  });

  const resolveCustomerParams = {
      RegistrationToken: 'asdfa',
  };

  const resolve = new ResolveCustomerCommand(resolveCustomerParams);

  try {

     const data = await marketplacemetering.send(resolve);
     return data;

  } catch (error)  {

    return error;

  }

The call returns "TypeError: Failed to fetch" which usually means that the Api call is just not setup properly. What I'm expecting is a 400 error of type InvalidToken. Any thoughts?

dblock commented 3 years ago

I am going to speculate that credentials needs to be a credentials object like here. Does that change anything?

const marketplacemetering = new MarketplaceMeteringClient({
   region: 'us-west-2',
   credentials: new Credentials(tempCredentials)
 });

Also double-check that tempCredentials has values.

slimandslam commented 3 years ago

Where do I get the Credentials object in JavaScript SDK Version 3? In SDK v3, the modules are imported piecemeal, e.g.

import { STSClient, AssumeRoleWithWebIdentityCommand } from "@aws-sdk/client-sts";

Your linked example would require (I think) npm install aws-sdk. which would install all of JavaScript SDK v2. Reference: https://www.npmjs.com/package/aws-sdk

dblock commented 3 years ago

Ok, I am wrong.

First, credentials now are brought in via providers that implement a basic Credentials interface, so you can just use a plain object/hash as you did, or a specific provider, such as fromEnv.

import { fromEnv } from '@aws-sdk/credential-provider-env'

const dataexchange = new DataExchange({
  region: process.env.AWS_REGION || 'us-east-1',
  credentials: fromEnv()
});

So that's not the problem here.

I upgraded a sample in https://github.com/aws-samples/aws-dataexchange-api-samples/pull/45 to AWS SDK v3 and I didn't need to do that anymore.

I'll gladly take a look at your code if you can put it up on GitHub, and tell me how to reproduce the issue?

slimandslam commented 3 years ago

This code fragment should be all that you need. Just start with a sessiontoken, Accesskey, and secretkey that have the ability to make the resolveCustomer() call. If this works properly, you should get a 400 response code and an "InvalidToken" error.

import { MarketplaceMeteringClient, ResolveCustomerCommand } from "@aws-sdk/client-marketplace-metering";

const tempCredentials =  {
  accessKeyId: AccessKeyId,
  secretAccessKey: SecretAccessKey,
  sessionToken: SessionToken
};

 const marketplacemetering = new MarketplaceMeteringClient({
   region: 'us-west-2',
   credentials: tempCredentials
 });

 const resolveCustomerParams = {
     RegistrationToken: 'asdfa',
 };

 const resolve = new ResolveCustomerCommand(resolveCustomerParams);

 try {

    const data = await marketplacemetering.send(resolve);
    console.log(data);

 } catch (error)  {

   console.log(error)

 }
dblock commented 3 years ago

I put this code up in https://github.com/aws-samples/aws-marketplace-api-samples/pull/4 and I get the expected error.

$ node -v
v12.9.0
$ npm -v
6.10.2
$ npm run build
{ InvalidTokenException: Registration token is invalid
    at deserializeAws_json1_1ResolveCustomerCommandError (/Users/dblock/source/aws-mp/aws-marketplace-api-samples/dblock/marketplace-metering-api/node_modules/@aws-sdk/client-marketplace-metering/protocols/Aws_json1_1.ts:535:39)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  name: 'InvalidTokenException',
  '$fault': 'client',
  '$metadata':
   { httpStatusCode: 400,
     requestId: 'efaed4ea-7a2f-421c-b43b-2aedb486244b',
     extendedRequestId: undefined,
     cfId: undefined,
     attempts: 1,
     totalRetryDelay: 0 } }
slimandslam commented 3 years ago

Yup. Works for me too. But when I use the exact same code in a React component, I get the dreaded

TypeError: Failed To Fetch

It claims to be a CORS error (I'm running from Localhost in a web browser):

:3000/signup?x-amzn-marketplace-token=23423dd:1 Access to fetch at 'https://metering.marketplace.us-west-2.amazonaws.com/' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

But if it is a CORS error, why can I successfully make STSClient and CognitoIdentityClient API calls in the web browser just before that without any CORS errors?

dblock commented 3 years ago

Aha, so this is the actual problem. This is because metering does not support/allow CORS, see https://github.com/aws/aws-sdk-js/blob/master/SERVICES.md that says which ones do and which ones don't.

I am not sure what the rationale is for allowing/disallowing CORS. What are you trying to build?

slimandslam commented 3 years ago

My SaaS needs to make a resolveCustomer() call to determine if the incoming x-amzn-marketplace-token token is valid. Since this is a serverless SaaS (eventually, JS running from an S3 bucket), the call is from JS.

dblock commented 3 years ago

I have flagged this to someone in AWS who's close to this service, but I think it's a feature request at this point, so you should probably open a ticket with support, and ask for it, pointing this conversation.

slimandslam commented 3 years ago

I see that the resolveCuatomer() call is in a Lambda function in this project. Do the AWS access credentials have to be tied to a specific account in order to get the right results for resolvecustomer() or can anyone with permission to make the call do it?

https://github.com/aws-samples/aws-marketplace-serverless-saas-integration/blob/master/src/register-new-subscriber.js

dblock commented 3 years ago

@gjoshevski care to comment on ^?

malfree commented 3 years ago

I see that the resolveCuatomer() call is in a Lambda function in this project. Do the AWS access credentials have to be tied to a specific account in order to get the right results for resolvecustomer() or can anyone with permission to make the call do it?

From https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_ResolveCustomer.html

Note
The API needs to called from the seller account id used to publish the SaaS application to successfully resolve the token.
slimandslam commented 3 years ago

The next (obvious?) question is: It's very common to have development and production in separate AWS accounts. What's the best practice for testing Marketplace integration in that scenario? Can you signup using your development account and then later switch to production?

malfree commented 3 years ago

Our recommendation is to use your prod account to test with test products. We don't have a way to switch accounts. However, be aware throttling limits are per account and any test usage will impact your product usage.