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

How to connect to Esty ? #466

Open
Niji-Bemani opened this issue Nov 19, 2023 · 7 comments
Open

How to connect to Esty ? #466

Niji-Bemani opened this issue Nov 19, 2023 · 7 comments
Assignees

Comments

@Niji-Bemani
Copy link

Hi ! I am trying to use your library to connect to the new Etsy API (v3) that require OAuth2.
I used the Twitter sample, and tried to adapt it for Etsy.

I can generate the authorization url and see the Etsy page to grant access.
However, the redirection fails with this error : Error: Error retrieving token: Invalid authorization header (line 605, file "Service", project "OAuth2"), and I have no idea what part should be changed to make it work.

Here is my code :

var CLIENT_ID = '...';
var CLIENT_SECRET = '...';

/**
 * Authorizes and makes a request to the Etsy API v3
 * OAuth 2.0 Making requests on behalf of users
 * https://developers.etsy.com/documentation/essentials/authentication#step-3-request-an-access-token
 */
function run() {
  var service = getService_();
  if (service.hasAccess()) {
    var url = 'https://api.etsy.com/v3/application/openapi-ping';
    var response = UrlFetchApp.fetch(url, {
      headers: {
        Authorization: 'Bearer ' + service.getAccessToken()
      },
      muteHttpExceptions: true
    });
    var result = JSON.parse(response.getContentText());
    Logger.log(JSON.stringify(result, null, 2));
  } else {
    var authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s',
        authorizationUrl);
  }
}

/**
 * Reset the authorization state, so that it can be re-tested.
 */
function reset() {
  getService_().reset();
  PropertiesService.getUserProperties().deleteProperty('code_challenge');
  PropertiesService.getUserProperties().deleteProperty('code_verifier');
}

/**
 * Configures the service.
 */
function getService_() {
  pkceChallengeVerifier();
  var userProps = PropertiesService.getUserProperties();
  return OAuth2.createService('Etsy')
  // Set the endpoint URLs.
      .setAuthorizationBaseUrl('https://www.etsy.com/oauth/connect')
      .setTokenUrl(
          'https://api.etsy.com/v3/public/oauth/token?code_verifier=' + userProps.getProperty('code_verifier'))

  // Set the client ID and secret.
      .setClientId(CLIENT_ID)
      .setClientSecret(CLIENT_SECRET)

  // Set the name of the callback function that should be invoked to
  // complete the OAuth flow.
      .setCallbackFunction('authCallback')

  // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(userProps)

  // Set the scopes to request (space-separated).
      .setScope('address_r address_w')

  // Add parameters in the authorization url
      .setParam('response_type', 'code')
      .setParam('code_challenge_method', 'S256')
      .setParam('code_challenge', userProps.getProperty('code_challenge'))

      .setTokenHeaders({
        'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET),
        'Content-Type': 'application/x-www-form-urlencoded',
      });
}

/**
 * Handles the OAuth callback.
 */
function authCallback(request) {
  var service = getService_();
  var authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
}

/**
 * Logs the redict URI to register.
 */
function logRedirectUri() {
  Logger.log(OAuth2.getRedirectUri());
}

/**
 * Generates code_verifier & code_challenge for PKCE
 */
function pkceChallengeVerifier() {
  var userProps = PropertiesService.getUserProperties();
  if (!userProps.getProperty('code_verifier')) {
    var verifier = '';
    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';

    for (var i = 0; i < 128; i++) {
      verifier += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    var sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier);

    var challenge = Utilities.base64Encode(sha256Hash)
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');
    userProps.setProperty('code_verifier', verifier);
    userProps.setProperty('code_challenge', challenge);
  }
}
@Khnaz35
Copy link

Khnaz35 commented Nov 19, 2023

Based on the information from Etsy's API documentation, the error you're encountering might be related to how the OAuth token is requested. Here are the key points to consider for the OAuth token request:

POST Request Format: The access token should be requested using a POST request to https://api.etsy.com/v3/public/oauth/token​​.

Request Body Parameters: The request body must include the following parameters in application/x-www-form-urlencoded format:

grant_type: Must always be authorization_code.
client_id: Your Etsy App API Key string.
redirect_uri: The same redirect_uri value used in the prior authorization code request.
code: The authorization code received from Etsy after granting access.
code_verifier: The PKCE code verifier preimage of the code_challenge used in the prior authorization code request​​.
Header Format: The format for the authorization header in your token request seems correct. It uses the 'Basic' format with the CLIENT_ID and CLIENT_SECRET encoded in Base64. Ensure that this is consistent with Etsy's requirement.

