Verify current user authenticity from Cloud Code - parse-platform

I have a Parse Cloud Code function that returns some user private data given a user id. How can I verify that the given user id is indeed the id of the currently logged in user?

I ended up creating this function and calling it from my other Cloud Code functions.
// Verifies that the given user is an authentic user
// Parameters: user
// Return: Promise
function verifyUserAuthenticity(user)
{
var promise = new Parse.Promise();
var userSessionToken = user.getSessionToken();
var query = new Parse.Query(Parse.Session);
query.equalTo("user", user);
query.equalTo("sessionToken", userSessionToken);
query.find({ useMasterKey: true }).then(
function(results)
{
if(results.length > 0)
{
promise.resolve("User is authentic.");
}
else
{
promise.reject("User is not authentic.");
}
},
function(error)
{
promise.reject("Error verifying user's authenticity: " + error);
}
);
return promise;
}

Related

Parse Cloud - Get user informations by objectId

I'm trying to get user lang from User class in Parse Cloud. lang is one of the columns in User class. I wanna get lang of the user. My entire Cloud Code is as following (it didn't work):
Parse.Cloud.beforeSave('Order', function(request, response) {
var orderState = request.object.get('orderState');
var subtitleMessage = '';
var userLang = '';
var currentUser = request.object.get('user');
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('objectId', currentUser.id);
.find()
.then((result)=>{
userLang = result.get('lang');
})
if (orderState === undefined || ['nonApproved', 'approved', 'delivered', 'canceled'].indexOf(orderState) < 0) {
response.error("OrderState is null or not one the ['nonApproved', 'approved', 'delivered', 'canceled']!");
} else {
var query = new Parse.Query(Parse.Installation);
query.include('user');
query.equalTo('user', request.object.get('user'));
Parse.Push.send(
{
where: query,
data: {
title: "MyTitle",
alert: subtitleMessage
}
},
{
success: function() {
},
error: function(error) {
response.error(error)
},
useMasterKey: true
}
);
response.success();
}
});
The answer from Jake T. has some good points. Based on his answer and your comment on the question, you could try something like this:
Parse.Cloud.beforeSave('Order', function(request, response) {
var currentUser = request.object.get('user');
currentUser.fetch({useMasterKey: true}).then(function(user) {
var userLang = user.get('lang');
// send push notifications based on userLang here
response.success();
}).catch(function(error) {
// handle any errors here
console.error(error);
});
});
Verify you actually have a User object shell from request.object.get("user"); And, if you do, you can just call currentUser.fetch() instead of performing a query, unless there are other objects you may need to include.
Since you used a query, the result is an array, even if there is only a single object returned (or none, it would be simply []). So, you're doing Array.get("lang"), which shouldn't do anything. Try if( results && results.length > 0 ) user = results[0];, then you should be able to access user.get("lang");
You should have ACL / CLP's set up for your User class. Ideally, these should not be accessible by people who are not the user or master. So, if that is set up properly, your solution may be passing {useMasterKey:true} as an option to the query / fetch.

Update user object from Parse cloud code is not saving

