How to get Hex value from computeHmacSha256Signature method of Google Apps Script? - google-apps-script

I read solution for MD5 below, but I could not quite get it.
get back a string representation from computeDigest(algorithm, value) byte[]
I'd like to create API signature with HMAC-SHA256 hash.
var date = new Date();
var nonce = Math.floor(date.getTime()/1000);
var url = "https://mysweet.com/api/accounts"
var secret = "my_secret";
var signature = Utilities.computeHmacSha256Signature(nonce+url, secret);
but it returns byte array [42, -8, -47, -21, ..], and it cannot be used as API signature directly.
Is there a simple way to get a Hex value from the method? or conversion?

I applied the method you linked to and get:
var sig = signature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');;
So here's a test function:
function testSig() {
var date = new Date();
var message = "Violets are red";
var secret = "my_secret";
var signature = Utilities.computeHmacSha256Signature(message, secret);
var sig = signature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
Logger.log(sig); // fe70fa2e74b3ee0d67aa3c1d5c2844e558fea6802e8cfa58e5d4cbdf8bad25fe
//output from http://www.freeformatter.com/hmac-generator.html#ad-output is:
// fe70fa2e74b3ee0d67aa3c1d5c2844e558fea6802e8cfa58e5d4cbdf8bad25fe
}

Plus golf:
Utilities.computeHmacSha256Signature(message, secret)
.map(function(chr){return (chr+256).toString(16).slice(-2)})
.join('')

var date = new Date();
var nonce = Math.floor(date.getTime()/1000);
var url = "https://mysweet.com/api/accounts"
var secret = "my_secret";
var signature = Utilities.computeHmacSha256Signature(nonce+url, secret)
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");

Related

Attachments: undefined Apps Script

I am trying to pull an attachment from an email which is a zip file and send that file to Google Analytics to upload the data. I am struggling with the getAttachment part in the script as it is showing as undefined. I have no idea what I am doing wrong. Any help would be greatly appreciated. I have now fixed where it is pulling the right attachment by using: var attachments = messages[0].getAttachments(); However I am now getting a new error where it seems to be looking for a title of the attachment but it is still showing as undefined.
function refundImport() {
/// use custom report to schedule the email - will need to adjust the processCsv() function based on your schema
/// if you are using a non-bing data source - you will probably need to adjust the findCsvAttachments() function as it grabs a zip file now
var CONFIG = {
'emailSubject': 'Refunded or Partially Refunded Orders - TKS',
'customDataSourceId': 'xxxxxxxxxxxxxxxxxxxxx',
'now': new Date(),
'zipFileName': 'refunded_or_partially_refunded_orders.zip',
//'csvFileName': 'refunded_or_partially_refunded_orders.csv',
'analyticsAccountId': '12345678',
'analyticsPropertyId': 'UA-12345678-1'
}
//adds one whole day to a date object - can take negative days if you want yesterday etc
function addDaysToDate(DATE, DAYS) {
var newDate = DATE.getTime() + DAYS * 3600000 * 24;
var newDate1 = new Date(newDate);
return newDate1;
}
//takes a date object and formats it as a string
function formatDateAsString(DATE) {
var dateString = Utilities.formatDate(DATE, 'GMT+12:00', 'yyyy/MM/dd');
return dateString;
}
//after and before must be date strings - use the above function
function grabEmailAttachments(SUBJECT, AFTER, BEFORE) {
var query = 'subject:' + SUBJECT + ' ' + 'has:attachment after:' + AFTER + ' ' + 'before:' + BEFORE;
//assumes only 1 will match - if more than 1 - will match the first one
var thread = GmailApp.search('in:inbox from:"noreply#highviewapps.com"');
var messages = thread[0].getMessages();
var content = messages[0].getPlainBody();
//var attachments = thread.getAttachments()[0];
var attachments = messages[0].getAttachments();
//thread.moveToTrash();
return attachments;
}
// finds csv attachment and creates 2d array of row,column e.g. csv[0][1] = value in row 0 column 1 of csv
function findCsvAttachment(attachments, zipFileToSearch, fileNameToSearch) {
var counter = 0;
for (i = 0; i < attachments.length; i++) {
if (attachments[i].getName().search(zipFileToSearch) != -1) {
var unzip = Utilities.unzip(attachments[i]);
var csvData = Utilities.parseCsv(unzip[0].getDataAsString(), ",");
counter = counter + 1;
}
}
if (counter == 0) {
Logger.log('No file with ' + fileNameToSearch + ' in its name was found.');
}
if (counter == 1) {
return csvData;
}
if (counter > 1) {
Logger.log('More than 1 file with ' + fileNameToSearch + ' in its name was found - the last one was used.');
}
}
function processCsv(csvData, date) {
var headers = 'ga:transactionId,ga:productSku,ga:productPrice,ga:quantityRefunded,ga:transactionRevenue';
var dataForUpload = headers;
return dataForUpload;
}
//assumes media dataType for upload https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/uploads/uploadData
function uploadDataToAnalytics(data, accountId, webPropertyId, customDataSourceId) {
var dataBlob = Utilities.newBlob(data, "application/octet-stream");
var upload = Analytics.Management.Uploads.uploadData(accountId, webPropertyId, customDataSourceId, dataBlob);
return upload;
}
///////// ACTUAL IMPLEMENTATION OF SCRIPT /////////////
var tomorrowString = formatDateAsString(addDaysToDate(CONFIG.now, 1));
var yesterdayString = formatDateAsString(addDaysToDate(CONFIG.now, -1));
var todayString = formatDateAsString(CONFIG.now);
var emailAttachments = grabEmailAttachments(CONFIG.emailSubject, yesterdayString, tomorrowString);
var csv = findCsvAttachment(emailAttachments, CONFIG.zipFileName, CONFIG.csvFileName);
var csvForUpload = processCsv(csv, yesterdayString);
var analyticsUpload = uploadDataToAnalytics(csvForUpload, CONFIG.analyticsAccountId, CONFIG.analyticsPropertyId, CONFIG.customDataSourceId);
}
Try changing these lines of code
From:
var attachments = messages.getAttachments();
and
var unzip = Utilities.unzip(attachments[i]);
To:
var attachments = messages[0].getAttachments();
and
var unzip = Utilities.unzip(attachments[i].copyBlob());

