I am a newer Apps Scripts user who has been stuck on this problem for some time. I am trying to connect my company's reporting feature to a Google Sheet so I can create reporting dashboards for our users.
I have a .jsNode code snippet which I am able to successfully run outside of the Apps Scripts editor, but I am having issues translating it to Apps Scripts.
My goal is to be able to generate an Authentication Token which will be used in a header within a POST request. I will then use this to get a specific URL that I can pull data from (it will be a .csv file. I already feel I can accomplish this using another script)
Below is the .NodeJS code snippet:
const crypto = require('crypto');
module.exports.init = function () {
let prefix = 'abc-admin-v1'
let businessId = 'XXXX'
let publicAppKeyId = 'XXXX'
let secretKey = 'XXXX'
let unixTimestamp = Math.floor(Date.now() / 1000)
let payload = `${prefix}${businessId}${unixTimestamp}`
let rawKey = Buffer.from(secretKey, 'base64')
let signature = crypto.createHmac('sha256', rawKey).update(payload, 'utf8').digest('base64')
let token = `${signature}${payload}`
let httpBasicPayload = `${publicAppKeyId}:${token}`
let httpBasicCredentials = Buffer.from(httpBasicPayload, 'utf8').toString('base64')
console.log(httpBasicCredentials);
};
Below will be what I have in Apps Scripts:
function apiInfo() {
var secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var apiKey = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
var locationID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
var prefix = "abc-admin-v1";
var timeStamp = Math.round((new Date()).getTime() / 1000);
var timeStampReal = JSON.stringify(timeStamp);
var tokenPayload = prefix + businessID + timeStampReal;
var enCodeTokenPayload = Utilities.base64Encode(tokenPayload);
var deCodeTokenPayload = Utilities.base64Decode(enCodeTokenPayload);
var tokenPayloadReal = JSON.stringify(tokenPayload);
var rawKey = Utilities.base64Decode(secret);
var rawMac = Utilities.computeHmacSha256Signature(tokenPayloadReal, rawKey);
var signature = Utilities.base64Encode(rawMac);
var token = signature + tokenPayload;
var httpBasicPayload = apiKey + ":" + token;
var httpBasicCredentials = Utilities.base64Encode(httpBasicPayload);
When testing using the current GAS code, it produces an invalid Auth Token.
When I run the Node.js version outside of GAS, it produces a valid Auth Token. I am looking to find out what is currently wrong with my code that is producing an invalid auth token. I would like to keep the project within GAS if possible.
I believe your goal is as follows.
You want to convert your showing Node.js script to Google Apps Script. Namely, you want to retrieve the same values of httpBasicCredentials between your Node.js script and Google Apps Script.
const crypto = require('crypto');
module.exports.init = function () {
let prefix = 'abc-admin-v1'
let businessId = 'XXXX'
let publicAppKeyId = 'XXXX'
let secretKey = 'XXXX'
let unixTimestamp = Math.floor(Date.now() / 1000)
let payload = `${prefix}${businessId}${unixTimestamp}`
let rawKey = Buffer.from(secretKey, 'base64')
let signature = crypto.createHmac('sha256', rawKey).update(payload, 'utf8').digest('base64')
let token = `${signature}${payload}`
let httpBasicPayload = `${publicAppKeyId}:${token}`
let httpBasicCredentials = Buffer.from(httpBasicPayload, 'utf8').toString('base64')
console.log(httpBasicCredentials);
};
When I saw your showing Google Apps Script, unfortunately, there are several undeclared variable and unused variables. By this, in this answer, I would like to propose to directly converting your showing Node.js to Google Apps Script.
Sample script:
function myFunction() {
let prefix = 'abc-admin-v1'
let businessId = 'XXXX'
let publicAppKeyId = 'XXXX'
let secretKey = 'XXXX'
let unixTimestamp = Math.floor(Date.now() / 1000);
let payload = `${prefix}${businessId}${unixTimestamp}`;
let payloadBytes = Utilities.newBlob(payload).getBytes();
let rawKey = Utilities.base64Decode(secretKey);
let signature = Utilities.base64Encode(Utilities.computeHmacSha256Signature(payloadBytes, rawKey));
let token = `${signature}${payload}`;
let httpBasicPayload = `${publicAppKeyId}:${token}`;
let httpBasicCredentials = Utilities.base64Encode(httpBasicPayload);
console.log(httpBasicCredentials);
}
Testing:
In the above scripts, when the value of unixTimestamp is 1661403828, both your Node.js and my sample Google Apps Script are the same values as follows.
WFhYWDpGYXBwRk9kaFoxdDVQNGVkV2JJbFIvR1RiMkEyZ0hwYThld05BYjVDVkxnPWFiYy1hZG1pbi12MVhYWFgxNjYxNDAzODI4
Note:
From your question, in this answer, your Node.js is converted to Google Apps Script. Please be careful about this.
References:
base64Encode(data)
computeHmacSha256Signature(value, key)
After seeing through this link, I tried the same in my postman.
var storageAccount = "mystorage";
var accountKey = "<<primaryKey>>";
var date = new Date();
var UTCstring = date.toUTCString();
var data = UTCstring + "\n" + "/mystorage/Health"
var encodedData = unescape(encodeURIComponent(data));
var hash = CryptoJS.HmacSHA256(encodedData, CryptoJS.enc.Base64.parse(accountKey));
var signature = hash.toString(CryptoJS.enc.Base64);
var auth = "SharedKeyLite " + storageAccount + ":" + signature;
postman.setEnvironmentVariable("auth", auth);
postman.setEnvironmentVariable("date", UTCstring);
When I make the request to ATS, to the following url,
I get the auth denied!
Can someone please guide me what's going wrong here?!
I think you need to generate a bearer token and put it to the Authorization of Postman.
If you are using C#, you can use this to get the bearer token:
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
string accesstoken = azureServiceTokenProvider.GetAccessTokenAsync("https://storage.azure.com/").Result;
string bearertoken = "Bearer " + accesstoken;
Then Copy the bearer token:
After that, it should be ok.
Just realized that the url and the data that we are encoding should exactly match the url we are querying...
After changing
var data = UTCstring + "\n" + "/mystorage/Health"
to
var data = UTCstring + "\n" + "/mystorage/Health(PartitionKey='USA',RowKey='WA')"
things started working.
Update :
It just expects the right table query. The following works fine,
var data = UTCstring + "\n" + "/mystorage/Health()"
with all filter expressions in the url being invoked from postman.
I have been trying to send a sample message to Azure Service Bus Topic from POSTMAN, But it came with an unusual error. Here my doubt is, I am generating the SAS token using NodeJs own 'Buffer' rather than 'utf8' module. Can that be a reason Why I am getting 'The specified HTTP verb (POST) is not valid.'. Here is my snippet to generate the SAS token.
*function createSharedAccessToken(uri, saName, saKey) {
if (!uri || !saName || !saKey) {
throw "Missing required parameter";
}
console.log('inside the function')
var encoded = encodeURIComponent(uri);
var now = new Date();
var week = 60 * 60 * 24 * 7;
var ttl = Math.round(now.getTime() / 1000) + week;
var signature = encoded + '\n' + ttl;
var signatureUTF8 = Buffer.from(signature, 'utf8');
var hash = crypto.createHmac('sha256', saKey).update(signatureUTF8).digest('base64');
return 'SharedAccessSignature sr=' + encoded + '&sig=' +
encodeURIComponent(hash) + '&se=' + ttl + '&skn=' + saName;
}*
Its a POST call with Authorization and Content-Type header.
Please add 'messages' at the end of the url:
http{s}://{serviceNamespace}.servicebus.windows.net/{queuePath|topicPath}/messages
->
https://mynamespace.servicebus.windows.net/my-topic-name/messages
Send Message
Problem
We are seeing this error returned from the DocumentDB REST API whenever we request a list or query, but not when we fetch objects by name/id:
The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used.
Background
We have been successfully using the node.js sdk with DocumentDB for over a year now, but as we want to migrate our back-end restful API code from a node.js App Service to Azure Functions we are seeing 10-30 second lag times come into play as the DocumentDB sdk loads slowly when the Function hasn't been called in a while. We know that the Function instance is hot, and this isn't a cold instance issue based on previous communication with the Azure Functions team.
To work around this we want to test the DocumentDB REST API which requires zero external libraries to run in a node.js Function and should execute as quickly as possible.
Code
This is the test harness running in local node.js. We'll move this to an Azure Function once it's working.
var express = require('express');
var router = express.Router();
var crypto = require("crypto");
var request = require('request');
router.get('/', function (req, res, next) {
var key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var uri = "https://xxxxxx.documents.azure.com";
var verb = 'GET';
var type = 'dbs';
var link = 'dbs';
var url = `${uri}/${link}`;
var headers = getDefaultRequestHeaders();
// var body = `{"query":"SELECT * FROM c", "parameters": []}`;
var body = '';
headers['content-length'] = body.length;
headers['authorization'] = getAuthorizationTokenUsingMasterKey(verb, type, link, headers['x-ms-date'], key);
request[verb.toLowerCase()]({"url": url, "headers": headers, "body": body}, function (error, response, body) {
// console.log(`error is ${error}`);
// console.log(`response is ${JSON.stringify(response, null, 2)}`);
console.log(`body is ${body}`);
res.status(response.statusCode).json(body);
});
});
function getDefaultRequestHeaders(isQuery, date) {
var headers = {
"content-type": "application/json",
"x-ms-date": new Date().toUTCString(),
"x-ms-version": "2017-02-22",
"accept": "application/json",
"cache-control": "no-cache",
"user-agent": "xxxxxx/1.0"
};
if(isQuery) {
headers["x-ms-documentdb-isquery"] = true;
headers["content-type"] = "application/query+json";
}
if(date) {
headers["x-ms-date"] = date;
}
return headers;
}
function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, masterKey) {
var key = new Buffer(masterKey, "base64");
var text = (verb || "").toLowerCase() + "\n" +
(resourceType || "").toLowerCase() + "\n" +
(resourceLink || "") + "\n" +
date.toLowerCase() + "\n" +
"" + "\n";
var body = new Buffer(text, "utf8");
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
var MasterToken = "master";
var TokenVersion = "1.0";
return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
}
module.exports = router;
We are using the getAuthorizationTokenFromMasterKey function verbatim from the Access control in the DocumentDB API page.
The key, app name, and user-agent have been replaced with x's for privacy/security.
Test Results
List Databases
When I try the most basic call to list dbs the server returns the token error:
var verb = 'GET';
var type = 'dbs';
var link = 'dbs';
Response:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ndbs\n\nsat, 12 aug 2017 12:28:41 gmt\n\n'\r\nActivityId: acbf19d9-6485-45c5-9c30-6aa21f14d5b3\"}"
Get Database
However, when I perform the get database request it works fine:
var verb = 'GET';
var type = 'dbs';
var link = 'dbs/00001';
Response:
"{\"id\":\"00001\",\"_rid\":\"0eUiAA==\",\"_ts\":1441256154,\"_self\":\"dbs\/0eUiAA==\/\",\"_etag\":\"\\"00007d4a-0000-0000-0000-55e7d2da0000\\"\",\"_colls\":\"colls\/\",\"_users\":\"users\/\"}"
List Collections
Similarly, requesting the list of collections from this database returns a token error:
var verb = 'GET';
var type = 'colls';
var link = 'dbs/00001/colls';
Respose:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ncolls\ndbs/00001\nsat, 12 aug 2017 12:32:19 gmt\n\n'\r\nActivityId: 8a9d4ff8-24ef-4fd2-b400-f9f8aa743572\"}"
Get Collection
But when I call get collection I get a valid response:
var verb = 'GET';
var type = 'colls';
var link = 'dbs/00001/colls/00001';
Response:
"{\"id\":\"00001\",\"indexingPolicy\":{\"indexingMode\":\"consistent\",\"automatic\":true,\"includedPaths\":[{\"path\":\"\/*\",\"indexes\":[{\"kind\":\"Range\",\"dataType\":\"Number\",\"precision\":-1},{\"kind\":\"Range\",\"dataType\":\"String\",\"precision\":-1},{\"kind\":\"Spatial\",\"dataType\":\"Point\"}]}],\"excludedPaths\":[]},\"_rid\":\"0eUiAJMAdQA=\",\"_ts\":1454200014,\"_self\":\"dbs\/0eUiAA==\/colls\/0eUiAJMAdQA=\/\",\"_etag\":\"\\"00000100-0000-0000-0000-56ad54ce0000\\"\",\"_docs\":\"docs\/\",\"_sprocs\":\"sprocs\/\",\"_triggers\":\"triggers\/\",\"_udfs\":\"udfs\/\",\"_conflicts\":\"conflicts\/\"}"
List Documents
Requesting list documents on that collection gives me this error:
var verb = 'GET';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs';
Response:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get\ndocs\ndbs/00001/colls/00001\nsat, 12 aug 2017 12:34:48 gmt\n\n'\r\nActivityId: 57097e95-c41b-4770-b91a-370418ef2cce\"}"
Get Document
Not surprisingly, fetching a single document works fine:
var verb = 'GET';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs/e7fe638d-2152-2097-f9c6-9801d7cf5cdd';
Response:
"{\"name\":\"test rest api\",\"id\":\"e7fe638d-2152-2097-f9c6-9801d7cf5cdd\",\"_rid\":\"0eUiAJMAdQCbHgAAAAAAAA==\",\"_self\":\"dbs\/0eUiAA==\/colls\/0eUiAJMAdQA=\/docs\/0eUiAJMAdQCbHgAAAAAAAA==\/\",\"_etag\":\"\\"0d00d1ee-0000-0000-0000-598ef7d40000\\"\",\"_attachments\":\"attachments\/\",\"_ts\":1502541779}"
Query Documents
Finally, sending a query also results in a token error:
var verb = 'POST';
var type = 'docs';
var link = 'dbs/00001/colls/00001/docs';
var body = `{"query":"SELECT * FROM c", "parameters": []}`;
Response:
"{\"code\":\"Unauthorized\",\"message\":\"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\ndocs\ndbs/00001/colls/00001\nsat, 12 aug 2017 12:35:42 gmt\n\n'\r\nActivityId: b8b95f8c-1339-423e-b0e7-0d15d3056180\"}"
I believe the documentation is incorrect. Where they say resourceLink, they should actually say resource id. If you look at the Node SDK code, this is how they are calculating the authorization header (notice the use of resourceId):
getAuthorizationTokenUsingMasterKey: function (verb, resourceId, resourceType, headers, masterKey) {
var key = new Buffer(masterKey, "base64");
var text = (verb || "").toLowerCase() + "\n" +
(resourceType || "").toLowerCase() + "\n" +
(resourceId || "") + "\n" +
(headers["x-ms-date"] || "").toLowerCase() + "\n" +
(headers["date"] || "").toLowerCase() + "\n";
var body = new Buffer(text, "utf8");
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
var MasterToken = "master";
var TokenVersion = "1.0";
return "type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature;
},
So if you want to list the databases, because there is no resource id you will need to use an empty string for your link variable. Similarly, if you want to list collections in the database, the link should actually be the id of the database (e.g. dbs/00001 and not dbs/00001/colls).
I was getting the same issue. For querying documents I was getting authorization token error. It was due to wrong ResourceId/ResourceLink
var verb = 'POST';
var type = 'docs';
var link = 'dbs/{db-id}/colls/{coll-id}/docs';
var url = `${uri}/${link}`;
var resourceLink = "dbs/{db-id}/colls/{coll-id}"
getAuthorizationTokenUsingMasterKey(verb, type, resourceLink, headers['x-ms-date'], key)
the only correction is required from the given question data is to change the appropriate resourceLink while generating AuthorizationToken. For querying Documents the resourceLink is <dbs/{db-id}/colls/{coll-id}> instead of <dbs/{db-id}/colls/{coll-id}/docs>
I want to provide another thing to consider in addressing this issue. In my case I had to add this header: x-ms-documentdb-query-enablecrosspartition: true, because I created my container with a partitionKey.
I also want to confirm how my main parameters were setup to calculate the authorization header for querying over Documents:
resourceType: docs
resourceLink: dbs/<databaseId>/colls/<containerId>
I had thought, up to this point, that the resourceLInk had to match with the request URL but this is showing me I was wrong. Similarly notice that the resourceType is not present in the resourceLink.
POST /dbs/<databaseId>/colls/<containerId>/docs HTTP/1.1
accept: application/json
x-ms-documentdb-isquery: true
x-ms-version: 2018-12-31
authorization: type%3Dmaster%26ver%3D1.0%26sig%***********************
x-ms-date: Sat, 03 Apr 2021 22:34:24 GMT
x-ms-documentdb-query-enablecrosspartition: true
x-correlation-id: be1b1fe1-94cc-11eb-a0a4-38f9d3924940
Host: <host>.documents.azure.com
User-Agent: AHC/1.0
Connection: keep-alive
Content-Type: application/query+json
Content-Length: 72
{
"query": "SELECT * FROM <containerId>",
"parameters": [
]
}
I got the same error while making an update to document DB but in my case I realized I was using the Read-Only keys. After changing connection string to use it to Read-Write Keys, I was able to update the records.
Am close to tearing my hair out (as a passionate developer, should), so hopefully I can get some help on this one. After reading a bunch of forums and sifting through facebook documentation, here is a log file of trying to make a subscription to user feed's. I really only need to know if the bottom set of API's I am running should work in theory. Here is my log:
*********************************************************
makeRequest - Host: graph.facebook.com - Path: /v2.2/{ app_id }/subscriptions?object=user
*********************************************************
Type: DELETE
*********************************************************
Wed Feb 25 2015 12:52:15 GMT+0000 (UTC) Server is listening on port 81
*********************************************************
makeRequest - Host: graph.facebook.com - Path: /oauth/access_token?client_id={ app_id }&client_secret={ app_secret }&grant_type=client_credentials
*********************************************************
Type: GET
*********************************************************
App Access Token: { app_token }
*********************************************************
makeRequest - Host: graph.facebook.com - Path: /v2.2/{ app_id }/subscriptions?&object=user&fields=feed&callback_url={ uri_encoded_ callback_url }&verify_token={ verify_token }&access_token={ app_token }
*********************************************************
Type: POST
*********************************************************
Success: {"error":{"message":"Invalid OAuth access token.","type":"OAuthException","code":190}}
So here I am first trying to delete any subscriptions currently active, then using the API to get issued a new application access token, and finally to make a new subscription. Here is my function to initialize this whole process:
function subscribe_facebook(http,https) {
var oauth_obj = getOauthObj('facebook');
// First Delete
var url = oauth_obj.subscription_url;
var host = splitUrl(url,"host");
var path = splitUrl(url,"path");
path += "?object=user";
makeRequest(http,host,path,80,'DELETE',function(ret){
// Get App Access Token
url = oauth_obj.access_token_url;
host = splitUrl(url,"host");
path = splitUrl(url,"path");
path += "?client_id=" + oauth_obj.app_id;
path += "&client_secret=" + oauth_obj.app_secret;
path += "&grant_type=client_credentials";
makeRequest(https,host,path,443,'GET',function(ret){
facebook_app_token = ret.toString().split('|');
facebook_app_token = facebook_app_token[1];
consoleLogger("App Access Token: " + facebook_app_token);
subscribe(https,'facebook',443,'POST',facebook_app_token,myServer);
});
});
}
And here is the subscribe function it enters:
function subscribe(http,type,port,method,access_token,myServer) {
var oauth_obj = getOauthObj(type);
var sub_post_data = "";
switch (type) {
case "facebook":
sub_post_data =
"&object=" + "user"+
"&fields=" + "feed"+
"&callback_url=" + encodeURIComponent(myServer + "/" + type) +
"&verify_token=" + "*****" +
"&access_token=" + access_token
break;
}
var host = splitUrl(oauth_obj.subscription_url,"host");
var path = splitUrl(oauth_obj.subscription_url,"path");
path += "?" + sub_post_data;
makeRequest(http,host,path,port,method,function(ret) {
consoleLogger("Success: " + ret);
});
}
I made this other function to maintain the social media variables:
function getOauthObj(type) {
var obj = {};
var authorize_url = "";
var access_token_url = "";
var request_token_url = "";
var subscription_url = "";
var app_id = "";
var app_secret = "";
var main_domain = "";
var scope = "";
switch (type) {
case "facebook":
app_id = '*****';
app_secret = '**********';
main_domain = 'https://graph.facebook.com/';
authorize_url = 'https://www.facebook.com/dialog/oauth';
access_token_url = main_domain + 'oauth/access_token';
request_token_url = main_domain + '2.2/me';
subscription_url = main_domain + "v2.2/" + app_id + "/subscriptions";
scope = 'read_stream';
break;
}
obj.main_domain = main_domain;
obj.authorize_url = authorize_url;
obj.access_token_url = access_token_url;
obj.request_token_url = request_token_url;
obj.subscription_url = subscription_url;
obj.app_id = app_id;
obj.app_secret = app_secret;
return obj;
}
Being that the OAuth is invalid, I'm assuming that I'm not passing the right parameters to the oauth API to do this or not - can someone steer me in the right direction?
Ok, well it's fixed now. I have read on some forums about different Facebook API's which details that using the application id, concatenated with a pipe, then the application secret can be used in place of an access token, but I didn't think this could be used here, but turns out it can!
*********************************************************
makeRequest - Host: graph.facebook.com - Path: /v2.2/{ app_id }/subscriptions?&object=user&fields=feed&callback_url={ callback_url }&verify_token={ verify_token }&access_token={ app_id }|{ app_secret }
Hopefully this helps someone.
Cheers.