Currently I am trying to update the username and password from parse cloud code, but In the parse.com console I am seeing the success messages, but the object is not actually saved in the parse.com database. Here is the contents of cloud/main.js
// code to update username
Parse.Cloud.define("updateUserName", function(request, response){
if(!request.user){
response.error("Must be signed in to update the user");
return;
}
Parse.Cloud.useMasterKey();
var userId = request.params.id;
var userName = request.params.userName;
// var User = Parse.Object.extend("User");
var updateQuery = new Parse.Query(Parse.User);
updateQuery.get(userId,{
success: function(userRecord){
console.log(userRecord.get("id"));
userRecord.set("username", userName);
// userRecord.set("resetToken", "Apple");
userRecord.save(null,{
success: function(successData){
response.success("username updated successfully.");
// userRecord.fetch();
},
error: function(errorData){
console.log("Error while updating the username: ",errorData);
}
});
},
error: function(errorData){
console.log("Error: ",errorData);
}
});
});
Parse.Cloud.define("resetPassword", function(request, response){
var successMsg = "";
if(!request.user){
response.error("Must be signed in to update the user");
return;
}
Parse.Cloud.useMasterKey();
var resetToken = request.params.resetToken;
var password = request.params.password;
// var User = Parse.Object.extend("User");
var updateQuery = new Parse.Query(Parse.User);
// updateQuery.equalTo("resetToken", resetToken);
updateQuery.get(resetToken,{
success: function(userRecord){
// console.log(userRecord.get("id"));
// userRecord.set("password",password)
userRecord.set("password",password);
userRecord.save(null, {
success: function(successData){
successMsg = "Password Changed !";
console.log("Password changed!");
userRecord.set("resetToken", "");
userRecord.save();
},
error: function(errorData){
response.error("Uh oh, something went wrong");
}
})
},
error: function(errorData){
console.log("Error: ",errorData);
}
});
response.success(successMsg);
});
The code actually runs without any error, but it is not updating the values in the database. Here is how I am calling these cloud functions in js/index.js
$(".update-user").click(function(){
Parse.Cloud.run("updateUserName", {id: $(this).data("id"), username: $(".uname").val()},
{
success: function(successData){
console.log("username updated successfully.");
$("#editModal").modal("hide");
$(".edit-modal").hide();
},
error: function(errorData){
}
});
});
The contents that I see in the firefox console username updated successfully.
The contents that I see in parse.com console
I2015-12-11T06:15:13.361Z]v106 Ran cloud function updateUserName for user chfgGhaPEl with:
Input: {"id":"MAvm9FlGgg","username":"testuser12"}
Result: username updated successfully.
But this line of code userRecord.set("resetToken", "Apple"); is updating the resetToken column in the database, but why not it is not letting me update the username/password(or other columns that I didn't try updating) columns ?
I analyse your code. Instead of using get to retrieve correlated user, use first where you specify the user id as query constraint. One example (working) code is below where user information is updated in Parse User table. Hope this helps.
Regards.
Parse.Cloud.define("updateUser", function(request, response)
{
Parse.Cloud.useMasterKey();
var query = new Parse.Query(Parse.User);
var objectId = request.params.objectId;
var username = request.params.username;
var email = request.params.email;
var userType = request.params.userType;
var password = request.params.password;
query.equalTo("objectId", objectId);
query.first({
success: function(object)
{
object.set("username", username);
object.set("email", email);
object.set("userType", userType);
object.set("password", password);
object.save();
response.success("Success");
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
response.error("Error");
}
});
});
What worked for me is
//cloud/main.js
Parse.Cloud.define("updateUserName", function(request, response){
if(!request.user){
response.error("Must be signed in to update the user");
return;
}
if(request.params.IsAdmin == false){
response.error("Only the administrators can edit username.");
return;
}
Parse.Cloud.useMasterKey();
// var userId = request.params.Id; --> I guess he was the main culprit, the params and the actual column value should match. Instead of passing Id from my client code(see below) I just passed objectId and it worked.
var userId = request.params.objectId;
// var userName = request.params.username;
var name = request.params.name;
// var User = Parse.Object.extend("User");
var updateQuery = new Parse.Query(Parse.User);
console.log("id from params: "+userId);
updateQuery.equalTo("objectId", userId);
updateQuery.first({
success: function(userRecord){
// userRecord.set("username", userName);
userRecord.set("name", name);
userRecord.save(null,{
success: function(successData){
response.success("username updated successfully.");
userRecord.fetch();
},
error: function(errorData){
console.log("Error while updating the username: ",errorData);
}
});
},
error: function(errorData){
console.log("Error: ",errorData);
response.error(errorData);
}
});
});
// js/index.js
/* initially I was using Id instead of objectId, the field names are case
sensitive
Parse.Cloud.run("updateUserName", {id: $(this).data("id"), name: $(".name").val(),IsAdmin: Parse.User.current().get("IsAdmin") },*/
Parse.Cloud.run("updateUserName", {objectId: $(this).data("id"), name: $(".name").val(),IsAdmin: Parse.User.current().get("IsAdmin") },
{
success: function(successData){
console.log("username updated successfully.");
$("#editModal").modal("hide");
$(".edit-user-server-error").html("");
$(".edit-modal").hide();
location.reload();
},
error: function(errorData){
console.log(errorData);
$(".edit-user-server-error").html("");
$(".edit-user-server-error").html(errorData.message);
}
});
So I had referred to this post and it gave me an hint, based on that I just did one trial and error thing and got it working.
You can pass any sort of values in the parameters to this Cloud Function, so you might want to specify exactly which properties you wish to update in this manner. Also, don't forget to actually validate that request.user is allowed to perform such an operation.
Parse doesn't support HTTP PUT requests to save the data. Hence we need to use PUSH request containing a method call to save the data.
However it has to have a the line, Parse.Cloud.useMasterKey(); before calling the save method.

How can I assign multiple roles to one user in Parse?

I've written a bit of Cloud Code which executes after every user is saved. Inside, I would like to add the user to two roles, Alpha and Free, but this code only successfully adds new users to the Alpha role; the Free role has no data in the users table. Is there a way in Parse to assign users multiple roles?
Here is my Cloud Code.
Parse.Cloud.afterSave(Parse.User, function(request, response) {
Parse.Cloud.useMasterKey(); // grant administrative access to write to roles
var user = request.object;
query = new Parse.Query(Parse.Role);
query.equalTo("name", "Alpha");
query.first ( {
success: function(object) {
object.relation("users").add(user);
object.save();
response.success("The user has been authorized.");
},
error: function(error) {
response.error("User authorization failed!");
}
});
query = new Parse.Query(Parse.Role);
query.equalTo("name", "Free");
query.first ( {
success: function(object) {
object.relation("users").add(user);
object.save();
response.success("The user has been authorized.");
},
error: function(error) {
response.error("User authorization failed!");
}
});
});
The problem is sequencing. We need all of the queries and saves to complete before response.success() is called. As it is now, the timing of actions in the code is not deterministic. Clean it up by using the promises returned by the parse sdk...
Parse.Cloud.afterSave(Parse.User, function(request, response) {
Parse.Cloud.useMasterKey(); // grant administrative access to write to roles
var user = request.object;
query = new Parse.Query(Parse.Role);
query.equalTo("name", "Alpha");
query.first().then(function(object) {
object.relation("users").add(user);
return object.save();
}).then(function() {
query = new Parse.Query(Parse.Role);
query.equalTo("name", "Free");
return query.first();
}).then(function(object) {
object.relation("users").add(user);
return object.save();
}).then(function() {
response.success("The user has been authorized.");
}, function(error) {
response.error("error: " + error.message);
});
});

Parse Cloud: Query not running in exported function from save() callback

I'm using Parse to represent the state of a beer keg (among other things). I'd like to check the user's notifications, stored in a "Notifications" table, to see if they'd like to receive a notification when the keg is filled.
I have all of the logic for setting the user's notification settings as well as sending notifications in cloud/notifications.js. All of the logic for updating the keg is in cloud/beer.js. I created an exported function called "sendKegRefillNotification" which performs a query.find() on the Notifications table and gets called from beer.js.
The problem is that it doesn't seem to be executing query.find() when I call the function from beer.js, however when I call the same function from a job within notifications.js, it works just fine.
main.js:
require("cloud/beer.js");
require("cloud/notifications.js");
beer.js:
var notify = require("cloud/notifications.js");
var Keg = Parse.Object.extend("Keg");
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
promise.resolve(keg);
notify.sendKegRefillNotification(keg);
},
function(keg, error) {
promise.reject(error);
});
return promise;
}
Parse.Cloud.define("beerFillKeg", function(request, response) {
var beerName = request.params.name;
if (!beerName) {
response.error("No beer was specified.");
return;
}
if (!util.isUserAdmin(request.user)) {
response.error("User does not have permission to update the keg.");
return;
}
fillKeg(beerName).then(function(keg) {
kegResponse(keg).then(function(result) {
response.success(result);
});
},
function(error) {
response.error(error);
});
});
function kegResponse(keg) {
var promise = new Parse.Promise();
var result = {
id: keg.id,
beer: {
name: keg.get("beerName")
},
filled: keg.createdAt,
kickedReports: []
};
var kickedReports = keg.get("kickedReports");
if (!kickedReports || kickedReports.length == 0) {
promise.resolve(result);
} else {
util.findUsers(kickedReports).then(function(users) {
result.kickedReports = util.infoForUsers(users);
promise.resolve(result);
}, function(users, error) {
console.log(error);
promise.resolve(result);
});
}
return promise;
}
notifications.js:
var Keg = Parse.Object.extend("Keg");
var Notification = Parse.Object.extend("Notifications");
exports.sendKegRefillNotification = function(keg) {
var beerName = keg.get("beerName");
console.log("Sending notifications that keg is refilled to '" + beerName + "'.");
var promise = new Parse.Promise();
var query = new Parse.Query(Notification);
query.include("user");
query.equalTo("keg_filled", true);
query.find({ useMasterKey: true }).then(function(notifications) {
console.log("Found notifications!");
promise.resolve("Found notifications!");
},
function(notifications, error) {
console.error("No notifications");
console.error(error);
promise.reject(error);
});
return promise;
}
Parse.Cloud.job("beerSendRefillNotification", function(request, status) {
var query = new Parse.Query(Keg);
query.descending("createdAt");
query.first().then(function(keg) {
if (!keg) {
status.error("No keg");
return;
}
exports.sendKegRefillNotification(keg);
},
function(keg, error) {
response.error(error);
});
});
When I run the job "beerSendRefillNotification" from the Parse dashboard, I can tell that query.find() is getting called because it prints "Found notifications!":
E2015-02-23T06:59:49.006Z]v1564 Ran job beerSendRefillNotification with:
Input: {}
Result: success/error was not called
I2015-02-23T06:59:49.055Z]false
I2015-02-23T06:59:49.190Z]Sending notifications that keg is refilled to 'test'.
I2015-02-23T06:59:49.243Z]Found notifications!
However, when I call the cloud function "beerFillKeg", it isn't because it's not printing "Found notifications!" or "No notifications":
I2015-02-23T07:00:17.414Z]v1564 Ran cloud function beerFillKeg for user HKePOEWZvC with:
Input: {"name":"Duff"}
Result: {"beer":{"name":"Duff"},"filled":{"__type":"Date","iso":"2015-02-23T07:00:17.485Z"},"id":"olLXh0F54E","kickedReports":[]}
I2015-02-23T07:00:17.438Z]false
I2015-02-23T07:00:17.523Z]Keg updated to Duff.
I2015-02-23T07:00:17.525Z]Sending notifications that keg is refilled to 'Duff'.
I finally understand it. In sendKegRefillNotification, you're calling query.find({...}), then returning an object. That find is asynchronous, and you're doing nothing to wait for the result. I think you need to return the find function call, rather than an object you set within that method.
In other words, you're running along, leaving some async running code behind you.
Edit: I understand what you tried to do. It sort of makes sense. You defined a promise, and thought the caller would wait for the promise. The problem is, the promise is defined in an asynchronous block. It doesn't yet have any meaning at the moment the caller gets it.
It looks like Parse doesn't allow you to run a query from inside a callback from save(). When I moved "notify.sendKegRefillNotification(keg);" to outside of the callback, it worked.
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
console.log("Send notifications.");
promise.resolve(keg);
},
function(keg, error) {
promise.reject(error);
});
notify.sendKegRefillNotification(keg); // Now this works
return promise;
}
Can anyone shed some more light on why this worked?

