Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

freedom-social-xmpp

Package Overview
Dependencies
Maintainers
7
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

freedom-social-xmpp - npm Package Compare versions

Comparing version 0.3.15 to 0.4.6

329

dist/google-auth.js

@@ -12,3 +12,8 @@ /*globals freedom:true,setTimeout,VCardStore,XMPPSocialProvider */

XMPPSocialProvider.prototype.oAuthScope = "email%20profile%20https://www.googleapis.com/auth/googletalk&";
XMPPSocialProvider.prototype.clientSecret = "h_hfPI4jvs9fgOgPweSBKnMu";
// These credentials are for a native app.. but we can't change the redirect URL.
// XMPPSocialProvider.prototype.oAuthClientId = "746567772449-mv4h0e34orsf6t6kkbbht22t9otijip0.apps.googleusercontent.com";
// XMPPSocialProvider.prototype.clientSecret = "M-EGTuFRaWLS5q_hygpJZMBu";
/**

@@ -23,2 +28,5 @@ * Begin the login view, potentially prompting for credentials.

* network - A string used to differentiate this provider in events.
* interactive - Login will attempt to re-use the last remembered
* login credentials if this is set to false.
* rememberLogin: Login credentials will be cached if true.
*/

@@ -31,40 +39,34 @@ XMPPSocialProvider.prototype.login = function(loginOpts, continuation) {

if (!this.credentials) {
this.oauth = freedom["core.oauth"]();
this.oauth.initiateOAuth(this.oAuthRedirectUris).then(function(stateObj) {
var oauthUrl = "https://accounts.google.com/o/oauth2/auth?" +
"client_id=" + this.oAuthClientId +
"&scope=" + this.oAuthScope +
"&redirect_uri=" + encodeURIComponent(stateObj.redirect) +
"&state=" + encodeURIComponent(stateObj.state) +
"&response_type=token";
var url = 'https://accounts.google.com/accountchooser?continue=' +
encodeURIComponent(oauthUrl);
return this.oauth.launchAuthFlow(url, stateObj);
}.bind(this)).then(function(continuation, responseUrl) {
var token = responseUrl.match(/access_token=([^&]+)/)[1];
var xhr = freedom["core.xhr"]();
xhr.open('GET', 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json', true);
xhr.on("onload", function(continuation, token, xhr) {
xhr.getResponseText().then(function(continuation, token, responseText) {
var response = JSON.parse(responseText);
var credentials = {
userId: response.email,
jid: response.email,
oauth2_token: token,
oauth2_auth: 'http://www.google.com/talk/protocol/auth',
host: 'talk.google.com'
};
this.onCredentials(continuation, {cmd: 'auth', message: credentials});
}.bind(this, continuation, token));
}.bind(this, continuation, token, xhr));
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send();
}.bind(this, continuation)).catch(function (continuation, err) {
this.logger.error(err);
if (loginOpts.interactive && !loginOpts.rememberLogin) {
// Old login logic that gets accessToken and skips refresh tokens
this.getAccessTokenWithOAuth_(continuation);
return;
}
if (!this.storage) {
this.storage = freedom['core.storage']();
}
var getCredentialsPromise = loginOpts.interactive ?
this.getCredentialsInteractive_() : this.getCredentialsFromStorage_();
getCredentialsPromise.then(function(data) {
var refreshToken = data.refreshToken;
var accessToken = data.accessToken;
var email = data.email;
if (this.loginOpts.rememberLogin) {
this.saveLastRefreshTokenAndEmail_(refreshToken, email);
this.saveRefreshTokenForEmail_(refreshToken, email);
}
var credentials = {
userId: email, jid: email, oauth2_token: accessToken,
oauth2_auth: 'http://www.google.com/talk/protocol/auth',
host: 'talk.google.com'
};
this.onCredentials(continuation, {cmd: 'auth', message: credentials});
}.bind(this)).catch(function(e) {
this.logger.error('Error getting credentials: ', e);
continuation(undefined, {
errcode: 'LOGIN_OAUTHERROR',
message: err.message
//message: this.ERRCODE.LOGIN_OAUTHERROR
errcode: 'LOGIN_OAUTHERROR', message: 'Error getting refreshToken: ' + e
});
}.bind(this, continuation));
}.bind(this));
return;

@@ -78,1 +80,256 @@ }

};
// Returns Promise<{refreshToken, accessToken, email}>
XMPPSocialProvider.prototype.getCredentialsFromStorage_ = function() {
return new Promise(function(fulfill, reject) {
this.loadLastRefreshTokenAndEmail_().then(function(data) {
if (!data) {
reject(new Error('Could not load last refresh token and email'));
return;
}
var email = data.email;
var refreshToken = data.refreshToken;
this.getAccessTokenFromRefreshToken_(refreshToken).then(
function(accessToken) {
fulfill({
refreshToken: refreshToken,
accessToken: accessToken,
email: email});
}.bind(this)); // end of getAccessTokenFromRefreshToken_
}.bind(this)); // end of loadLastRefreshTokenAndEmail_
}.bind(this)); // end of return new Promise
};
/**
Returns Promise<{refreshToken, accessToken, email}>
Open Google account chooser to let the user pick an account, then request a
refresh token. Possible outcomes:
1. Google gives us a refresh token after closing the oauth window, all good
2. Google does not give us a refresh token after closing the oauth window,
in this case we should use the access token to get the user's email,
and see if we already have a refresh token stored for that email address.
2a. If we have a refresh token for that email address, return it.
2b. If we don't have a refresh token for that email address, we will have to
launch another oauth window which with "approval_prompt=force", so that
the user grants us "offline access" again and we can get a refresh token
*/
XMPPSocialProvider.prototype.getCredentialsInteractive_ = function() {
// First try to get a refresh token via the account chooser
return new Promise(function(fulfill, reject) {
// Check if there is any refresh token stored. If not, always force the
// approval prompt. This is a small optimization to ensure that we always
// get a refresh token on the 1st login attempt without needing to display
// 2 oauth views.
this.loadLastRefreshTokenAndEmail_().then(function(data) {
var isFirstLoginAttempt = !(data && data.refreshToken);
this.tryToGetRefreshToken_(isFirstLoginAttempt, null).then(function(responseObj) {
// tryToGetRefreshToken_ should always give us an access_token, even
// if no refresh_token is given.
var accessToken = responseObj.access_token;
if (!accessToken) {
reject(new Error('Could not find access_token'));
}
// Get the user's email, needed loading/saving refresh tokens to/from
// storage.
this.getEmail_(accessToken).then(function(email) {
if (responseObj.refresh_token) {
// refresh_token was given on first attempt, all done.
fulfill({
refreshToken: responseObj.refresh_token,
accessToken: accessToken,
email: email});
return;
}
// If no refresh_token is returned, it may mean that the user has already
// granted this app a refresh token. We should first check to see if we
// already have a refresh token stored for this user, and if not we should
// prompt them again with approval_prompt=force to ensure we get a refresh
// token.
// Note loadRefreshTokenForEmail_ will fulfill with null if there is
// no refresh token saved.
this.loadRefreshTokenForEmail_(email).then(function(refreshToken) {
if (refreshToken) {
// A refresh token had already been saved for this email, done.
fulfill({
refreshToken: refreshToken,
accessToken: accessToken,
email: email});
return;
}
// No refresh token was returned to us, or has been stored for this
// email address (this would happen if the user already granted us
// a token but re-installed the chrome app). Try again forcing
// the approval prompt for this email address.
this.tryToGetRefreshToken_(true, email).then(function(responseObj) {
if (responseObj.refresh_token) {
fulfill({
refreshToken: responseObj.refresh_token,
accessToken: accessToken,
email: email});
} else {
reject(new Error('responseObj does not contain refresh_token'));
}
}.bind(this)).catch(function(error) {
reject(new Error('Failed to get refresh_token with forcePrompt'));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
};
XMPPSocialProvider.prototype.saveRefreshTokenForEmail_ =
function(refreshToken, email) {
this.storage.set('Google-Refresh-Token:' + email, refreshToken);
};
XMPPSocialProvider.prototype.loadRefreshTokenForEmail_ = function(email) {
return this.storage.get('Google-Refresh-Token:' + email);
};
XMPPSocialProvider.prototype.saveLastRefreshTokenAndEmail_ =
function(refreshToken, email) {
this.storage.set('Google-Refresh-Token-Last',
JSON.stringify({refreshToken: refreshToken, email: email}));
};
XMPPSocialProvider.prototype.loadLastRefreshTokenAndEmail_ = function() {
return new Promise(function(fulfill, reject) {
this.storage.get('Google-Refresh-Token-Last').then(function(data) {
fulfill(JSON.parse(data));
}.bind(this));
}.bind(this));
};
XMPPSocialProvider.prototype.getEmail_ = function(accessToken) {
return new Promise(function(fulfill, reject) {
var xhr = freedom["core.xhr"]();
xhr.open('GET', 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json', true);
xhr.on("onload", function() {
xhr.getResponseText().then(function(responseText) {
fulfill(JSON.parse(responseText).email);
}.bind(this));
}.bind(this));
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.send();
}.bind(this));
};
XMPPSocialProvider.prototype.getCode_ = function(
oauth, stateObj, forcePrompt, authUserEmail) {
var getCodeUrl = 'https://accounts.google.com/o/oauth2/auth?' +
'response_type=code' +
'&access_type=offline' +
'&client_id=' + this.oAuthClientId +
'&scope=' + this.oAuthScope +
(forcePrompt ? '&approval_prompt=force' : '') +
'&redirect_uri=' + encodeURIComponent(stateObj.redirect) +
'&state=' + encodeURIComponent(stateObj.state);
var url;
if (authUserEmail) {
// Skip account chooser and set the authuser param
url = getCodeUrl + '&authuser=' + authUserEmail;
} else {
// Got to account chooser.
url = 'https://accounts.google.com/accountchooser?continue=' +
encodeURIComponent(getCodeUrl);
}
return oauth.launchAuthFlow(url, stateObj).then(function(responseUrl) {
return responseUrl.match(/code=([^&]+)/)[1];
}.bind(this));
};
// Returns a Promise which fulfills with the object Google gives us upon
// requesting a refresh token. This object will always have an access_token,
// but may not have a refresh_token if forcePrompt==false.
XMPPSocialProvider.prototype.tryToGetRefreshToken_ = function(
forcePrompt, authUserEmail) {
return new Promise(function(fulfill, reject) {
var oauth = freedom["core.oauth"]();
return oauth.initiateOAuth(this.oAuthRedirectUris).then(function(stateObj) {
this.getCode_(
oauth, stateObj, forcePrompt, authUserEmail).then(function(code) {
var data = 'code=' + code +
'&client_id=' + this.oAuthClientId +
'&client_secret=' + this.clientSecret +
"&redirect_uri=" + encodeURIComponent(stateObj.redirect) +
'&grant_type=authorization_code';
var xhr = freedom["core.xhr"]();
xhr.open('POST', 'https://www.googleapis.com/oauth2/v3/token', true);
xhr.setRequestHeader(
'content-type', 'application/x-www-form-urlencoded');
xhr.on('onload', function() {
xhr.getResponseText().then(function(responseText) {
fulfill(JSON.parse(responseText));
});
});
xhr.send({string: data});
}.bind(this));
}.bind(this));
}.bind(this));
};
XMPPSocialProvider.prototype.getAccessTokenWithOAuth_ = function(continuation) {
this.oauth = freedom["core.oauth"]();
this.oauth.initiateOAuth(this.oAuthRedirectUris).then(function(stateObj) {
var oauthUrl = "https://accounts.google.com/o/oauth2/auth?" +
"client_id=" + this.oAuthClientId +
"&scope=" + this.oAuthScope +
"&redirect_uri=" + encodeURIComponent(stateObj.redirect) +
"&state=" + encodeURIComponent(stateObj.state) +
"&response_type=token";
var url = 'https://accounts.google.com/accountchooser?continue=' +
encodeURIComponent(oauthUrl);
return this.oauth.launchAuthFlow(url, stateObj);
}.bind(this)).then(function(continuation, responseUrl) {
var token = responseUrl.match(/access_token=([^&]+)/)[1];
var xhr = freedom["core.xhr"]();
xhr.open('GET', 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json', true);
xhr.on("onload", function(continuation, token, xhr) {
xhr.getResponseText().then(function(continuation, token, responseText) {
var response = JSON.parse(responseText);
var credentials = {
userId: response.email,
jid: response.email,
oauth2_token: token,
oauth2_auth: 'http://www.google.com/talk/protocol/auth',
host: 'talk.google.com'
};
this.onCredentials(continuation, {cmd: 'auth', message: credentials});
}.bind(this, continuation, token));
}.bind(this, continuation, token, xhr));
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send();
}.bind(this, continuation)).catch(function (continuation, err) {
this.logger.error('Error in getAccessTokenWithOAuth_', err);
continuation(undefined, {
errcode: 'LOGIN_OAUTHERROR',
message: err.message
});
}.bind(this, continuation));
};
XMPPSocialProvider.prototype.getAccessTokenFromRefreshToken_ =
function(refreshToken) {
return new Promise(function(fulfill, resolve) {
var data = 'refresh_token=' + refreshToken +
'&client_id=' + this.oAuthClientId +
'&client_secret=' + this.clientSecret +
'&grant_type=refresh_token';
var xhr = freedom["core.xhr"]();
xhr.open('POST', 'https://www.googleapis.com/oauth2/v3/token', true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
xhr.on('onload', function() {
xhr.getResponseText().then(function(responseText) {
fulfill(JSON.parse(responseText).access_token);
});
});
xhr.send({string: data});
}.bind(this));
};

@@ -125,2 +125,10 @@ /*jslint white:true,sloppy:true */

XMPPSocialProvider.prototype.connect = function(continuation) {
if (this.client) {
// Store our new credentials since logging out the old client
// will clear this.credentials.
var newCredentials = this.credentials;
this.logout();
this.credentials = newCredentials;
}
var key, jid, connectOpts = {

@@ -170,4 +178,3 @@ xmlns: 'jabber:client',

if (this.client) {
this.client.end();
delete this.client;
this.logout();
}

@@ -212,3 +219,3 @@ }.bind(this));

XMPPSocialProvider.prototype.clearCachedCredentials = function(continuation) {
delete this.credentials;
this.credentials = null;
continuation();

@@ -584,3 +591,2 @@ };

this.status = 'offline';
this.credentials = null;
this.lastMessageTimestampMs_ = null;

@@ -596,2 +602,10 @@ if (this.pollForDisconnectInterval_) {

this.client.end();
// end() still relies on the client's event listeners
// so they can only be removed after calling end().
this.client.removeAllListeners('online');
this.client.removeAllListeners('error');
this.client.removeAllListeners('offline');
this.client.removeAllListeners('close');
this.client.removeAllListeners('end');
this.client.removeAllListeners('stanza');
this.client = null;

@@ -598,0 +612,0 @@ }

33

Gruntfile.js

@@ -61,12 +61,2 @@ /*jshint node:true*/

},
demo_chrome_facebook: {
src: [
'dist/*',
'src/demo_common/*',
'node_modules/freedom-for-chrome/freedom-for-chrome.js',
'src/demo_chrome_facebook/**/*',
],
dest: 'build/demo_chrome_facebook/',
flatten: true, filter: 'isFile', expand: true
},
demo_firefox_google: {

@@ -88,18 +78,2 @@ src: [ '**/*' ],

},
demo_firefox_facebook: {
src: [ '**/*' ],
dest: 'build/demo_firefox_facebook/',
cwd: 'src/demo_firefox_facebook/',
filter: 'isFile', expand: true,
},
demo_firefox_facebook_data: {
src: [
'dist/*',
'src/demo_common/**/*',
'node_modules/freedom-for-firefox/freedom-for-firefox.jsm',
'src/demo_chrome_facebook/demo.json',
],
dest: 'build/demo_firefox_facebook/data/',
flatten: true, filter: 'isFile', expand: true
},
jasmine: {

@@ -196,3 +170,3 @@ src: [freedomPrefix + '/freedom.js'],

'jshint',
'browserify',
//'browserify',
'copy:dist'

@@ -206,7 +180,4 @@ ]);

'copy:demo_chrome_google',
'copy:demo_chrome_facebook',
'copy:demo_firefox_google',
'copy:demo_firefox_google_data',
'copy:demo_firefox_facebook',
'copy:demo_firefox_facebook_data',
'copy:demo_firefox_google_data'
]);

@@ -213,0 +184,0 @@

{
"name": "freedom-social-xmpp",
"description": "XMPP Social provider for freedomjs",
"version": "0.3.15",
"version": "0.4.6",
"homepage": "http://freedomjs.org",

@@ -6,0 +6,0 @@ "bugs": {

@@ -12,2 +12,5 @@ describe("Tests for message batching in Social provider", function() {

},
removeAllListeners: function(eventName) {
delete xmppSocialProvider.client.events[eventName];
},
end: function() {}

@@ -57,6 +60,4 @@ };

xmppSocialProvider = new XMPPSocialProvider(dispatchEvent);
xmppSocialProvider.client = xmppClient;
xmppSocialProvider.id = 'myId';
xmppSocialProvider.loginOpts = {};
spyOn(xmppSocialProvider.client, 'send');

@@ -71,2 +72,3 @@ jasmine.clock().install();

it("add first message to batch and save time of message", function() {
xmppSocialProvider.client = xmppClient;
dateSpy = spyOn(Date, "now").and.returnValue(500);

@@ -79,2 +81,3 @@ xmppSocialProvider.sendMessage('Bob', 'Hi', function() {});

it("set callback after first message is added to batch", function() {
xmppSocialProvider.client = xmppClient;
expect(xmppSocialProvider.sendMessagesTimeout).toBeNull();

@@ -86,2 +89,4 @@ xmppSocialProvider.sendMessage('Bob', 'Hi', function() {});

it("send message after 100ms", function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider.client, 'send');
xmppSocialProvider.sendMessage('Bob', 'Hi', function() {});

@@ -95,2 +100,4 @@ expect(xmppSocialProvider.client.send).not.toHaveBeenCalled();

it("calls callback after send", function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider.client, 'send');
var spy = jasmine.createSpy('callback');

@@ -106,2 +113,4 @@ xmppSocialProvider.sendMessage('Bob', 'Hi', spy);

it("timeout resets to 100ms after each message", function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider.client, 'send');
xmppSocialProvider.sendMessage('Bob', 'Hi', function() {});

@@ -126,2 +135,4 @@ expect(xmppSocialProvider.client.send).not.toHaveBeenCalled();

it("do not reset timeout if oldest message is from >=2s ago", function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider.client, 'send');
dateSpy = spyOn(Date, "now").and.returnValue(500);

@@ -153,2 +164,4 @@ xmppSocialProvider.sendMessage('Bob', 'Hi', function() {});

it("sends message to correct destinations", function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider.client, 'send');
xmppSocialProvider.sendMessage('Bob', 'Hi', function() {});

@@ -190,3 +203,3 @@ xmppSocialProvider.sendMessage('Alice', 'Hi', function() {});

jasmine.clock().tick(xmppSocialProvider.MAX_MS_PING_REPSONSE_ + 10);
expect(xmppSocialProvider.logout).toHaveBeenCalled();
expect(xmppSocialProvider.logout.calls.count()).toEqual(1);
});

@@ -236,28 +249,31 @@

it('detects sleep and pings immediately', function() {
var nowMs = 0;
dateSpy = spyOn(Date, "now").and.callFake(function() { return nowMs; });
spyOn(window.XMPP, 'Client').and.returnValue(xmppClient);
var setIntervalCallbacks = [];
spyOn(window, 'setInterval').and.callFake(function(callback, intervalMs) {
setIntervalCallbacks.push(callback);
});
spyOn(xmppSocialProvider, 'ping_');
// TODO: re-enable this test when we figure out
// https://github.com/freedomjs/freedom-social-xmpp/issues/118
// it('detects sleep and pings immediately', function() {
// var nowMs = 0;
// dateSpy = spyOn(Date, "now").and.callFake(function() { return nowMs; });
// spyOn(window.XMPP, 'Client').and.returnValue(xmppClient);
// var setIntervalCallbacks = [];
// spyOn(window, 'setInterval').and.callFake(function(callback, intervalMs) {
// setIntervalCallbacks.push(callback);
// });
// spyOn(xmppSocialProvider, 'ping_');
// Connect and emit online event to start polling loop.
xmppSocialProvider.connect(function() {});
xmppSocialProvider.client.events['online']();
// // Connect and emit online event to start polling loop.
// xmppSocialProvider.connect(function() {});
// xmppSocialProvider.client.events['online']();
// Advance the clock by 2010 ms and invoke callbacks.
nowMs = 2010;
jasmine.clock().tick(2010);
setIntervalCallbacks.map(function(callback) { callback(); });
// // Advance the clock by 2010 ms and invoke callbacks.
// nowMs = 2010;
// jasmine.clock().tick(2010);
// setIntervalCallbacks.map(function(callback) { callback(); });
// Expect sleep to have been detected and ping to be invoked.
expect(xmppSocialProvider.ping_).toHaveBeenCalled();
// logout must be called to clearInterval on the polling loop
xmppSocialProvider.logout();
});
// // Expect sleep to have been detected and ping to be invoked.
// expect(xmppSocialProvider.ping_).toHaveBeenCalled();
// // logout must be called to clearInterval on the polling loop
// xmppSocialProvider.logout();
// });
it('parses JSON encoded arrays', function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider, 'dispatchEvent');

@@ -274,2 +290,3 @@ var fromClient = xmppSocialProvider.vCardStore.getClient('fromId');

it('does not parse JSON that is not an array', function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider, 'dispatchEvent');

@@ -285,2 +302,3 @@ var jsonString = '{key: "value"}';

it('does not parse non-JSON messages', function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider, 'dispatchEvent');

@@ -311,3 +329,3 @@ var fromClient = xmppSocialProvider.vCardStore.getClient('fromId');

xmppSocialProvider.client.events['end']();
expect(xmppSocialProvider.logout).toHaveBeenCalled();
expect(xmppSocialProvider.logout.calls.count()).toEqual(1);
});

@@ -329,2 +347,3 @@

function() {
xmppSocialProvider.client = xmppClient;
spyOn(xmppSocialProvider, 'dispatchEvent');

@@ -331,0 +350,0 @@

@@ -12,3 +12,8 @@ /*globals freedom:true,setTimeout,VCardStore,XMPPSocialProvider */

XMPPSocialProvider.prototype.oAuthScope = "email%20profile%20https://www.googleapis.com/auth/googletalk&";
XMPPSocialProvider.prototype.clientSecret = "h_hfPI4jvs9fgOgPweSBKnMu";
// These credentials are for a native app.. but we can't change the redirect URL.
// XMPPSocialProvider.prototype.oAuthClientId = "746567772449-mv4h0e34orsf6t6kkbbht22t9otijip0.apps.googleusercontent.com";
// XMPPSocialProvider.prototype.clientSecret = "M-EGTuFRaWLS5q_hygpJZMBu";
/**

@@ -23,2 +28,5 @@ * Begin the login view, potentially prompting for credentials.

* network - A string used to differentiate this provider in events.
* interactive - Login will attempt to re-use the last remembered
* login credentials if this is set to false.
* rememberLogin: Login credentials will be cached if true.
*/

@@ -31,40 +39,34 @@ XMPPSocialProvider.prototype.login = function(loginOpts, continuation) {

if (!this.credentials) {
this.oauth = freedom["core.oauth"]();
this.oauth.initiateOAuth(this.oAuthRedirectUris).then(function(stateObj) {
var oauthUrl = "https://accounts.google.com/o/oauth2/auth?" +
"client_id=" + this.oAuthClientId +
"&scope=" + this.oAuthScope +
"&redirect_uri=" + encodeURIComponent(stateObj.redirect) +
"&state=" + encodeURIComponent(stateObj.state) +
"&response_type=token";
var url = 'https://accounts.google.com/accountchooser?continue=' +
encodeURIComponent(oauthUrl);
return this.oauth.launchAuthFlow(url, stateObj);
}.bind(this)).then(function(continuation, responseUrl) {
var token = responseUrl.match(/access_token=([^&]+)/)[1];
var xhr = freedom["core.xhr"]();
xhr.open('GET', 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json', true);
xhr.on("onload", function(continuation, token, xhr) {
xhr.getResponseText().then(function(continuation, token, responseText) {
var response = JSON.parse(responseText);
var credentials = {
userId: response.email,
jid: response.email,
oauth2_token: token,
oauth2_auth: 'http://www.google.com/talk/protocol/auth',
host: 'talk.google.com'
};
this.onCredentials(continuation, {cmd: 'auth', message: credentials});
}.bind(this, continuation, token));
}.bind(this, continuation, token, xhr));
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send();
}.bind(this, continuation)).catch(function (continuation, err) {
this.logger.error(err);
if (loginOpts.interactive && !loginOpts.rememberLogin) {
// Old login logic that gets accessToken and skips refresh tokens
this.getAccessTokenWithOAuth_(continuation);
return;
}
if (!this.storage) {
this.storage = freedom['core.storage']();
}
var getCredentialsPromise = loginOpts.interactive ?
this.getCredentialsInteractive_() : this.getCredentialsFromStorage_();
getCredentialsPromise.then(function(data) {
var refreshToken = data.refreshToken;
var accessToken = data.accessToken;
var email = data.email;
if (this.loginOpts.rememberLogin) {
this.saveLastRefreshTokenAndEmail_(refreshToken, email);
this.saveRefreshTokenForEmail_(refreshToken, email);
}
var credentials = {
userId: email, jid: email, oauth2_token: accessToken,
oauth2_auth: 'http://www.google.com/talk/protocol/auth',
host: 'talk.google.com'
};
this.onCredentials(continuation, {cmd: 'auth', message: credentials});
}.bind(this)).catch(function(e) {
this.logger.error('Error getting credentials: ', e);
continuation(undefined, {
errcode: 'LOGIN_OAUTHERROR',
message: err.message
//message: this.ERRCODE.LOGIN_OAUTHERROR
errcode: 'LOGIN_OAUTHERROR', message: 'Error getting refreshToken: ' + e
});
}.bind(this, continuation));
}.bind(this));
return;

@@ -78,1 +80,256 @@ }

};
// Returns Promise<{refreshToken, accessToken, email}>
XMPPSocialProvider.prototype.getCredentialsFromStorage_ = function() {
return new Promise(function(fulfill, reject) {
this.loadLastRefreshTokenAndEmail_().then(function(data) {
if (!data) {
reject(new Error('Could not load last refresh token and email'));
return;
}
var email = data.email;
var refreshToken = data.refreshToken;
this.getAccessTokenFromRefreshToken_(refreshToken).then(
function(accessToken) {
fulfill({
refreshToken: refreshToken,
accessToken: accessToken,
email: email});
}.bind(this)); // end of getAccessTokenFromRefreshToken_
}.bind(this)); // end of loadLastRefreshTokenAndEmail_
}.bind(this)); // end of return new Promise
};
/**
Returns Promise<{refreshToken, accessToken, email}>
Open Google account chooser to let the user pick an account, then request a
refresh token. Possible outcomes:
1. Google gives us a refresh token after closing the oauth window, all good
2. Google does not give us a refresh token after closing the oauth window,
in this case we should use the access token to get the user's email,
and see if we already have a refresh token stored for that email address.
2a. If we have a refresh token for that email address, return it.
2b. If we don't have a refresh token for that email address, we will have to
launch another oauth window which with "approval_prompt=force", so that
the user grants us "offline access" again and we can get a refresh token
*/
XMPPSocialProvider.prototype.getCredentialsInteractive_ = function() {
// First try to get a refresh token via the account chooser
return new Promise(function(fulfill, reject) {
// Check if there is any refresh token stored. If not, always force the
// approval prompt. This is a small optimization to ensure that we always
// get a refresh token on the 1st login attempt without needing to display
// 2 oauth views.
this.loadLastRefreshTokenAndEmail_().then(function(data) {
var isFirstLoginAttempt = !(data && data.refreshToken);
this.tryToGetRefreshToken_(isFirstLoginAttempt, null).then(function(responseObj) {
// tryToGetRefreshToken_ should always give us an access_token, even
// if no refresh_token is given.
var accessToken = responseObj.access_token;
if (!accessToken) {
reject(new Error('Could not find access_token'));
}
// Get the user's email, needed loading/saving refresh tokens to/from
// storage.
this.getEmail_(accessToken).then(function(email) {
if (responseObj.refresh_token) {
// refresh_token was given on first attempt, all done.
fulfill({
refreshToken: responseObj.refresh_token,
accessToken: accessToken,
email: email});
return;
}
// If no refresh_token is returned, it may mean that the user has already
// granted this app a refresh token. We should first check to see if we
// already have a refresh token stored for this user, and if not we should
// prompt them again with approval_prompt=force to ensure we get a refresh
// token.
// Note loadRefreshTokenForEmail_ will fulfill with null if there is
// no refresh token saved.
this.loadRefreshTokenForEmail_(email).then(function(refreshToken) {
if (refreshToken) {
// A refresh token had already been saved for this email, done.
fulfill({
refreshToken: refreshToken,
accessToken: accessToken,
email: email});
return;
}
// No refresh token was returned to us, or has been stored for this
// email address (this would happen if the user already granted us
// a token but re-installed the chrome app). Try again forcing
// the approval prompt for this email address.
this.tryToGetRefreshToken_(true, email).then(function(responseObj) {
if (responseObj.refresh_token) {
fulfill({
refreshToken: responseObj.refresh_token,
accessToken: accessToken,
email: email});
} else {
reject(new Error('responseObj does not contain refresh_token'));
}
}.bind(this)).catch(function(error) {
reject(new Error('Failed to get refresh_token with forcePrompt'));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));
};
XMPPSocialProvider.prototype.saveRefreshTokenForEmail_ =
function(refreshToken, email) {
this.storage.set('Google-Refresh-Token:' + email, refreshToken);
};
XMPPSocialProvider.prototype.loadRefreshTokenForEmail_ = function(email) {
return this.storage.get('Google-Refresh-Token:' + email);
};
XMPPSocialProvider.prototype.saveLastRefreshTokenAndEmail_ =
function(refreshToken, email) {
this.storage.set('Google-Refresh-Token-Last',
JSON.stringify({refreshToken: refreshToken, email: email}));
};
XMPPSocialProvider.prototype.loadLastRefreshTokenAndEmail_ = function() {
return new Promise(function(fulfill, reject) {
this.storage.get('Google-Refresh-Token-Last').then(function(data) {
fulfill(JSON.parse(data));
}.bind(this));
}.bind(this));
};
XMPPSocialProvider.prototype.getEmail_ = function(accessToken) {
return new Promise(function(fulfill, reject) {
var xhr = freedom["core.xhr"]();
xhr.open('GET', 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json', true);
xhr.on("onload", function() {
xhr.getResponseText().then(function(responseText) {
fulfill(JSON.parse(responseText).email);
}.bind(this));
}.bind(this));
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.send();
}.bind(this));
};
XMPPSocialProvider.prototype.getCode_ = function(
oauth, stateObj, forcePrompt, authUserEmail) {
var getCodeUrl = 'https://accounts.google.com/o/oauth2/auth?' +
'response_type=code' +
'&access_type=offline' +
'&client_id=' + this.oAuthClientId +
'&scope=' + this.oAuthScope +
(forcePrompt ? '&approval_prompt=force' : '') +
'&redirect_uri=' + encodeURIComponent(stateObj.redirect) +
'&state=' + encodeURIComponent(stateObj.state);
var url;
if (authUserEmail) {
// Skip account chooser and set the authuser param
url = getCodeUrl + '&authuser=' + authUserEmail;
} else {
// Got to account chooser.
url = 'https://accounts.google.com/accountchooser?continue=' +
encodeURIComponent(getCodeUrl);
}
return oauth.launchAuthFlow(url, stateObj).then(function(responseUrl) {
return responseUrl.match(/code=([^&]+)/)[1];
}.bind(this));
};
// Returns a Promise which fulfills with the object Google gives us upon
// requesting a refresh token. This object will always have an access_token,
// but may not have a refresh_token if forcePrompt==false.
XMPPSocialProvider.prototype.tryToGetRefreshToken_ = function(
forcePrompt, authUserEmail) {
return new Promise(function(fulfill, reject) {
var oauth = freedom["core.oauth"]();
return oauth.initiateOAuth(this.oAuthRedirectUris).then(function(stateObj) {
this.getCode_(
oauth, stateObj, forcePrompt, authUserEmail).then(function(code) {
var data = 'code=' + code +
'&client_id=' + this.oAuthClientId +
'&client_secret=' + this.clientSecret +
"&redirect_uri=" + encodeURIComponent(stateObj.redirect) +
'&grant_type=authorization_code';
var xhr = freedom["core.xhr"]();
xhr.open('POST', 'https://www.googleapis.com/oauth2/v3/token', true);
xhr.setRequestHeader(
'content-type', 'application/x-www-form-urlencoded');
xhr.on('onload', function() {
xhr.getResponseText().then(function(responseText) {
fulfill(JSON.parse(responseText));
});
});
xhr.send({string: data});
}.bind(this));
}.bind(this));
}.bind(this));
};
XMPPSocialProvider.prototype.getAccessTokenWithOAuth_ = function(continuation) {
this.oauth = freedom["core.oauth"]();
this.oauth.initiateOAuth(this.oAuthRedirectUris).then(function(stateObj) {
var oauthUrl = "https://accounts.google.com/o/oauth2/auth?" +
"client_id=" + this.oAuthClientId +
"&scope=" + this.oAuthScope +
"&redirect_uri=" + encodeURIComponent(stateObj.redirect) +
"&state=" + encodeURIComponent(stateObj.state) +
"&response_type=token";
var url = 'https://accounts.google.com/accountchooser?continue=' +
encodeURIComponent(oauthUrl);
return this.oauth.launchAuthFlow(url, stateObj);
}.bind(this)).then(function(continuation, responseUrl) {
var token = responseUrl.match(/access_token=([^&]+)/)[1];
var xhr = freedom["core.xhr"]();
xhr.open('GET', 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json', true);
xhr.on("onload", function(continuation, token, xhr) {
xhr.getResponseText().then(function(continuation, token, responseText) {
var response = JSON.parse(responseText);
var credentials = {
userId: response.email,
jid: response.email,
oauth2_token: token,
oauth2_auth: 'http://www.google.com/talk/protocol/auth',
host: 'talk.google.com'
};
this.onCredentials(continuation, {cmd: 'auth', message: credentials});
}.bind(this, continuation, token));
}.bind(this, continuation, token, xhr));
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send();
}.bind(this, continuation)).catch(function (continuation, err) {
this.logger.error('Error in getAccessTokenWithOAuth_', err);
continuation(undefined, {
errcode: 'LOGIN_OAUTHERROR',
message: err.message
});
}.bind(this, continuation));
};
XMPPSocialProvider.prototype.getAccessTokenFromRefreshToken_ =
function(refreshToken) {
return new Promise(function(fulfill, resolve) {
var data = 'refresh_token=' + refreshToken +
'&client_id=' + this.oAuthClientId +
'&client_secret=' + this.clientSecret +
'&grant_type=refresh_token';
var xhr = freedom["core.xhr"]();
xhr.open('POST', 'https://www.googleapis.com/oauth2/v3/token', true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
xhr.on('onload', function() {
xhr.getResponseText().then(function(responseText) {
fulfill(JSON.parse(responseText).access_token);
});
});
xhr.send({string: data});
}.bind(this));
};

@@ -125,2 +125,10 @@ /*jslint white:true,sloppy:true */

XMPPSocialProvider.prototype.connect = function(continuation) {
if (this.client) {
// Store our new credentials since logging out the old client
// will clear this.credentials.
var newCredentials = this.credentials;
this.logout();
this.credentials = newCredentials;
}
var key, jid, connectOpts = {

@@ -170,4 +178,3 @@ xmlns: 'jabber:client',

if (this.client) {
this.client.end();
delete this.client;
this.logout();
}

@@ -212,3 +219,3 @@ }.bind(this));

XMPPSocialProvider.prototype.clearCachedCredentials = function(continuation) {
delete this.credentials;
this.credentials = null;
continuation();

@@ -584,3 +591,2 @@ };

this.status = 'offline';
this.credentials = null;
this.lastMessageTimestampMs_ = null;

@@ -596,2 +602,10 @@ if (this.pollForDisconnectInterval_) {

this.client.end();
// end() still relies on the client's event listeners
// so they can only be removed after calling end().
this.client.removeAllListeners('online');
this.client.removeAllListeners('error');
this.client.removeAllListeners('offline');
this.client.removeAllListeners('close');
this.client.removeAllListeners('end');
this.client.removeAllListeners('stanza');
this.client = null;

@@ -598,0 +612,0 @@ }

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc