authorize chrome app as standalone in vkontakte api - google-chrome-extension

I'm trying to use vk.messages api in chrome app like this:
app.login = function () {
var link = 'https://oauth.vk.com/authorize?' +
'client_id=4837072' +
'&scope=friends,docs,status,messages,notifications,' +
'&redirect_uri=' + 'https://oauth.vk.com/blank.html' +
'&display=popup' +
'&v=API_VERSION' +
'&response_type=token';
var width = 655;
var height = 539;
var left = (screen.width / 2) - (width / 2);
var top = (screen.height / 2) - (height / 2);
chrome.identity.launchWebAuthFlow({
'url': link,
'interactive': true
}, function (redirect_url) {
// redirect_url is undefined
console.log(redirect_url);
});
};
It needs to set auth redirect_uri this way "https://oauth.vk.com/blank.html", but chrome.identity api uses "https://.chromiumapp.org/". So, vk auth window is showed and redirect works, but chrome can't determine auth redirect, and doesn't call back with redirect url. Is there any other way to get redirect url in chrome app?

Well, I see where you're going with "it needs to set redirect_uri this way":
redirect_uri=https://oauth.vk.com/blank.html
It is a mandatory condition for methods which descriptions say that they are available only for Desktop applications.
I'm afraid there is no way to override the URL used by identity API.
In this case, you can go kind of the same route Silver Bird Twitter client went: inject a Content Script in the target address. From there you can extract the tokens needed.
Edit: I forgot that you are writing a Chrome app. You should then try using <webview> and its script injection capabilities.
This means you'll have to do OAuth without help from chrome.identity yourself, but at least it's a solution.

Related

How to include access-token in the HTTP header when requesting a new page from browser

The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:
How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?
Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?
I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.
By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.
By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.
exports.customer_profile = function (req, res) {
var token = req.headers.token;
var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
var decoded = jwt.verify(token, public_key);
var sql = 'SELECT * FROM customer WHERE username = "' + decoded.sub + '"';
util.conn.query(sql, function (err, rows) {
if (!err) {
for (var i = 0; i < rows.length; i++) {
res.render('customer_profile', {customer_profile: rows[i]});
break;
}
}
});
};
I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.
So far what I was able to figure out is the following:
Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
// Get current user's ID Token
firebase.auth().currentUser.getIdToken()
.then(token => {
// Make a fetch request to 'path'
return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
method: 'GET',
headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
});
})
.then(response => {
// As noted below, this part I haven't solved yet.
// TODO: Open response as new webpage instead of displaying as data in existing one
return response.text();
})
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
}
Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup).
// getLocale - language negotiation.
// getContext - auth token verification if it is available and appends it to Request object for convenience
app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);
// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
if(!idToken) {
return next(); // Passes to next middleware if no token, terminates further execution
}
admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
.then(token => {
req.decoded_token = token; // Append token to Request object for convenience in further middleware
return next(); // Pass on further
})
.catch(error => {
console.log('Request not authorized', 401, error)
return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
});
}
Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user
router.get('/console', middleware.getTranslation('console'), (req, res) => {
if(req.decoded_token) { // if token was verified successfully and is appended to req
res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
} else {
res.status(401).send('Not authorized'); // else send 401 to user
}
});
As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access
What I have not solved yet:
As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.
Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet

Chrome DevTools extension can't access storage

I'm writing a Chrome extension where I would like to access the Chrome Storage API from the Devtools page. The chrome.storage.local variable (with the get/set functions) are available, but when I call them the callback never get's called. When I use my background page to access the storage API it works as expected.
I could, of course, send the request via the background page and pass it to the Devtools page through messages but I'm looking for a more cleaner approach. Am I doing something wrong?
I hope that you understand my question, to illustrate my problem I have created a little sample of what I'm coping with, ready for download here; http://g2f.nl/0w2kko9.
It includes the following test which verifies that the API is available but does not work as expected;
document.getElementById("p").innerHTML += "chrome.storage.local.set = " + !!chrome.storage.local.set
+ "<br>chrome.storage.local.get = " + !!chrome.storage.local.get;
chrome.storage.local.set( {'foo' : 'bar'} , function () {
document.getElementById("p").innerHTML += '<br>foo = bar';
});
setTimeout(function () {
document.getElementById("p").innerHTML += '<br>That was one second';
chrome.storage.local.get('foo', function (result) {
document.getElementById("p").innerHTML += '<br>foo = ' + result;
});
}, 1000);