submitAdapterAuthentication not working

I have been trying to do a specific operation once I receive the submitAdapterAuthentication from the challenge handler and I could not do any operation because my code it does not even compile through it. I am using the submitAdapterAuthentication in one method of my angular service. The method looks like this:
login: function (user, pass) {
//promise
var deferred = $q.defer();
//tempuser
tempUser = {username: user, password: pass};
userObj.user = user;
checkOnline().then(function (onl) {
if (onl) { //online
console.log("attempting online login");
var auth = "Basic " + window.btoa(user + ":" + pass);
var invocationData = {
parameters: [auth, user],
adapter: "SingleStepAuthAdapter",
procedure: "submitLogin"
};
ch.submitAdapterAuthentication(invocationData, {
onFailure: function (error) {
console.log("ERROR ON FAIL: ", error);
},
onConnectionFailure: function (error) {
console.log("BAD CONNECTION - OMAR", error);
},
timeout: 10000,
fromChallengeRequest: true,
onSuccess: function () {
console.log("-> submitAdapterAuthentication onSuccess!");
//update user info, as somehow isUserAuthenticated return false without it
WL.Client.updateUserInfo({
onSuccess: function () {
//return promise
deferred.resolve(true);
}
});
}
});
} else { //offline
console.log("attempting offline login");
deferred.resolve(offlineLogin());
}
uiService.hideBusyIndicator();
});
uiService.hideBusyIndicator();
return deferred.promise;
}
where ch is
var ch = WL.Client.createChallengeHandler(securityTest);
and checkOnline is this function that checks whether the user is online or not:
function checkOnline() {
var deferred = $q.defer();
WL.Client.connect({
onSuccess: function () {
console.log("** User is online!");
deferred.resolve(true);
},
onFailure: function () {
console.log("** User is offline!");
deferred.resolve(false);
},
timeout: 1000
});
return deferred.promise;
}
Finally this is the "submitLogin" procedure that I have in my SingleStepAuthAdapter.js. SingleStepAuthAdapter is the name of the adapter.
//-- exposed methods --//
function submitLogin(auth, username){
WL.Server.setActiveUser("SingleStepAuthAdapter", null);
var input = {
method : 'get',
headers: {Authorization: auth},
path : "/",
returnedContentType : 'plain'
};
var response = "No response";
response = WL.Server.invokeHttp(input);
WL.Logger.info('Response: ' + response.isSuccessful);
WL.Logger.info('response.responseHeader: ' + response.responseHeader);
WL.Logger.info('response.statusCode: ' + response.statusCode);
if (response.isSuccessful === true && (response.statusCode === 200)){
var userIdentity = {
userId: username,
displayName: username,
attributes: {
foo: "bar"
}
};
WL.Server.setActiveUser("SingleStepAuthAdapter", userIdentity);
return {
authRequired: false
};
}
WL.Logger.error('Auth unsuccessful');
return onAuthRequired(null, "Invalid login credentials");
}
So I am trying to send a promise to my controller in order to redirect the user to another page but the promise is not being returned as the challenge handler is not even working.
And by the way, I have followed this tutorial: https://medium.com/#papasimons/worklight-authentication-done-right-with-angularjs-768aa933329c
Does anyone know what this is happening?
Your understanding of the Challenge Handler and mine are considerably different.
Although the
ch.submitAdapterAuthentication()
is similar in structure to the standard adapter invocation methods I have never used any callbacks with it.
I work from the IBM AdapteBasedAuthentication tutorial materials
The basic idea is that your challenge handler should have two callback methods:
isCustomResponse()
handleChallenge()
You will see these functions invoked in response to your submission.
I suggest that start by looking at those methods. I can't comment on the ionic example you reference, but I have myself used angular/ionic with the authentication framework and challenge handlers. My starting point was the IBM material I reference above.

Resources