Accessing Google Drive from a Firefox extension - javascript

I'm trying to access (CRUD) Google Drive from a Firefox extension. Extensions are coded in Javascript, but neither of the two existing javascript SDKs seem to fit; the client-side SDK expects "window" to be available, which isn't the case in extensions, and the server-side SDK seems to rely on Node-specific facilities, as a script that works in node no longer does when I load it in chrome after running it through browserify. Am I stuck using raw REST calls? The Node script that works looks like this:
var google = require('googleapis');
var readlineSync = require('readline-sync');
var CLIENT_ID = '....',
CLIENT_SECRET = '....',
REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob',
SCOPE = 'https://www.googleapis.com/auth/drive.file';
var oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
var url = oauth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: SCOPE // If you only need one scope you can pass it as string
});
var code = readlineSync.question('Auth code? :');
oauth2Client.getToken(code, function(err, tokens) {
console.log('authenticated?');
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
console.log('authenticated');
oauth2Client.setCredentials(tokens);
} else {
console.log('not authenticated');
}
});
I wrap the node GDrive SDK using browserify on this script:
var Google = new function(){
this.api = require('googleapis');
this.clientID = '....';
this.clientSecret = '....';
this.redirectURL = 'urn:ietf:wg:oauth:2.0:oob';
this.scope = 'https://www.googleapis.com/auth/drive.file';
this.client = new this.api.auth.OAuth2(this.clientID, this.clientSecret, this.redirectURL);
}
}
which is then called using after clicking a button (if the text field has no code it launches the browser to get one):
function authorize() {
var code = document.getElementById("code").value.trim();
if (code === '') {
var url = Google.client.generateAuthUrl({access_type: 'offline', scope: Google.scope});
var win = Components.classes['#mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('navigator:browser');
win.gBrowser.selectedTab = win.gBrowser.addTab(url);
} else {
Google.client.getToken(code, function(err, tokens) {
if(!err) {
Google.client.setCredentials(tokens);
// store token
alert('Succesfully authorized');
} else {
alert('Not authorized: ' + err); // always ends here
}
});
}
}
But this yields the error Not authorized: Invalid protocol: https:

It is possible though, depending on the use case, it might also of limited interest.
Firefox ships with a tiny http server, just the bare bones. It is included for test purposes but this is not a reason to overlook it.
Lets follow the quickstart guide for running a Drive app in Javascript
The tricky part is to set the Redirect URIs and the Javascript Origins. Obviously the right setting is http://localhost, but how can you be sure that every user has port 80 available?
You can't and, unless you have control over your users, no port is guaranteed to work for everyone. With this in mind lets choose port 49870 and pray.
So now Redirect URIs and the Javascript Origins are set to http://localhost:49870
Assuming you use Add-on SDK, save the quickstart.html (remember to add your Client ID) in the data directory of your extension. Now edit your main.js
const self = require("sdk/self");
const { Cc, Ci } = require("chrome");
const tabs = require("sdk/tabs");
const httpd = require("sdk/test/httpd");
var quickstart = self.data.load("quickstart.html");
var srv = new httpd.nsHttpServer();
srv.registerPathHandler("/gdrive", function handler(request, response){
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
let converter = Cc["#mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
response.write(converter.ConvertFromUnicode(quickstart));
})
srv.start(49870);
tabs.open("http://localhost:49870/gdrive");
exports.onUnload = function (reason) {
srv.stop(function(){});
};
Notice that quickstart.html is not opened as a local file, with a resource: URI. The Drive API wouldn't like that. It is served at the url http://localhost:49870/gdrive. Needless to say that instead of static html we can use a template or anything else. Also the http://localhost:49870/gdrive can be scripted with a regular PageMod.
I don't consider this a real solution. It's just better than nothing.

From here https://developer.mozilla.org/en/docs/Working_with_windows_in_chrome_code you could try window = window || content || {}
Use the JavaScript client API and not the node.js client. Although browserify will make it work. You will have to expose your client secret in the latter. The flow of client side authentication is very diff than server side. Refer to https://developers.google.com/accounts/docs/OAuth2
Having said all this. Its really not that difficult to implement an app with REST based calls. The methods in all client libraries mimic the corresponding REST URLs. You could set up some functions of your own to handle request and response and the rest would feel the same.

Related

how to use Cryptocapital API v4, can't find sdk download link

Cryptocapital.co provides API to exchange crypto currency, I want to access their API to integrate with a website, they do have a documentation here - https://api.cryptocapital.co/v4
and they have a sample code as below
var key = '1234567890abcdef';
var secret = '1234567890abcdef';
var command = 'PING';
var nonce = Date.now();
var message = command + nonce;
var signature = CryptoJS.SHA1(message + key + secret);
var options = {
url: 'https://api.cryptocapital.co/v4/ping',
headers: {
'key': key,
'message': message,
'signature': signature,
'nonce': nonce
}
};
request(options, function(err, res, body) {
// do something
// ...
});
There is no download link or reference to any SDK, when i run this code it says
request is not defined
I don't know where to start in this specific API.
please see the documentation and help me on identifying what I am doing wrong.
It's making the assumption that your using the popular request lib to make the HTTP request.
You can, however, use whatever library you like. Or don't use a library at all, use https.get.

Authenticating to S3 from Meteor Mobile Application

I have a Meteor Mobile app that accesses a lot of photos stored in my S3 bucket. These photos are user uploaded and change frequently. I don't want these photos to be accessible to anyone that isn't using my app. (ie: these photos are only viewable from my application and going to the url directly in a browser won't load them).
What is the best way to accomplish this? AWS Cognito seems to be the logical choice, but it doesn't seem easy to implement and I'm not exactly sure how to authenticate to AWS from the client once it gets a Cognito identity.
My other thought was putting a read only AWS Key on every url and authenticating that way, but that's almost pointless. It would be really easy to find out the key and secret.
EDIT:
To be specific, the URLs for the images are in a Mongo collection and I pass them into a template. So, the S3 resources are just loaded up with an image tag (<img src="). Something like AWS STS sounds like a great option, but I don't know of a way to pass the tokens in the headers when I'm loading them like this. Doing them as a pre-signed query string seems inefficient.
Another option is to restrict access with the referrer header, like this issue. But like Martijn said, it isn't really a secure way of doing it.
After some research and testing I solved this myself. My ultimate solution was to use the referer header to limit access to my S3 bucket. I created a more secure and detailed solution (see below), but it came with a performance hit that wouldn't work for my app. My app is based around viewing photos and videos, and not being able to have them load near instantly wasn't in the cards. Although, I feel like it could be a sufficient solution for most use-cases. Because my app isn't highly sensitive, the referer header is sufficient for me. Here is how to use the http header referer to limit access to a bucket.
Solution using Amazon's STS:
First, you need to have the AWS SDK on both the server and the client. There was no up to date packages for Meteor available, so I created my own. (I'll publish it shortly and put a link here once I do.)
On the server, you must use credentials that have the ability to assume a role. The role to be assumed must have a Trust Relationship with the user that is assuming the role. Article on using IAM. - Article on using credentials with SDK
In the server.js file I created a Meteor Method that I can call from the client. It first checks if a user is logged in. If that's true, it checks to see if it's current temp-credentials are expiring in the next 5 minutes. If they are, I issue new credentials and either write them to the user document or return them as a callback. If they aren't expiring in the next 5 minutes, I return their current temp-credentials.
You must use Meteor.bindEnvironmentfor the callback. See docs
Meteor.methods({
'awsKey': function(){
if (Meteor.userId()){
var user = Meteor.userId();
var now = moment(new Date());
var userDoc = Meteor.users.findOne({_id: user});
var expire = moment(userDoc.aws.expiration);
var fiveMinutes = 5 * 60 * 1000;
var fut = new Future();
if(moment.duration(expire.diff(now))._milliseconds < fiveMinutes ){
var params = {
RoleArn: 'arn:aws:iam::556754141176:role/RoleToAssume',
RoleSessionName: 'SessionName',
DurationSeconds: 3600 //1 Hour
};
var sts = new AWS.STS();
sts.assumeRole(params, Meteor.bindEnvironment((err, data) => {
if (err){
fut.throw(new Error(err));
}else{
Meteor.users.update({_id: user}, {$set: {aws: {accessKey: data.Credentials.AccessKeyId, secretKey: data.Credentials.SecretAccessKey, sessionToken: data.Credentials.SessionToken, expiration: data.Credentials.Expiration}}});
fut.return(data.Credentials);
}
}));
return fut.wait();
}else{
return userDoc.aws;
}
}
}
}
});
Then you can invoke this method manually or in an setInterval on Meteor.startup.
Meteor.setInterval(function(){
if(Meteor.userId()){
Meteor.call('awsKey', function(err, data){
if (err){
console.log(err);
}else{
if(data.accessKey){
Session.set('accessKey', data.accessKey);
Session.set('secretKey', data.secretKey);
Session.set('sessionToken', data.sessionToken);
}else{
Session.set('accessKey', data.AccessKeyId);
Session.set('secretKey', data.SecretAccessKey);
Session.set('sessionToken', data.SessionToken);
}
}
});
}
}, 300000); //5 Minute interval
This way just sets the keys in a Session variable from the callback. You could do this by querying the user's document to get them as well.
Then, you can use these temporary credentials to get a signed URL for the object you are trying to access in your bucket.
I put this in a template helper by passing the object name to it in the template:
{{getAwsUrl imageName}}
Template.templateName.helpers({
'getAwsUrl': function(filename){
var accessKey = Session.get('accessKey');
var secretKey = Session.get('secretKey');
var sessionToken = Session.get('sessionToken');
var filename = filename;
var params = {Bucket: 'bucketName', Key: filename, Expires: 6000};
new AWS.S3({accessKeyId: accessKey, secretAccessKey: secretKey, sessionToken: sessionToken, region: 'us-west-2'}).getSignedUrl('getObject', params, function (err, url) {
if (err) {
console.log("Error:" +err);
}else{
result = url;
}
});
return result;
}
});
That's all there is to it! I'm sure this can be refined to be better, but this is just what I came up with in testing it really fast. Like I said, it should work in most use cases. My particular one didn't. For some reason, when you tried to toggle the visibility: visible|hidden; on an img src of these signedURLs they would take a lot longer to load than just setting the URL directly. It must be because Amazon has to decrypt the signed URL on their side before return the object.
Thanks to Mikkel for the direction.

JS require in QML

I am using the dbox library in a QML app (sources available at github). In a QML file I import the dbox library with the following code:
import "./dbox-master/lib/dbox.js" as Dbox
Then I use it in this way:
var app = Dbox.app({ "app_key": root.appKey, "app_secret": root.appSecret })
However, in the dbox.js there're series of require statements, at the top of the file:
define(['require', 'request', 'querystring', 'path'], function (require) {
var request = require('request');
var qs = require('querystring');
var path = require('path');
var helpers_ = require("./helpers")
// var request = require('request');
});
//var request = require("request")
//var qs = require("querystring")
//var path = require("path")
//require(['request'], function (foo) {
// console.log('request is loaded')
//});
exports.app = function(config){
var root = config.root || "sandbox"
var helpers = helpers_(config)
return {
root: root,
requesttoken: function(cb){
var signature = helpers.sign({})
var body = qs.stringify(signature)
var args = {
"method": "POST",
"headers": {
"content-type": "application/x-www-form-urlencoded",
"content-length": body.length
},
"url": "https://api.dropbox.com/1/oauth/request_token",
"body": body
}
return request(args, function(e, r, b){
var obj = qs.parse(b)
obj.authorize_url = "https://www.dropbox.com/1/oauth/authorize?oauth_token=" + obj.oauth_token
cb(e ? null : r.statusCode, obj)
})
},...
As you see, I've changed the code to get the dbox.js to work but the require is not defined. How to use the require.js properly?
Update.
As I found out, the problem is in the host environment. The QML global space is constant. Node.js requires objects to be present in the space (e.g. iself) and importing into the global space. There is a project on github glueing Node.js and QML but it is not finished yet. I propose another solution: make a C++ plugin to run a script in js. The script is run in the Node.js environment to communicate Dropbox account information to the Quick-based application.
What is the best approach to use a web API? Dropbox in this case.
It depends.
Since I need to list all files and download a file, the best choice is to use the Dropbox API manually.
I used OAuth2 to authorize the application, the Token Flaw, because it is fast.
WebView was the app. built-in browser. Once a redirect_uri is set as the WebView url property value then it means the authorization is passed. The access token is returned with the redirection.
Note.
The redirect_uri must be equal to the redirect_uri set in the application AppConsole in the app. Dropbox account.
Result: I did not use the 3d party Dropbox JS libraries, it is much faster to send requests manually.

Google API in Javascript

I am trying to get calendar info from google in javascript. I ve read 'how to' manuals. They didn't help. Even this 'helpful' copypasted code (to authorize) didn't. Would somebody be so kind to teach me how to use google api? Maybe someone has some samples to share
And this beautiful js code :
<html>
<button id="authorize-button" onclick='handleAuthClick()'>Authorize</button>
<script type="text/javascript">
var clientId = '***';
var apiKey = '***';
var scopes = 'https://www.googleapis.com/auth/plus.me';
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.visibility = 'hidden';
makeApiCall();
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick(event) {
// Step 3: get authorization to use private data
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
return false;
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
// Step 4: Load the Google+ API
gapi.client.load('plus', 'v1', function() {
// Step 5: Assemble the API request
var request = gapi.client.plus.people.get({
'userId': 'me'
});
// Step 6: Execute the API request
request.execute(function(resp) {
var heading = document.createElement('h4');
var image = document.createElement('img');
image.src = resp.image.url;
heading.appendChild(image);
heading.appendChild(document.createTextNode(resp.displayName));
document.getElementById('content').appendChild(heading);
});
});
}
</script>
Error Message (from Console):
'Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('file://') does not match the recipient window's origin ('null').'
so im stuck on 'gapi.auth.authorize'. nothing works after
Based on the error you're receiving, my guess is that you either do not have your Javascript Origin configured properly on the Google API console you got your Client ID from, and/or you are trying to run your script from the file system instead of through a web server, even one running on localhost. The Google API client, near as I've been able to tell, does not accept authorization requests from the file system or any domain that has not been configured to request authorization under the supplied Client ID.
Google API Console reference :
In Client ID for web application:
Javascript Origins : http://localhost:3000/
Key for browser applications:
Referers : http://localhost:3000/
localhost would work 100%
i got the same error and as you preferred, after running html file in my local web server problem solved.
i created credentials for web application and set following values both to my local with "http://localhost:5000" string
"Authorized JavaScript origins"
"Authorized redirect URIs
i checked the json file too. i got the following json file as a result.
{"web":
{
"client_id":"myClientID",
"project_id":"my-project",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"XqXmgQGrst4xkZ2pgJh3Omxg",
"redirect_uris":["http://localhost:5000"],
"javascript_origins":["http://localhost:5000"]
}
}
https://developers.google.com/drive/v2/web/auth/web-client
Some APIs will work fine when queried from local files, but some won't.
In response to an error such as yours, try to serve your files from a web server. If you need a quick web server running, use Python's builtin HTTP server (Mac OSX and Linux systems have Python pre-installed). This HTTP server can turn any directory in your system into your web server directory. cd into your project directory and run the following command:
python -m SimpleHTTPServer 3000 The number at the end is the port number your http server will start in and you can change that port number. In our example, your directory would be served from: http://localhost:3000.

Meteor login via Third party library

I'm trying to login to my meteor site via a third party library like this one:
https://gist.github.com/gabrielhpugliese/4188927
In my server.js i have:
Meteor.methods({
facebook_login: function (fbUser, accessToken) {
var options, serviceData, userId;
serviceData = {
id: fbUser.id,
accessToken: accessToken,
email: fbUser.email
};
options = {
profile: {
name: fbUser.name
}
};
userId = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options);
return userId;
}, ......
In my client.js I have:
facebookLogin: function () {
if (Meteor.user())
return;
if (!Session.equals("deviceready", true))
return;
if (!Session.equals("meteorLoggingIn", false))
return;
// Do not run if plugin not available
if (typeof window.plugins === 'undefined')
return;
if (typeof window.plugins.facebookConnect === 'undefined')
return;
// After device ready, create a local alias
var facebookConnect = window.plugins.facebookConnect;
console.log('Begin activity');
Session.equals("meteorLoggingIn", true);
Accounts._setLoggingIn(true);
facebookConnect.login({
permissions: ["email", "user_about_me"],
appId: "123456789012345"
}, function (result) {
console.log("FacebookConnect.login:" + JSON.stringify(result));
// Check for cancellation/error
if (result.cancelled || result.error) {
console.log("FacebookConnect.login:failedWithError:" + result.message);
Accounts._setLoggingIn(false);
Session.equals("meteorLoggingIn", false);
return;
}
var access_token = result.accessToken;
Meteor.call('facebook_login', result, access_token, function (error, user) {
Accounts._setLoggingIn(false);
Session.equals("meteorLoggingIn", false);
if (!error) {
var id = Accounts._makeClientLoggedIn(user.id, user.token);
console.log("FacebookConnect.login: Account activated " + JSON.stringify(Meteor.user()));
} else {
// Accounts._makeClientLoggedOut();
}
});
});
}, // login
facebookLogout: function () {
Meteor.logout();
// var facebookConnect = window.plugins.facebookConnect;
// facebookConnect.logout();
},
The third party library (Facebook Android SDK in my case) works fine. My problem is after the "var id = Accounts._makeClientLoggedIn(user.id, user.token);" the Meteor.user() returns Undefined. However If I do a page refresh in the browser works fine and the template renders as a logged in user.
Anyone knows how to fix the 'Undefined' on client ??
PS. On server side the users collection looks fine. The meteor token and everything else are there.
Solved. I had to add : this.setUserId(userId.id);
after userId = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options); at server.js
Meteor's client side javascript can't run fibers. Fibers allows synchronous code to be used with javascript since by design js is asynchronous. This means there are callbacks that need to be used to let you know when the task is complete.
From what it looks like Accounts._makeClientLoggedIn doesn't take a callback & unfortunately and doesn't return any data looking at its source. I can't say i've tried this myself because I can't test your code without the android sdk but have you tried using Deps.flush to do a reactive flush?
Also Meteor also has very clean and easy facbeook integration. If you simply add the facebook meteor package
meteor add accounts-facebook
You can get access to a lovely Meteor.loginWithFacebook method that can make everything reactive and your code simpler and really easy. If you need to modify it to use the Android SDK Dialog instead you can easily modify the code out as the code for the module is out there for you to hack up to your spec
Edit: If you're using an external SDK such as the java SDK/cordova plugin
Set your plugin so that it redirects to the following URL (set up for meteor.com hosting):
http://yourmeteorapp.meteor.com/_oauth/facebook?display=touch&scope=your_scope_request_params&state=state&code=yourOAuthCodeFromJava&redirect=YourAPP
So in the querystring we have:
scope= Contains your facebook scope params (for permissions)
code= Your OAuth code from the java sdk
redirect=Where to redirect to after once logged in instead of the window.close
state= A cros site forgery state value, any random value will do
This url is basically used to mimic would what be given to the REDIRECT_URI at : https://developers.facebook.com/docs/reference/dialogs/oauth/
This will redirect to meteor's OAuth helper (at https://github.com/meteor/meteor/blob/master/packages/accounts-oauth-helper/oauth_server.js)
So what would happen is you give the OAuth code from Java to meteor, it fetches the OAuth token and the user's data, then redirect the user to a URL in your app

Categories

Resources