Why can't I use credentials to make followup oauth call

I am using passport-freshbooks to authenticate and retrieve a token and tokenSecret. However, when I try to use those with a separate OAuth object, I get a 401 authentication failed error.
The strategy used by passport-freshbooks uses the same oauth library, and the call to "post" is identical to the followup call (at least it looks the same to me, but maybe I'm missing something obvious).
Here's some of the pertinent code from the passport strategy:
OAuth = require('oauth').OAuth //This is called from within require('passport-oauth').OAuthStrategy
...
this._oauth = new OAuth('https://' + options.subdomain + '.freshbooks.com/oauth/oauth_request.php',
'https://' + options.subdomain + '.freshbooks.com/oauth/oauth_access.php',
freshbookDao.config.apiSubdomain,
freshbookDao.config.oauthSecret,
"1.0",
null,
"PLAINTEXT",
null,
options.customHeaders);
...
log.info("Calling userProfile with " + token + " and " + tokenSecret);
...
this._oauth.post(url, token, tokenSecret, post_body, post_content_type, function (err, body, res) {...}
I try to use that same token and tokenSecret later. I'm creating a new OAuth object, but setting it with the identical settings passed to the passport strategy. Here's some code from that:
OAuth = require('oauth')
...
oauth = new OAuth.OAuth(
'https://' + options.subdomain + '.freshbooks.com/oauth/oauth_request.php',
'https://' + options.subdomain + '.freshbooks.com/oauth/oauth_access.php',
exports.config.apiToken,
exports.config.oauthSecret,
'1.0',
null,
'PLAINTEXT');
...
log.info("Calling listInvoices with " + token + " and " + tokenSecret);
...
oauth.post(url, token, tokenSecret, body, 'application/xml', function(err, data, res) {...}
These look the same to me. However, the first one passes, and the second fails with this response:
{"statusCode":401,"data":"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<response xmlns=\"http://www.freshbooks.com/api/\" status=\"fail\">\n <error>Authentication failed.</error>\n <code>20010</code>\n</response>\n"} <code>20010</code>\n</response>\n"}
What is it that I'm doing wrong? I've included to "log.info" lines to show that I've compared the token and tokenSecret, and they are indeed the same. What is it I'm missing?
glad you're getting use out of passport-freshbooks!
I didn't write the OAuth code in it. I copied this from Jared Hanson's passport-linkedin module, then tweaked it to work with Freshbooks
If you're getting different outputs, then one of two things is happening:
1) Either you're sending different inputs, or
2) there is a different internal state.
For 1) try logging requests to a file and see what happens: Logging in express js to a output file?
is your app sending different requests?
For 2) I don't know the OAuth protocol well enough to debug it. Just enough to use it. It may be that you can't reuse the tokens on a different connection? I can't say for sure.
Hope that helps Todd!

Chrome Omnibox extension to post form data to a website?