PKCE Verification: Make sure that the PKCE code_verifier and code_challenge are correctly generated and used. The code_verifier should be the preimage of the code_challenge used in the authorization request

@Niji-Bemani
Copy link
Author

Niji-Bemani commented Nov 20, 2023

Thank you for your reply ! I tried to change my code, but without success.

The access token should be requested using a POST request

I removed the parameter ?code_verifier=' + userProps.getProperty('code_verifier') from the token url.
So I have :

.setTokenUrl('https://api.etsy.com/v3/public/oauth/token')

and I set the method to POST (although the default value is post according to the JSDoc) :

.setTokenMethod('POST')

The request body must include the following parameters [...]

I see a specific method for grant_type with :

.setGrantType('authorization_code')

but I don't understand how to set the others parameters. Could you provide an example ?

@Niji-Bemani
Copy link
Author

It seems that the others parameters can be set with setTokenPayloadHandler(tokenHandler)

And a tokenHandler already has some parameters I need :

Name Type Description
code string The authorization code.
client_id string The client ID.
client_secret string The client secret.
redirect_uri string The redirect URI.
grant_type string The type of grant requested.

So, I need to set an additional parameter code_verifier, and remove the client_secret :

function getService_() {
   ...
   .setTokenPayloadHandler(etsyTokenHandler);
}

function etsyTokenHandler(payload) {
  payload.code_verifier = PropertiesService.getUserProperties().getProperty('code_verifier');
  
  if (payload.client_secret) {
    delete payload.client_secret;
  }
  
  return payload;
}

Unfortunately, I am stuck with the same error : Error retrieving token: Invalid authorization header.
I would like to check the information I submit, but I don't know how to see them.

@Khnaz35
Copy link

Khnaz35 commented Nov 22, 2023

try something like this

var CLIENT_ID = '...';  // Replace with your Client ID
var CLIENT_SECRET = '...';  // Replace with your Client Secret

/**
 * Configures the OAuth2 service.
 */
function getService_() {
  pkceChallengeVerifier();
  var userProps = PropertiesService.getUserProperties();
  return OAuth2.createService('Etsy')
    .setAuthorizationBaseUrl('https://www.etsy.com/oauth/connect')
    .setTokenUrl('https://api.etsy.com/v3/public/oauth/token')
    .setClientId(CLIENT_ID)
    .setClientSecret(CLIENT_SECRET)
    .setCallbackFunction('authCallback')
    .setPropertyStore(userProps)
    .setScope('address_r address_w')
    .setParam('response_type', 'code')
    .setParam('code_challenge_method', 'S256')
    .setParam('code_challenge', userProps.getProperty('code_challenge'))
    .setTokenHeaders({
      'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET),
      'Content-Type': 'application/x-www-form-urlencoded',
    })
    .setTokenPayloadHandler(etsyTokenHandler);
}

function etsyTokenHandler(payload) {
  payload.code_verifier = PropertiesService.getUserProperties().getProperty('code_verifier');
  delete payload.client_secret;
  return payload;
}

function authCallback(request) {
  var service = getService_();
  var authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
}

// Include other necessary functions (run, reset, pkceChallengeVerifier) as per your existing code.

@Niji-Bemani
Copy link
Author

Thanks for your help, I have exactly the same code as you, excepted this extra line :

.setGrantType('authorization_code')

But I tried, with and without this line, and I get the same error.

I just saw in Etsy documentation, that I need to register a callback URL :

https://developers.etsy.com/documentation/essentials/authentication#redirect-uris

All redirect_uri parameter values must match a precise callback URL string registered with Etsy. These values can be provided by editing your application at etsy.com/developers/your-apps.

I tried to set https://script.google.com/macros/d/{MY_SCRIPT_ID}/usercallback, but without success. It seems that Etsy does not accept this type or URI, but I have no idea what they want 😓 I contacted the support to get more information about that.

@Khnaz35
Copy link

Khnaz35 commented Nov 23, 2023

Have you gone through this documentions ?
https://developer.etsy.com/documentation/essentials/authentication/

@rcknr
Copy link
Contributor

rcknr commented Dec 14, 2023

@Niji-Bemani I'd be willing to help you but it is such a hustle to get approved for an app on this platform!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants