【问题标题】:Google App Script oAuth2 to Questrade API Invalid TokenGoogle App 脚本 oAuth2 到 Questrade API 无效令牌
【发布时间】:2021-06-13 13:01:52
【问题描述】:

我正在尝试使用 Google Apps 脚本将 Questrade 帐户信息提取到 Google 表格电子表格中。我从 GitHub (https://github.com/googleworkspace/apps-script-oauth2) 添加了 oAuth2 库,然后主要从示例代码中复制和粘贴(稍作修改)。

奇怪的是这段代码确实有效,但一天后它不再有效并返回以下内容:

Exception: Request failed for https://api01.iq.questrade.com returned code 401. Truncated server response: {"code":1017,"message":"Access token is invalid"} (use muteHttpExceptions option to examine full response)

我的 Google Apps 脚本发布在下面。我只删除了我的 Questrade Client_ID 和 Google Script Script_ID。我的电子表格中有三个按钮,它们已链接到脚本中的函数:

按钮 1 - QT oAuth - 调用 showSidebar

按钮 2 - 加载帐户信息 - 调用 makeRequest

按钮 3 - QT 注销 - 调用注销

通常,我按下 QT Logout 按钮来重置 0Auth2 服务,然后按下 QT oAuth 按钮。这似乎成功地通过了授权过程。然后我按下 Load Account Info 按钮,在 100 次中大约有 99 次我收到无效的访问令牌消息。 我不知道这是否相关,但是当我登录到 Questrades API 中心时,我可以看到脚本在按下 QT oAuth 按钮后添加了授权,但它似乎在大约一分钟后消失了。

脚本:

function getQTService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('QT')

      // Set the endpoint URLs.
      .setAuthorizationBaseUrl('https://login.questrade.com/oauth2/authorize')
      .setTokenUrl('https://login.questrade.com/oauth2/token')

      // Set the client ID and secret.
      .setClientId('Client_ID')
      .setClientSecret(' ')   //there is no client secret but oAuth2 requires one

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

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties()) 
      .setCache(CacheService.getUserCache())
      
      // Set the scopes to request (space-separated for Google services).
      .setScope('read_acc')
            
}

function showSidebar() {
  var QTService = getQTService();
  if (!QTService.hasAccess()) {
    var authorizationUrl = QTService.getAuthorizationUrl();
    var template = HtmlService.createTemplate(
        '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
        'Reopen the sidebar when the authorization is complete.');
    template.authorizationUrl = authorizationUrl;
    var page = template.evaluate();
    SpreadsheetApp.getUi().showSidebar(page);
  } else {
  // ...
  }
}

function authCallback(request) {
  var QTService = getQTService();
  var isAuthorized = QTService.handleCallback(request);
  if (isAuthorized) {
     return HtmlService.createHtmlOutput('Success! You can close this tab.');
    
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}



function makeRequest() {
  var QTService = getQTService();
  var token = QTService.getAccessToken();
  var spreadsheet = SpreadsheetApp.openById("Script_ID");
  // Get account number
  var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts',{
    headers: {
      Authorization: 'Bearer ' + token
    }
  });
  
  var json = response.getContentText();
  var accountdata = JSON.parse(json);

  var j = 0;

  while(j < accountdata.accounts.length) {
    var Account_num = accountdata.accounts[j].number;
    var Account_type = accountdata.accounts[j].type;

    var sheet = spreadsheet.getSheetByName(Account_type);

    // GET CASH BALANCE
    var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts/' + Account_num + '/balances',{
      headers: {
        Authorization: 'Bearer ' + token
      }
    });
  
    json = response.getContentText();
    var balancedata = JSON.parse(json);
  
    var i = 0;
    while(balancedata.perCurrencyBalances[i].currency != 'CAD') {
      i=i+1;
    }

    //send cash value to spreadsheet
    sheet.getRange("G1").setValue(balancedata.perCurrencyBalances[i].cash);

    // GET POSITIONS
    var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts/' + Account_num + '/positions',{
      headers: {
        Authorization: 'Bearer ' + token
      }
    });
  
    json = response.getContentText();
    var positionsdata = JSON.parse(json);
  
    var num_of_positions = positionsdata.positions.length;
    var i = 0;
    while(i < num_of_positions) {  //this loop is not that smart assumes the positions are where I specify, fix later
    
      if(positionsdata.positions[i].symbol == 'VCN.TO'){ 
        sheet.getRange("D5").setValue(positionsdata.positions[i].openQuantity);
      }
      if(positionsdata.positions[i].symbol == 'VUN.TO') {
        sheet.getRange("D6").setValue(positionsdata.positions[i].openQuantity);
      }
      if(positionsdata.positions[i].symbol == 'VIU.TO') {
        sheet.getRange("D7").setValue(positionsdata.positions[i].openQuantity);
      }
      if(positionsdata.positions[i].symbol == 'VEE.TO') {
        sheet.getRange("D8").setValue(positionsdata.positions[i].openQuantity);
      }
      i=i+1;
    }
    j=j+1;
  }
  //send cash value to spreadsheet
 //   sheet.getRange("G1").setValue(data.perCurrencyBalances[i].cash);
  

 
}

function logout() {
  var service = getQTService();
  service.reset();
}




任何关于这里可能出错的建议将不胜感激。

【问题讨论】:

    标签: google-apps-script oauth-2.0


    【解决方案1】:

    我认为您不能依赖使用 api01。我认为您必须从为您提供令牌的 auth 调用中提取 api_server(或者至少我使用带有 refresh_token 的 https://www.questrade.com/api/documentation/getting-started 上的示例进行了此操作)。我对承载的最后 3 个 refresh_token 身份验证产生了 api06 端点。我拿走了你的代码,并通过 oauth 授权和使用 api06 工作正常。

    魔法酱是var api_server = QTService.getToken().api_server;

    【讨论】:

    • 非常感谢您的“魔法酱”似乎成功了。恐怕我不明白 api01 与 api06 有何不同,并且似乎无法在 Questrade 的文档中找到定义,但代码现在可以使用。
    • Questrade 拥有多个响应 API 调用的 API 服务器。在这种情况下,当您验证并接收令牌时,它只会在他们的一台服务器上授权您。在大多数情况下,您只会有一个静态端点,例如api.example.com,但他们可能正在做某种基本的负载平衡,这就是为什么他们会告诉你要与哪个服务器通信。
    • 您可能还想在 createService 调用中添加 .setRefreshUrl('https://login.questrade.com/oauth2/token'),并从 github.com/googleworkspace/apps-script-oauth2/blob/… 复制/使用 withRetry,如果它过期,它将刷新您的令牌(您需要让它搜索“访问令牌无效”而不是“INVALID_SESSION_ID”)。感谢您分享您的示例代码,这对我来说是一个很好的起点!
    • 啊,好吧,现在可以理解为什么原始代码偶尔会起作用(即,当处理我的请求的 API 服务器恰好是 api01 时)。我将研究令牌刷新。再次感谢您的帮助。
    猜你喜欢
    • 2013-05-02
    • 2016-08-03
    • 2013-08-09
    • 2014-03-17
    • 2016-08-17
    • 2022-08-06
    • 1970-01-01
    • 2011-11-27
    • 2017-03-22
    相关资源
    最近更新 更多