I have implemented google login in parse. Here is my code:
var querystring = require('querystring');
var _ = require('underscore');
var Buffer = require('buffer').Buffer;
var googleValidateEndpoint = 'https://www.googleapis.com/oauth2/v1/userinfo';
var TokenStorage = Parse.Object.extend("TokenStorage");
var restrictedAcl = new Parse.ACL();
restrictedAcl.setPublicReadAccess(false);
restrictedAcl.setPublicWriteAccess(false);
Parse.Cloud.define('accessGoogleUser', function(req, res) {
var data = req.params;
var token = data.code;
/**
* Validate that code and state have been passed in as query parameters.
* Render an error page if this is invalid.
*/
if (!(data && data.code)) {
res.error('Invalid auth response received.');
return;
}
Parse.Cloud.useMasterKey();
Parse.Promise.as().then(function() {
// Validate & Exchange the code parameter for an access token from Google
return getGoogleAccessToken(data.code);
}).then(function(httpResponse) {
var userData = httpResponse.data;
if (userData && userData.id) {
return upsertGoogleUser(token, userData, data.email);
} else {
return Parse.Promise.error("Unable to parse Google data");
}
}).then(function(user) {
/**
* Send back the session token in the response to be used with 'become/becomeInBackground' functions
*/
res.success(user.getSessionToken());
}, function(error) {
/**
* If the error is an object error (e.g. from a Parse function) convert it
* to a string for display to the user.
*/
if (error && error.code && error.error) {
error = error.code + ' ' + error.error;
}
res.error(JSON.stringify(error));
});
});
var getGoogleAccessToken = function(code) {
var body = querystring.stringify({
access_token: code
});
return Parse.Cloud.httpRequest({
url: googleValidateEndpoint + '?access_token=' + code
});
}
var upsertGoogleUser = function(accessToken, googleData, emailId) {
var query = new Parse.Query(TokenStorage);
query.equalTo('accountId', googleData.id);
//query.ascending('createdAt');
// Check if this googleId has previously logged in, using the master key
return query.first({ useMasterKey: true }).then(function(tokenData) {
// If not, create a new user.
if (!tokenData) {
return newGoogleUser(accessToken, googleData, emailId);
}
// If found, fetch the user.
var user = tokenData.get('user');
return user.fetch({ useMasterKey: true }).then(function(user) {
// Update the access_token if it is different.
if (accessToken !== tokenData.get('accessToken')) {
tokenData.set('accessToken', accessToken);
}
/**
* This save will not use an API request if the token was not changed.
* e.g. when a new user is created and upsert is called again.
*/
return tokenData.save(null, { useMasterKey: true });
}).then(function(obj) {
// Reset password
password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
});
password = password.toString('base64')
user.setPassword(password);
return user.save();
}).then(function(user) {
// ReLogin
// This line is what I am talking about
return Parse.User.logIn(user.get('username'), password);
}).then(function(obj) {
// Return the user object.
return Parse.Promise.as(obj);
});
});
}
var newGoogleUser = function(accessToken, googleData, email) {
var user = new Parse.User();
// Generate a random username and password.
var username = new Buffer(24);
var password = new Buffer(24);
_.times(24, function(i) {
username.set(i, _.random(0, 255));
password.set(i, _.random(0, 255));
});
var name = googleData.name;
// name = name.split(" ");
// var fullname = name;
// if(name.length > 1)
// var lastName = name[name.length-1];
user.set("username", username.toString('base64'));
user.set("password", password.toString('base64'));
user.set("email", email);
user.set("fullName", name);
// user.set("last_name", lastName);
user.set("accountType", 'google');
// Sign up the new User
return user.signUp().then(function(user) {
// create a new TokenStorage object to store the user+Google association.
var ts = new TokenStorage();
ts.set('user', user);
ts.set('accountId', googleData.id);
ts.set('accessToken', accessToken);
ts.setACL(restrictedAcl);
// Use the master key because TokenStorage objects should be protected.
return ts.save(null, { useMasterKey: true });
}).then(function(tokenStorage) {
return upsertGoogleUser(accessToken, googleData);
});
}
It works perfectly fine. Now the problem I am facing is that I want to link google account with an existing parse account created using email or username & password
. The problem in doing so is that to login/signup
using google I have to reset the password of the user
to login so as to get the session token. See this line in the code -> [This line is what I am talking about]. So if I do so an existing user
who had earlier used username/email & password to login won't be able to login again using email since I have reset his/her password. I have seen this and all the other links related to this but none of which solves this problem.
Can somebody here guide me in the right direction?
Log added as response to one of the comments:
{"accountType":"google","createdAt":"2016-01-07T17:30:57.429Z","email":"skdkaney@gmail.com","fullName":"ashdakhs basdkbney","updatedAt":"2016-01-07T17:30:57.429Z","username":"owt3h0ZZEZQ1K7if55W2oo3TBLfeWM6m","objectId":"lSlsdsZ9"}
Added upsert
function as per comment request:
var upsertGoogleUser = function(accessToken, googleData, emailId) {
var query = new Parse.Query(TokenStorage);
query.equalTo('accountId', googleData.id);
//query.ascending('createdAt');
// Check if this googleId has previously logged in, using the master key
return query.first({ useMasterKey: true }).then(function(tokenData) {
// If not, create a new user.
if (!tokenData) {
return newGoogleUser(accessToken, googleData, emailId);
}
// If found, fetch the user.
var userw = tokenData.get('user');
var users_id = userw.id;
var query2 = new Parse.Query(Parse.User);
query2.equalTo('objectId',users_id);
// The new query added
return query2.first({ useMasterKey: true }).then(function(user) {
// Update the access_token if it is different.
// if (accessToken !== tokenData.get('accessToken')) {
// tokenData.set('accessToken', accessToken);
// }
console.log(user);
console.log("******");
/**
* This save will not use an API request if the token was not changed.
* e.g. when a new user is created and upsert is called again.
*/
// return tokenData.save(null, { useMasterKey: true });
}).then(function(obj) {
console.log(obj);
// console.log(user);
var result = user ;
// Return the user object.
return Parse.Promise.as(result); // this is the user object acquired above
});
After a discussion with OP, there are possible solutions to this matter but each of them have pros and cons.
Since the introduction of Revocable Session, getSessionToken
will always return undefined
even with master key. To turn it off, go to App Settings >> Users >> Turn off Require revocable sessions.
Then, in upsertGoogleUser
method, you just need to return the user
object from tokenData.get('user')
. It is enough to call user.getSessionToken()
in your main cloud function. The final method should look like:
var upsertGoogleUser = function(accessToken, googleData, emailId) {
Parse.Cloud.useMasterKey();
var query = new Parse.Query(TokenStorage);
query.equalTo('accountId', googleData.id);
//query.ascending('createdAt');
// Check if this googleId has previously logged in, using the master key
return query.first().then(function(tokenData) {
// If not, create a new user.
if (!tokenData) {
return newGoogleUser(accessToken, googleData, emailId);
}
// If found, fetch the user.
var userw = tokenData.get('user');
var users_id = userw.id;
var query2 = new Parse.Query(Parse.User);
query2.equalTo('objectId',users_id);
return query2.first().then(function(user) {
console.log(user);
console.log(user.getSessionToken());
console.log("******");
return Parse.Promise.as(user);
});
});
};
In order not to change user's password, we can ask user to input his password once we successfully authenticated Google data. We then use the input password to log user in. This is not a good UX, since the purpose of Google Login is to increase usability by letting users not entering password.
This is a possible solution if you want to use "Revocable Session" feature. In the code above, instead of querying on Parse.User
, we can look for any revocable session in Parse.Session
class. Then we can call getSessionToken
on returned object. This is not optimal solution in cases that we need to know which devices the user is logged in.