Coinbase v2 with Google Sheets

I'm trying to use the Coinbase API v2 (NOT PRO) to simply pull the contents of my wallet in to my spreadsheet. I have the following:
var url = 'https://api.coinbase.com/v2';
var requestPath = '/accounts';
var api_key = '********';
var secret_key = '********';
// var timestamp = new Date().getTime().toString().slice(0, -3);
var timestamp = Math.floor(Date.now() / 1000).toString();
var method = 'GET';
var message = Utilities.base64Decode(Utilities.base64Encode(timestamp + method + requestPath));
var decoded_secret = Utilities.base64Decode(secret_key);
var signature = Utilities.base64Encode(Utilities.computeHmacSha256Signature(message, decoded_secret));
//previous attempt
/* var signature = Utilities.computeHmacSha256Signature(message, decoded_secret);
signature = signature.map(function(e) {
var v = (e < 0 ? e + 256 : e).toString(16);
return v.length == 1 ? "0" + v : v;
}).join("");*/
var params = {
'method': method,
'headers': {
'Content-Type': 'application/json',
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': api_key,
'CB-VERSION': '2021-02-03'
}
};
Logger.log(params);
var res = await UrlFetchApp.fetch(url + requestPath, params);
Unfortunately I am getting the invalid signature error. I feel like I'm close. It could be that I don't have the url/request path going in to the signature correctly.
Creating a Coinbase signature with Apps Script is a bit tricky
As you can see from the documentation for Coinbase API, in Python the signature is built with
signature = hmac.new(self.secret_key, message, hashlib.sha256).hexdigest().
In other words, the signature needs to be hex-encoded.
There is no 1:1 equivalent to hexdigest() in Apps Script, but you can build it manually.
This works for me:
var message = (timestamp + method + requestPath + body);
var byteSignature = Utilities.computeHmacSha256Signature(message, secret);
var signature = byteSignature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
Thereby secret is your credential provided by Coinbase - no need to decode it.

Unable to get my script to return ContentService

Let me just preface this with I know this script could probably be better but I'm very new to scripting in any language and I'm just playing around with an idea for work. This is essentially just a frankensteined combination of google results for what I want to accomplish but I've hit a deadend.
I'm using an aspect from another script to return contentservice in JSON format for a webapp except it's not working in this case
As far as I can tell it should work perfectly fine and if I replace the return contentservice with the browser.msgbox below I get the values returned that I want but when using contentservice and going to my scripts Web App URL with the action pointing to range I get the error The script completed but did not return anything.
var mysheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1sPuqdg0Va9LLQudl2ta23b-CGEF_-FFSTeggRw3J4L4/edit").getSheetByName('Sheet3');
var sheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1sPuqdg0Va9LLQudl2ta23b-CGEF_-FFSTeggRw3J4L4/edit").getSheetByName('Sheet1');
function doGet(e){
var action = e.parameter.action;
if(action == 'Range'){
return Range(e);
}
}
function Range(e) {
let term = 'Customer 6';
var sdata = mysheet.getRange("A:A").getValues();
sdata.forEach((val, index) => {
if(val == term){
var msgr = "B" + (index+1)
var msgc = "D" + (index+1)
var rrow = mysheet.getRange(msgr).getValue();
var ccol = mysheet.getRange(msgc).getValue();
var data = sheet.getRange("E" + rrow + ":I"+ccol);
var records={};
var rows = sheet.getRange("E" + rrow + ":I"+ccol).getValues();
data = [];
for (var r = 0, l = rows.length; r < l; r++) {
var row = rows[r],
record = {};
record['Product'] = row[0];
record['Case']=row[2];
record['Order QTY']=row[3];
record['Packed']=row[4];
data.push(record);
}
records.items = data;
var result=JSON.stringify(records);
return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON);
// Browser.msgBox(result);
}})}
I can't figure out why I'm getting the correct returned values for the msgBox, but no results for the ContentService.
Any help would be greatly appreciated. Thanks in advance.
Edit: I have been publishing new webapp versions every revision
This breaks because you're returning from within a forEach() loop. Check out the MDN web docs:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.
The simplest fix is to use a for loop instead.
function Range(e) {
let term = 'Customer 6';
var sdata = mysheet.getRange("A:A").getValues();
for (var index = 0; index < sdata.length; index++) {
var val = sdata[index];
if(val == term){
var msgr = "B" + (index+1)
var msgc = "D" + (index+1)
var rrow = mysheet.getRange(msgr).getValue();
var ccol = mysheet.getRange(msgc).getValue();
var data = sheet.getRange("E" + rrow + ":I"+ccol);
var records={};
var rows = sheet.getRange("E" + rrow + ":I"+ccol).getValues();
data = [];
for (var r = 0, l = rows.length; r < l; r++) {
var row = rows[r],
record = {};
record['Product'] = row[0];
record['Case']=row[2];
record['Order QTY']=row[3];
record['Packed']=row[4];
data.push(record);
}
records.items = data;
var result=JSON.stringify(records);
return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON);
}
}
}

Amazon MWS Signature mismatch in Google Sheet

