Skip to content
Merged
Prev Previous commit
Next Next commit
Pickup the token and secret from the InAppBrowser's url hash.
Since we do not rely on executeScript this will work if Content Security Policy prevents inline scripts.
  • Loading branch information
jperl committed Jun 9, 2014
commit 5943c7241aa7d60f5c729846c57f92d3f6a7ab0b
6 changes: 4 additions & 2 deletions package.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Package.describe({
});

Package.on_use(function (api) {
api.add_files(["oauth_patch.js"], "client");
});
api.use(['logging', 'oauth'], 'server');

api.add_files('patch_window.js', 'client');
api.add_files('patch_login_response.js', 'server');
});
42 changes: 42 additions & 0 deletions patch_login_response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Patch OAuth._endOfLoginResponse to expose the credentialToken and
// credentialSecret in the url for the InAppBrowser to retrieve.
// See https://groups.google.com/forum/#!topic/meteor-core/Ma3XTZk4Kqg and
// https://github.com/meteor/meteor/blob/3dfcb42eac34f40b6611a0a0a780b9210467ccb2/packages/oauth/oauth_server.js#L219-L255
OAuth._endOfLoginResponse = function (res, details) {
res.writeHead(200, {'Content-Type': 'text/html'});

var content = function (setCredentialSecret) {
return '<html><head><script>' +
setCredentialSecret +
'window.close()</script></head></html>';
};

if (details.error) {
Log.warn("Error in OAuth Server: " +
(details.error instanceof Error ?
details.error.message : details.error));
res.end(content(""), 'utf-8');
return;
}

if ("close" in details.query) {
// If we have a credentialSecret, report it back to the parent
// window, with the corresponding credentialToken. The parent window
// uses the credentialToken and credentialSecret to log in over DDP.
var setCredentialSecret = '';
if (details.credentials.token && details.credentials.secret) {
setCredentialSecret = 'var credentialToken = ' +
JSON.stringify(details.credentials.token) + ';' +
'var credentialSecret = ' +
JSON.stringify(details.credentials.secret) + ';' +
'if (window.opener) window.opener.Package.oauth.OAuth._handleCredentialSecret(' +
'credentialToken, credentialSecret);' +
// add the token and secret to the hash
'else window.location.hash = "credentialToken=" + credentialToken + ' +
'"&credentialSecret=" + credentialSecret;';
}
res.end(content(setCredentialSecret), "utf-8");
} else {
res.end("", "utf-8");
}
};
37 changes: 15 additions & 22 deletions oauth_patch.js → patch_window.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
// https://github.com/zeroasterisk/MeteorRider
window.patchWindow = function () {

// Check if the InAppBrowser is loaded.
// Make sure the InAppBrowser is loaded before patching the window.
var inAppBrowserLoaded = !!(window.cordova && window.cordova.require('org.apache.cordova.inappbrowser.inappbrowser'));
if (!inAppBrowserLoaded) return false;

// Keep a reference to the in app browser's window.open.
var __open = window.open,
oauthWin,
timer;
Expand All @@ -20,12 +21,11 @@ window.patchWindow = function () {
// open/closed state of popup to keep Meteor happy. Allows one to save window
// reference to a variable and close it later. e.g.,
// `var foo = window.open('url'); foo.close();
//
window.IAB = {
closed: true,

open: function (url) {
// TODO add options param and append to current options
// XXX add options param and append to current options
oauthWin = __open(url, '_blank', 'location=no,hidden=yes');

oauthWin.addEventListener('loadstop', checkIfOauthIsDone);
Expand All @@ -34,7 +34,7 @@ window.patchWindow = function () {
// each #show call. Lets events run on Meteor app, otherwise all listeners
// will *only* run when tapping done button or oauthWin.close
//
// FIXME should be a better way to do this...
// XXX should be a better way to do this
if (device.platform === 'Android') {
timer = setInterval(oauthWin.show, 200);
} else {
Expand All @@ -43,25 +43,17 @@ window.patchWindow = function () {

// check if uri contains an error or code param, then manually close popup
function checkIfOauthIsDone(event) {
// XXX improve this regex to match twitters as well
if (!event.url || !event.url.match(/error|code=/)) return;

oauthWin.executeScript({code: 'document.querySelector("script").innerHTML'}, function (res) {
// Get the credentialToken and credentialSecret from the InAppBrowser's url hash.
var hashes = event.url.slice(event.url.indexOf('#') + 1).split('&');
var credentialToken = hashes[0].split('=')[1];
var credentialSecret = hashes[1].split('=')[1];

var js = res[0].replace('window.close()', '').replace('window.opener && window.opener.', '');

//js = something like:
//Package.oauth.OAuth._handleCredentialSecret("RiU_bla_bla_9qQ2", "BtK_bla_bla_4Q");
//the first parameter is the "credentialToken" and the second the "credentialSecret"
eval(js);

//now code similar to this is what would usually be called after the popup closes
var credentialToken = js.match(/"(.+)",/)[0].replace(/"/g, '').replace(/,/, '');
Accounts.oauth.tryLoginAfterPopupClosed(credentialToken)
//TO DO: preserve callback to Meteor.loginWithTwitter(options, callback)
//FOR NOW: use Deps.autorun(function() {if(Meteor.user()) //do something});

oauthWin.close();
});
Package.oauth.OAuth._handleCredentialSecret(credentialToken, credentialSecret);
Accounts.oauth.tryLoginAfterPopupClosed(credentialToken);
oauthWin.close();

clearInterval(timer);
oauthWin.removeEventListener('loadstop', checkIfOauthIsDone)
Expand All @@ -86,12 +78,13 @@ window.patchWindow = function () {
};
}

// Clear patchWindow to prevent it from being called again.
// Clear patchWindow to prevent it from being called multiple times.
window.patchWindow = function () {
};

return true;
};

// If the InAppBrowser is not loaded yet wait until cordova is finished loading.
// Patch the window after cordova is finished loading
// if the InAppBrowser is not available yet.
if (!window.patchWindow()) document.addEventListener('deviceready', window.patchWindow, false);