How can an Omnibox extension create and post form data to a website and then display the result?
Here's an example of what I want to do. When you type lookup bieber into the Omnibox, I want my extension to post form data looking like
searchtype: all
searchterm: bieber
searchcount: 20
to the URL http://lookup.com/search
So that the browser will end up loading http://lookup.com/search with the results of the search.
This would be trivial if I could send the data in a GET, but lookup.com expects an HTTP POST. The only way I can think of is to inject a form into the current page and then submit it, but (a) that only works if there is a current page, and (b) it doesn't seem to work anyway (maybe permissions need to be set).
Before going off down that route, I figured that somebody else must at least have tried to do this before. Have you?
You could do this by using the omnibox api:
chrome.omnibox.onInputChanged.addListener(
function(text, suggest) {
doYourLogic...
});
Once you have you extension 'activated' due to a certain keyword you typed you can call something like this:
var q = the params you wish to pass
var url = "http://yourSite.com";
var req = new XMLHttpRequest();
req.open("POST", url, true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.onreadystatechange = function() {
if (req.readyState == 4) {
callback(req.responseXML);
}
}
req.send(q);

Cross domain iframe resizer using postMessage

I've read all the cross domain iframe posts here (my thanks to all of you!) and elsewhere.
The postMessage script at cross-domain iframe resizer? works beautifully in Firefox 5 and up. It resizes the iframe every time a page is clicked within the iframe perfectly.
But it doesn't resize at all in IE (7 8 or 9) on my computer. I checked the security settings and the one in IE for access across domains was checked to enable.
Does postMessage not work in IE? - Or is there something else that needs to be added? thanks
It's a great script from thomax - it also works on so you can use iframes on mobile - iphones and android
For IE7 and IE8, you have to use window.attachEvent instead of window.addEventListener
It should also be onmessage instead of message (see below) ps you also need to do the same on the server with the content posting its size
<script type="text/javascript">
if (window.addEventListener)
{
function resizeCrossDomainIframe(id) {
var iframe = document.getElementById(id);
window.addEventListener('message', function(event) {
var height = parseInt(event.data) + 32;
iframe.height = height + "px";
}, false);
}
}
else if (window.attachEvent)
{
function resizeCrossDomainIframe(id) {
var iframe = document.getElementById(id);
window.attachEvent('onmessage', function(event) {
var height = parseInt(event.data) + 32;
iframe.height = height + "px";
}, false);
}
}
</script>
Using Peter's code and some ideas from here, you could separate out the compatibility from the executable code, and add some cross-site validation.
<script type="text/javascript">
// Create browser compatible event handler.
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen for a message from the iframe.
eventer(messageEvent, function(e) {
if (e.origin !== 'http://yourdomain.com' || isNaN(e.data)) return;
document.getElementById('iframe_id_goes_here').style.height = e.data + 'px';
}, false);
</script>
Also, for completeness, you could use the following code within the iframe whenever you want to trigger the resize.
parent.postMessage(document.body.offsetHeight, '*');
You can use the implementation of Ben Alman. Here is an example of cross-domain communication, including an example of iframe resize.
http://benalman.com/code/projects/jquery-postmessage/examples/iframe/
According to the documentation, it works on Internet Explorer 6-8, Firefox 3, Safari 3-4, Chrome, Opera 9.
Having looked a lots of different solutions to this I ended up writing a simple jQuery plugin to take a account of a number of different use cases. As I needed a solution that supported multiple user generated iFrames on a portal page, supported browser resizes and could cope with the host page JavaScript loading after the iFrame. I also added support for sizing to width and a callback function and allow the override of the body.margin, as you will likely want to have this set to zero.
https://github.com/davidjbradshaw/iframe-resizer
The host page users jQuery, the iframe code is just a little self-contained JavaScript, so that it's a good guest on other people pages.
The jQuery is then initialised on the host page and has the following available options. More details on what these do on the GitHub page.
$('iframe').iFrameSizer({
log: false,
contentWindowBodyMargin:8,
doHeight:true,
doWidth:false,
enablePublicMethods:false,
interval:33,
autoResize: true,
callback:function(messageData){
$('p#callback').html('<b>Frame ID:</b> ' + messageData.iframe.id +
' <b>Height:</b> ' + messageData.height +
' <b>Width:</b> ' + messageData.width +
' <b>Event type:</b> ' + messageData.type);
}
});
If you set enablePublicMethods, it gives you access in the iframe to manually set the iFrame size and even remove the iframe from the host page.

Resources