OAuth1 authenticated calls on Khan Academy's API using Google App Script - javascript

I'm pretty new to coding. I'm using Google App Script, which is supposed to be javascript based and a library to manage OAuth1 api authentication. I'm trying to authenticate with the Khan Academy. This script which I got from the google apps site works to a point. The function 'listTweets' takes me to the 'else' branch and logs the url to take me to Khan Academy to grant the script permission to make the call. I accept and am supposed to rerun the function and end up in the 'then' branch of the 'if-then-else' statement. I just keep getting sent down the else. Does anyone know what gives? Thanks in advance for any help.
var CONSUMER_KEY = 'my key';
var CONSUMER_SECRET = 'my secret';
var PROJECT_KEY = 'my google project key';
function listTweets() {
var service = getTwitterService();
if (service.hasAccess()) {
var response = service.fetch('https://www.khanacademy.org//api/v1/user/exercises');
var tweets = JSON.parse(response.getContentText());
Logger.log(tweets);
} else {
var authorizationUrl = service.authorize();
Logger.log('Please visit the following URL and then re-run the script: ' + authorizationUrl);
}
}
function getTwitterService() {
var service = OAuth1.createService('twitter');
service.setAccessTokenUrl('https://www.khanacademy.org/api/auth2/access_token')
service.setRequestTokenUrl('https://www.khanacademy.org/api/auth2/request_token')
service.setAuthorizationUrl('https://www.khanacademy.org/api/auth2/authorize')
service.setConsumerKey(CONSUMER_KEY);
service.setConsumerSecret(CONSUMER_SECRET);
service.setProjectKey(PROJECT_KEY);
service.setCallbackFunction('authCallback');
service.setPropertyStore(PropertiesService.getScriptProperties());
service.setOAuthVersion('1.0');
return service;
}
function authCallback(request) {
var service = getTwitterService();
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this page.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this page');
}
}

I'm not familiar with this particular API, but reading through their documentation, and looking the PHP example, it appears that they are expecting the OAuth parameters to be passed in the URL rather than the Authorization Header.
By default, the OAuth services use the Authorization header, but this can be over-ridden with service.setParamLocation('uri-query').

I was able to reproduce and track down your problem. Funny enough, it ends up being a single-character fix (after the setParamLocation fix already mentioned): you just need to use OAuth version "1.0a" instead of "1.0". OAuth version 1.0a changed some of the details of how the OAuth callback works to fix a security issue, and I guess this OAuth library only includes the callback URL in the request_token step when using OAuth 1.0a. The KA API always uses the callback specified in the request_token step, so the previous version of the app script was never running the callback.
Here's some code that works for me:
var CONSUMER_KEY = 'FILL ME IN';
var CONSUMER_SECRET = 'FILL ME IN';
var PROJECT_KEY = 'FILL ME IN';
function listExercises() {
var service = getKhanAcademyService();
if (service.hasAccess()) {
var response = service.fetch('https://www.khanacademy.org/api/v1/user/exercises');
var exercises = JSON.parse(response.getContentText());
Logger.log(exercises);
} else {
var authorizationUrl = service.authorize();
Logger.log('Please visit the following URL and then re-run the script: ' + authorizationUrl);
}
}
function getKhanAcademyService() {
var service = OAuth1.createService('khanAcademy');
service.setAccessTokenUrl('https://www.khanacademy.org/api/auth2/access_token')
service.setRequestTokenUrl('https://www.khanacademy.org/api/auth2/request_token')
service.setAuthorizationUrl('https://www.khanacademy.org/api/auth2/authorize')
service.setConsumerKey(CONSUMER_KEY);
service.setConsumerSecret(CONSUMER_SECRET);
service.setProjectKey(PROJECT_KEY);
service.setCallbackFunction('authCallback');
service.setPropertyStore(PropertiesService.getScriptProperties());
service.setOAuthVersion('1.0a');
service.setParamLocation('uri-query');
return service;
}
function authCallback(request) {
var service = getKhanAcademyService();
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this page');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this page');
}
}

Related

Ouath2 in Google Apps Script