I am creating a Google Sheet add-on wherein I am downloading the FBA Orders. I have been trying to make a successful API call. Am I perhaps missing something here?
Response I am getting:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
Google Sheet JavaScript code:
function POSTRequest() {
var config = amazonMWSConfigProperties.getProperty('amazonConfig');
var configData = JSON.parse(config);
var sellerID=configData.sellerID;
var accessKey=configData.accessKey;
var secretKey=configData.secretKey;
var authToken=configData.authToken;
var defaultMarket=configData.defaultMarket;
var url = 'https://mws.amazonservices.in/Orders/2013-09-01?';
var today1 = new Date();
var today = new Date();
var todayTime = Utilities.formatDate(today, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
yesterday.setHours(0,0,0,0);
var yesterdayTime = Utilities.formatDate(yesterday, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var dayBeforeYesterday = new Date();
dayBeforeYesterday.setDate(today.getDate() - 2);
dayBeforeYesterday.setHours(0,0,0,0);
var dayBeforeYesterdayTime = Utilities.formatDate(dayBeforeYesterday, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var unsignedURL =
'POST\nmws.amazonservices.in\n/Orders/2013-09-01\n'+
'AWSAccessKeyId=' +accessKey+
'&Action=ListOrders'+
'&CreatedAfter='+encodeURIComponent(Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
//'&CreatedBefore=2020-01-18T18%3A30%3A00Z' + //yesterdayTime +
'&FulfillmentChannel.Channel.1=AFN' +
'&MWSAuthToken=' +authToken+
'&MarketplaceId.Id.1=' +defaultMarket+
'&SellerId='+sellerID+
'&SignatureMethod=HmacSHA256'+
'&SignatureVersion=2'+
'&Timestamp='+encodeURIComponent(Utilities.formatDate(today1, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
'&Version=2013-09-01';
Logger.log(unsignedURL);
var SignedRequest = calculatedSignature(unsignedURL, secretKey);
var Encoded = Utilities.base64Encode(SignedRequest);
var param = 'AWSAccessKeyId=' +accessKey+
'&Action=ListOrders'+
'&CreatedAfter='+encodeURIComponent(Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
//'&CreatedBefore=2020-01-18T18%3A30%3A00Z' + //yesterdayTime +
'&FulfillmentChannel.Channel.1=AFN' +
'&MWSAuthToken=' +authToken+
'&MarketplaceId.Id.1=' +defaultMarket+
'&SellerId='+sellerID+
'&Signature='+encodeURIComponent(SignedRequest) +
'&SignatureMethod=HmacSHA256'+
'&SignatureVersion=2'+
'&Timestamp='+encodeURIComponent(Utilities.formatDate(today1, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
'&Version=2013-09-01';
Logger.log(url+param);
var result = UrlFetchApp.fetch(url+param);
//writeDataToXML(result);
Logger.log(result);
if (result.getResponseCode() == 200) {
//writeDataToXML(result);
}
}
function calculatedSignature(url,secret) {
var urlToSign = url;
var byteSignature = Utilities.computeHmacSha256Signature(urlToSign, secret);
// convert byte array to hex string
var signature = byteSignature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
//Logger.log("URL to sign: " + urlToSign);
//Logger.log("");
//Logger.log("byte " + byteSignature);
//Logger.log("");
//Logger.log("reg " + signature);
var byte64 = Utilities.base64Encode(byteSignature)
//Logger.log("base64 byte " + Utilities.base64Encode(byteSignature));
//Logger.log("");
//Logger.log("base64 reg " + Utilities.base64Encode(signature));
Logger.log("base64 reg " + byte64)
return byte64;
}
In addition to using the built-in encodeURIComponent, I have found that you also have to manually replace certain values. This is the function that I use to encode my URL:
/**
* Performs URL encoding compatible with MWS http requests
* #param {String} The string to encode
*/
function urlEncode(str) {
str = encodeURIComponent(str);
str = str.replace(/\*/g, '%2A');
str = str.replace(/\(/g, '%28');
str = str.replace(/\)/g, '%29');
str = str.replace(/'/g, '%27');
str = str.replace(/\!/g, '%21');
return str;
}
I call it in my createSignature function:
function createSignature(value, key) {
var signature = Utilities.computeHmacSha256Signature(value, key);
return urlEncode(Utilities.base64Encode(signature));
}
Which I call on my unsigned URL, passing my secret as the key.
I hope that helps!

Fetch API data from Bittrex

I'm trying to fetch the API data with Google Apps Script about my balance on Bittrex but it returns me nothing, no errors and only blank cells. This is the code I've written based on the documentation here: https://bittrex.com/Home/Api
function getBalance() {
var ss = SpreadsheetApp.openById("***");
var data = ss.getSheetByName("Data");
var key = ss.getSheetByName("Api").getRange('A2').getValue();
var secret = ss.getSheetByName("Api").getRange('B2').getValue();
var baseUrl = 'https://bittrex.com/api/v1.1/';
var nonce = Math.floor(new Date().getTime()/1000);
var command = "/account/getbalances";
var uri = baseUrl.concat(command + "?apikey=" + key + "&nonce=" + nonce);
var signature = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_512,
uri,
secret);
signature = signature.map(function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('')
var headers = {
"apisign": signature
}
var params = {
"method": "get",
"headers": headers,
}
var response = UrlFetchApp.fetch(uri, params);
var json = JSON.parse(response.getContentText());
var blnc = [];
blnc.push(['Balance']);
for(var key in json.result)
{
blnc[0].push(json.result[key]);
}
askRange = data.getRange(2, 2, blnc.length, 1);
askRange.setValues(blnc);
}
The askrange last number is "1" because the script only pass the "Balance" value to the spreadsheet, but the values should be something around 210. Any help? Thank you
I solved it, the problem was in the "command" variable, there was a slash not needed that generated an url with a double slash