I've followed the guide on https://github.com/gsuitedevs/apps-script-oauth2 for setting up the Oauth2 token for a google services very closely but i'm still not able to get the access token to work.
The error I'm receiving is Access not granted or expired. (line 454, file "Service", project "OAuth2")
Note* My project has already been whitelisted for the GMB API library and I have enabled it in the API console console. I retrieved a ClientID and ClientSecret from my project aswell.
//Oauth 2 Flow Start
function getGmbService() {
// 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('gmb')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('...')
.setClientSecret('...')
// 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('https://www.googleapis.com/auth/business.manage')
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
function showSidebar() {
var gmbService = getGmbService();
if (!gmbService.hasAccess()) {
var authorizationUrl = gmbService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
DocumentApp.getUi().showSidebar(page);
} else {
Logger.log("No Access")
}
}
function authCallback(request) {
var gmbService = getGmbService();
var isAuthorized = gmbService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
//Oauth2 Flow Finish
function testRequest() {
var gmbService = getGmbService();
var payload = {
"pageSize": 5
}
var options = {
"headers": {
"Authorization": 'Bearer ' + gmbService.getAccessToken()
},
"method": 'GET',
"payload": payload,
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch("https://mybusiness.googleapis.com/v4/accounts",options)
Logger.log(response);
}

forge autodesk viewer api , onDocumentLoadFailure() - errorCode:4

i want to use the autodesk's viewer in my application so i used the forge ph client sdk,i made the 5 steps that are herehttps://forge.autodesk.com/blog/basic-usage-forge-sdk-php everything worked good.
but now, i want to view files in the viewer but it doesn't work i have thies error in my browser's concole :onDocumentLoadFailure() - errorCode:4
function viewObject(access,urn,divId){
var viewer;
var viewerDivId;
var options = {
env: 'AutodeskProduction',
accessToken: access
};
function onDocumentLoadSuccess(doc) {
// A document contains references to 3D and 2D viewables.
var viewables = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), {'type':'geometry'}, true);
if (viewables.length === 0) {
console.error('Document contains no viewables.');
return;
}
// Choose any of the avialble viewables
var initialViewable = viewables[0];
var svfUrl = doc.getViewablePath(initialViewable);
var modelOptions = {
sharedPropertyDbPath: doc.getPropertyDbPath()
};
var viewerDiv = document.getElementById('viewerDivId');
viewer = new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv);
viewer.start(svfUrl, modelOptions, onLoadModelSuccess, onLoadModelError);
}
function onDocumentLoadFailure(viewerErrorCode) {
console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}
function onLoadModelSuccess(model) {
console.log('onLoadModelSuccess()!');
console.log('Validate model loaded: ' + (viewer.model === model));
console.log(model);
}
function onLoadModelError(viewerErrorCode) {
console.error('onLoadModelError() - errorCode:' + viewerErrorCode);
}
var documentId = 'urn:'+urn;
viewerDivId = divId;
Autodesk.Viewing.Initializer(options, function onInitialized(){
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1 /jquery.min.js"></script>
<script>
function buttonViewClicked() {
var access = $('#token').val();
var urn = $('#urn').val();
viewObject(access, urn, "MonViewer");
}
</script>
Error code 4 refers to situations where the viewer gets 403 Access Denied when trying to download files from Forge. Make sure that the access token you're providing to the viewer is valid and that it has access to the model you're trying to view.
If you're still having issues, feel free to shoot us an email to forge (dot) help (at) autodesk (dot) com, and include as many details about your project, for example, how does the access token look like, the URN of your model, your Forge app ID, etc.

Chrome App using Apps Script API

Ran into another road block today, on my path of writing a desktop chrome app for logging users project time.
What i am trying to do (and failing) is use the Apps Script API to access a google sheet that retains the information (project numbers) that i want to populate a drop down in my Chrome App UI.
Update:
I have reworded this as to get to the point and be a little clear on what my issue is.
What i cant seem to achieve is calling the Apps script function from my chrome app. I have read this Execution API but still cant seem to make the coloration. for some reasons i keep getting the "Uncaught ReferenceError: gapi is not defined" in my console.
What i have managed to do is have both the Apps Script and the Chrome App under the same project name in the Developers Console. not sure if its needed but thought it might help with only 1 Oauth2 request.
Is there something my thick head is missing?
Any help or ideas would be much appreciated.
This is my manifest.json
{
"manifest_version": 2,
"name": "TimeSheet",
"description": "Small and easy desktop app for entering time spent on project files",
"version": "0.1.0",
"icons": {
"128": "icon_128.png"
},
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": [
"identity",
"app.window.alwaysOnTop"
],
"oauth2": {
"client_id": "clientid.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets"
]
},
"key": "very long string"
}
This is the bit of Oauth2 code running in my main.js
//This code confirms Oauth2 for access to google drive and related files
window.onload = function(){
document.querySelector("#Oauth2").addEventListener("click", function(){
chrome.identity.getAuthToken({"interactive": true}, function(token){
console.log(token);
});
});
};
// ID of the script to call. Acquire this from the Apps Script editor,
// under Publish > Deploy as API executable.
var scriptId = "blah";
// Create execution request.
var request = {
'function': 'getProjectNumbers',
};
// Make the request.
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': request
});
// Log the results of the request.
op.execute(function(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
if (error.scriptStackTraceElements) {
// There may not be a stacktrace if the script didn't start executing.
console.log('Script error stacktrace:');
for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
var trace = error.scriptStackTraceElements[i];
console.log('\t' + trace.function + ':' + trace.lineNumber);
}
}
} else {
// Here, the function returns an array of strings.
var projectNumbers = resp.response.result;
console.log('Project numbers in spreadsheet:');
projectNumbers.forEach(function(name){
console.log(name);
});
}
});
And this is the apps script code:
var projectDatabaseKey = 'blah'; //Project Database Sheet spreadsheet key
var pprojectDatabaseSheet = 'Project Database'; //Project Database Sheet spreadsheet sheet
//Function to revieve data of project numbers for drop down list
function getProjectNumbers() {
return SpreadsheetApp
.openById(projectDatabaseKey).getSheetByName(projectDatabaseSheet)
.getRange("A2:A" + (SpreadsheetApp.openById(projectDatabaseKey).getSheetByName(projectDatabaseSheet).getLastRow()))
.getValues();
}
I am just really unsure how to use the Oauth2 token and how to apply it to the apps script.
UPDATE
Ok i have tried to call an apps script in a different manor, What i am trying today is using the gapi-chrome-apps.js library to do the oauth2 work.
Now my problem is i get this error, that could be a range of things i am guessing:
POST https://www.googleapis.com/v1/scripts/blahblah:run 404 ()
gapi.client.request # VM80 gapi-chrome-apps.js:105
getSheetsList # gapiCallback.js:17
(anonymous function) # gapiCallback.js:49
callbackWrapper # VM80 gapi-chrome-apps.js:68
target.(anonymous function) # extensions::SafeBuiltins:19
safeCallbackApply # extensions::sendRequest:21
handleResponse # extensions::sendRequest:72
And this Error, that comes from the gapi-chrome-apps.js script:
Uncaught SyntaxError: Unexpected token N in JSON at position 0
Really not sure what is causing this, here is my updated code:
//get listof sheets in spreadsheet
function getSheetsList(){
var scriptId = "blahblah";
// Initialize parameters for function call.
var sheetId = "blahblah";
// Create execution request.
var requests = {
'function': 'getSheetNames',
'parameters': [sheetId],
'devMode': true // Optional.
};
// Make the request.
gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': requests,
'callback': printSheetsList
});
}
// Log the results of the request.
function printSheetsList(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
} else {
// Here, the function returns an array of strings.
var sheetNames = resp.response.result;
console.log('Sheet names in spreadsheet:');
sheetNames.forEach(function(name){
console.log(name);
});
}
}
//Prompts the user for authorization and then proceeds to
function authorize(params, callback) {
gapi.auth.authorize(params, function(accessToken) {
if (!accessToken) {
console.log("Error getting authorization");
} else {
callback();
}
});
}
function gapiIsLoaded() {
var params = { 'immediate': true };
if (!(chrome && chrome.app && chrome.app.runtime)) {
params.scope = "https://www.googleapis.com/auth/drive";
params.client_id = "blahblah";
gapi.auth.init(authorize.bind(null, params, getSheetsList));
} else {
authorize(params, getSheetsList);
}
}
Using traditional GAPI will not work, since it dynamically loads more external scripts and Apps are not allowed to do that.
One possible solution is to run GAPI code in a sandboxed page, which can overcome the remote code restriction. This, however, is cumbersome as you'll need to pass data back and forth using postMessage.
Another way is to try and use Google-provided library gapi-chrome-apps.js, that works in Chrome apps (and uses chrome.identity to manage OAuth) - but please note this comment:
This library is likely not suitable for use without additional modifications.
According to your post, you are simply not defining gapi. You can load it like this
jQuery.getScript( "https://apis.google.com/js/api.js", onApiLoad );
where onApiLoad is the function you would like to call when the gapi is loaded.
For your code, I would wrap the following code in a function like this:
function onApiLoad() {
// Make the request.
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': request
});
// Log the results of the request.
op.execute(function(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
if (error.scriptStackTraceElements) {
// There may not be a stacktrace if the script didn't start executing.
console.log('Script error stacktrace:');
for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
var trace = error.scriptStackTraceElements[i];
console.log('\t' + trace.function + ':' + trace.lineNumber);
}
}
} else {
// Here, the function returns an array of strings.
var projectNumbers = resp.response.result;
console.log('Project numbers in spreadsheet:');
projectNumbers.forEach(function(name){
console.log(name);
});
}
});
}
Edit: no jQuery, only pure JS
Thanks to the second answer in this post, the below code is a pure JS implementation of $.getScript(). It includes a callback, so the code snippet below should work, assuming you wrap your code in a function as described above.
function getScript(source, callback) {
var script = document.createElement('script');
var prior = document.getElementsByTagName('script')[0];
script.async = 1;
prior.parentNode.insertBefore(script, prior);
script.onload = script.onreadystatechange = function( _, isAbort ) {
if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
script.onload = script.onreadystatechange = null;
script = undefined;
if(!isAbort) { if(callback) callback(); }
}
};
script.src = source;
}
getScript("https://apis.google.com/js/api.js", onApiLoad);

Fitbit API OAuth 1.0a from Titanium (Appcelerator)

I am using Titanium (Appcelerator) to connect to Fitbit API. (http://www.appcelerator.com)
I have been facing issues of getting "Invalid Signature" when I am trying to request for token.
I'm using HTTPClient from Titanium.Network.HTTPClient class to send the HTTP Request.
I also uses the oauth-1.0a.js library from https://github.com/ddo/oauth-1.0a to assist in getting the nonce and signature value.
Here is the code:
Ti.include('/oauth/ddo/hmac-sha1.js');
Ti.include('/oauth/ddo/enc-base64-min.js');
Ti.include('/oauth/ddo/oauth-1.0a.js');
function FitBitAuth() {
FitBitAuth.signatureMethod = "HMAC-SHA1";
FitBitAuth.clientKey = 'XXXXXXXXXXXXXXXXXXXXXXXXX';
FitBitAuth.clientSecret = 'XXXXXXXXXXXXXXXXXXXXXXXXXX';
FitBitAuth.nonce = "R#nD0m_$tR!nGss";
FitBitAuth.request_token_url = "https://api.fitbit.com/oauth/request_token";
FitBitAuth.callback_url = "http://www.fitbit.com";
}
FitBitAuth.prototype.createConsumerTokenSecretPair = function() {
return OAuth({
consumer : {
public : FitBitAuth.clientKey,
secret : FitBitAuth.clientSecret
},
signature_method : FitBitAuth.signatureMethod
});
};
FitBitAuth.prototype.getRequestTokenRequestData = function() {
return {
url : "https://api.fitbit.com/oauth/request_token",
method : 'POST'
};
};
FitBitAuth.prototype.requestToken = function() {
var oauth = this.createConsumerTokenSecretPair();
var request_data = this.getRequestTokenRequestData();
var authorized_request = oauth.authorize(request_data, '', FitBitAuth.nonce, FitBitAuth.timestamp);
//alert(authorized_request);
return authorized_request;
};
function auth1a() {
var fb = new FitBitAuth();
var rt = fb.requestToken();
var req = Ti.Network.createHTTPClient();
req.open("POST", FitBitAuth.request_token_url);
req.setRequestHeader('Authorization', 'OAuth oauth_consumer_key="'+FitBitAuth.clientKey+'"');
Ti.API.info(rt);
req.send({
oauth_timestamp : rt.oauth_timestamp,
oauth_nonce : rt.oauth_nonce,
oauth_signature : encodeURIComponent(rt.oauth_signature),
oauth_signature_method: rt.oauth_signature_method,
oauth_callback : encodeURIComponent(FitBitAuth.callback_url),
oauth_version : rt.oauth_version
});
req.onload = function() {
var json = this.responseText;
Ti.API.info("HEADER =====================");
Ti.API.info(req.getAllResponseHeaders());
Ti.API.info("END HEADER =================");
Ti.API.info(json);
var response = JSON.parse(json);
//alert(response);
};
}
I have also tried the Fitbit API Debug tool to assist me in getting all the signature right, in fact the signature and base String do match with the one shown by Fitbit API Debug Tool.
However, I keep getting this Invalid Signature, a sample JSON return is shown below:
{"errors":[{"errorType":"oauth","fieldName":"oauth_signature","message":"Invalid signature: rN**ahem**SGJmFwHp6C38%2F3rMKEe6ZM%3D"}],"success":false}
I have also already tested to do the curl way and it works from Terminal, but to no avail it does not give me a success from Titanium.
Any help is appreciated.
I manage to solve it.
I tried to use another way of inserting the parameters through the header.
Such that the setRequestHeader will look like this:
req.setRequestHeader('Authorization', 'OAuth oauth_consumer_key="'+FitBitAuth.clientKey+'", oauth_nonce="'+rt.oauth_nonce+'", oauth_signature="'+rt.oauth_signature+'",...');
Alternatively, we can also use the built in toHeader feature of the oauth library that I'm using:
oauth.toHeader(oauth_data);
The code above will produce the oauth data in key-value pair.
{
'Authorization' : 'OAuth oauth_consumer_key="xxxxxxxxxxxxx"&oauth_nonce="xxxxxx"&...
}
So instead of the long code for setRequestHeader, we can make use of the value of toHeader, code shown below:
req.setRequestHeader('Authorization', oauth.toHeader(oauth_data).Authorization);
Do note that the return result by fitbit is in plaintext.
auth_token=xxxxxxxx&auth_token_secret=xxxxxxxxx&...

how to retrive User name/login name from gmail?

I am using google login for custom website. here i wrote the code for it
var sOAuthServiceEndPoint = "https://accounts.google.com/o/oauth2/auth?scope=http://gdata.youtube.com https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&response_type=token&";
var sOAuthRedirectURL = "http://example.com/testpage/test.html";
var termsAndCondURL = "termsandcondition.html";
var sOAuthClientID = "294016263542.apps.googleusercontent.com";
var sAuthenticationURL = sOAuthServiceEndPoint + "redirect_uri=" + sOAuthRedirectURL + "&client_id=" + sOAuthClientID;
even i got an access token using below function
function fnOnLoad() {
//alert("Form Loaded");
var sAccessToken = '';
var params = {}, queryString = location.hash.substring(1),regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(queryString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
if(params.error){
if(params.error == "access_denied"){
sAccessToken = "access_denied";
alert(sAccessToken);
}
}else{
sAccessToken = params.access_token;
alert(sAccessToken);
}
window.opener.fnAuthorisationSuccess(sAccessToken);
window.close();
}
It's working succesfully and redirect into the other page where I want. but my problem is how to retrive user login name..?
I am using javascript for it.
Thanks in Advance
This can be found in the documentation.
After your application acquires an access token and has (if necessary) verified it, you can use that access token when making requests to a Google API. If the https://www.googleapis.com/auth/userinfo.profile scope was included in the access token request, then you may use the access token to acquire the user's basic profile information by calling the UserInfo endpoint.
Endpoint: https://www.googleapis.com/oauth2/v1/userinfo
Returns basic user profile information, including name, userid, gender, birthdate, photo, locale, and timezone. If the https://www.googleapis.com/auth/userinfo.email scope was present in the request, then the user's email will also be present in the response. If the email has been verified, then there is also a field that indicates the email is a verified address.

Categories

Resources