I am creating a new PFUser to be a "sub user" of the currently logged in user.
For example Master is logged in and decides to create a Junior User.
The Junior user will have a different PFRole to the Master.
I can create a new User via
var newUser = PFUser()
newUser.email = "someemail#gmail.com"
newUser.username = "Demo Subuser"
newUser.password = "12341234"
newUser.signUpInBackgroundWithBlock { (newUser, error) -> Void in
println(error)
}
The user is created, but the problem is I am logged out of the master account and logged in as the Junior user.
How can I create a new PFUser without it logging into that account.
Ideally, I want to send an email to the new subuser stating, welcome here is your username and details.
Maybe I should be using CloudCode ?
Thanks
Create a Cloudcode function and upload it to Parse
Parse.Cloud.define("createNewUser", function(request, response) {
// extract passed in details
var username = request.params.username
var pw = request.params.password
var email = request.params.email
// cloud local calls
var user = new Parse.User();
user.set("username", username);
user.set("password", pw);
user.set("email", email);
user.signUp(null, {
success: function(user) {
//response.error("working");
// do other stuff here
// like set ACL
// create relationships
// and then save again!! using user.save
// you will need to use Parse.Cloud.useMasterKey();
},
error: function(user, error) {
//response.error("Sorry! " + error.message);
} });
});
Call the cloud code function from within your app via. Pass in details to the clod function using a Dictionary, ["username":"", "password":""] etc
PFCloud.callFunctionInBackground
Related
Are we able to clear PFInstallation data forcefully?
Needed this to recreate another PFInstallation when I want to force logout a user.
Current Problem:
New Account is using PFInstallation of old account and New Account can't update the PFInstallation (clearing it when logging out).
Other Possible Solution:
Update PFInstallation's ACL through cloud code with the new account's data. Is this possible?
In the beforeSave trigger for the installation, check to see if the user is getting set. If so, turn off public read/write access and give read/write access to that user. When logging out, the user should be removed, and you can return public read/write access.
Parse.Cloud.beforeSave(Parse.Installation, function(request, response) {
var installation = request.object;
if( installation.dirty("user") ) {
var acl = new Parse.ACL();
var user = installation.get("user");
if( user ) {
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setReadAccess(user.id, true);
acl.setWriteAccess(user.id, true);
}
else {
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
}
installation.setACL(acl);
}
response.success();
});
I am trying to send a push notification using a user's id. I have already tested sending with installationId, querying the _Installation class, but i would like to query the session class of the user pointer, to then turn around and query the installation class.
My problem is lying in the restrictions of querying the session class. I have successfully used createWithoutData() found here, and I know it is working because i can output that user. However, even after using the master key found here, the results are always empty.
The general practise for sending Push Notification to specific user is that you will store pointer to User in Installation class... for example when user register do this
Swift
if let installation = PFInstallation.current() {
installation["user_id"] = PFUser.current()!
installation.saveInBackground()
}
Cloudcode
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.equalTo('user_id', tarUser);
pushQuery.exists("deviceToken");
pushQuery.limit(1); // in case there are more Installation with the user ID, use only the latest
pushQuery.descending("createdAt");
Parse.Push.send({
where: pushQuery, // Set our Installation query
data: {
alert: "Some push text"
}
}, {
success: function() {
// Push was successful
response.success();
},
error: function(error) {
console.error("Got an error " + error.code + " : " + error);
response.error(error);
},
useMasterKey: true
});
if I remember correctly you have to query the pointer in Cloud code with pointer structure, like this
var tarUser = {
__type: 'Pointer',
className: '_User',
objectId: 'insertObjectIDHere'
};
Can some one just confirm that in order to add a user to a existing role the role needs to have public read & write access ?
as this seems to be the only way i can get it to work?
Code to create the Role (Working Fine)
let roleACL = PFACL()
roleACL.setPublicReadAccess(true)
//roleACL.setPublicWriteAccess(true)
let role = PFRole(name: "ClubName", acl:roleACL)
role.saveInBackground()
Code to add user to said Role (Works If write access set to public)
let QueryRole = PFRole.query()
QueryRole!.whereKey("name", equalTo: "ClubName")
QueryRole!.getFirstObjectInBackgroundWithBlock({ (roleObject: PFObject?, error: NSError?) -> Void in
if error == nil
{
let roleToAddUser = roleObject as! PFRole
roleToAddUser.users.addObject(user)
roleToAddUser.saveInBackground()
//print(roleObject)
}
else
{
print(error)
//print(roleObject)
}
})
the above code works but as i said only when the public write access to the role has been set true.
this is driving me crazy now
also IF the role is meant to have the public write access doesn't that make it vulnerable to someone changing the role?
if the role shouldn't have public write access then can someone point me in the right direction to get the above code working without setting the role with public write access.
the error i get if there is no public write access on the role is: object not found for update (Code: 101, Version: 1.8.1)
Why not perform all the work in cloud code instead, that will get you around the issue of read/write issue. Here is what I am doing.
Important to note: The code below would not work when the cloud code JDK was set to 'latest', I had to set it to JDK 1.5 and then redepled my cloud code.
Parse.Cloud.afterSave(Parse.User, function(request) {
Parse.Cloud.useMasterKey();
var user = request.object;
if (user.existed()) {
//console.log('object exits');
response.success();
// console.log('********return after save');
return;
}
// set ACL so that it is not public anymore
var acl = new Parse.ACL(user);
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
user.setACL(acl);
user.save();
//add user to role
var query = new Parse.Query(Parse.Role);
query.equalTo("name", "signedmember");
query.first().then(function(object) {
if (object) {
object.relation("users").add(request.user);
object.save(null, {
success: function(saveObject) {
object.relation("users").add(request.user);
object.save(null, {
success: function(saveObject) {
// The object was saved successfully.
console.log('assigned user to role');
},
error: function(saveObject, error) {
// The save failed.
console.error("Failed creating role with error: " + error.code + ":"+ error.message);
}
});
},
});
}
});
});
How to implement login mechanism with mobile verification code.
SignUp (New User with New Mobile Number)
I can able to do this for signup user by generating a random password after verifying code send to his mobile number.
Login (Existing User with Mobile Number)
But don't knows how to implement this. I cant use changepassword method because it works only for an already logged in user.
Setting the Current User
Saw this Method in Parse Documentation. Can I use this method. If yes, how can I get session token.
[PFUser becomeInBackground:#"session-token-here" block:^(PFUser *user, NSError *error) {
if (error) {
// The token could not be validated.
} else {
// The current user is now set to user.
}
}];
Successfully changed the password without login calling cloud code from ios and then logged in with a new password.
iOS Code
[PFCloud callFunctionInBackground:#"assignPasswordToUser" withParameters:#{#"username":[self generateUsername],#"password":loginModel.verficationCode} block:^(id object, NSError *error) {
if(!error)
{
NSLog(#"Assign New Password Success");
[self doLogin];
}else{
NSLog(#"Assign New Password Failed");
[self handError:error];
}
}];
Cloud Code
Parse.Cloud.define("assignPasswordToUser", function(request, response){
Parse.Cloud.useMasterKey();
var query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
query.first({
success: function(theUser){
var newPassword = request.params.password;
console.log("New Password: " + newPassword);
console.log("set: " + theUser.set("password", newPassword));
theUser.save(null,{
success: function(theUser){
// The user was saved correctly
response.success(1);
},
error: function(SMLogin, error){
response.error("save failure");
}
});
},
error: function(error){
response.error("error");
}
});
});
Assuming your wanted to allow something like registering a new device by scanning a QR Code or something shown on an existing device, you could do that without having to change the password as follows:
User class extra properties:
loginValidation: String
loginValidationExpiry: DateTime
You would use something like a 1 or 2 minute expiry to make things safer, making sure you create the Date using server-time in a Cloud Function. You could generate a guid/uuid for the code and create the QR code on an existing authenticated platform.
On the new device after reading the QR Code you could call this cloud function:
Parse.Cloud.define("validateLoginCode", function(request, response) {
Parse.Could.useMasterKey();
var username = request.params.username;
var validationCode = request.params.validationCode;
var query = new Parse.Query(Parse.User);
query.equalTo("username", username);
query.equalTo("loginValidation", validationCode);
query.first({
success: function(user) {
if (user) {
var expiry = user.get("loginValidationExpiry");
var now = new Date();
if (expiry > now) {
// code is valid, get token, only valid because
// we got user with master key
response.success({ token: user.getSessionToken() });
} else {
response.error("code expired");
}
} else {
response.error("invalid user/code");
}
},
error: function(error) {
response.error("error");
}
});
});
You could now use becomeInBackground:block: method in your calling code.
Suppose I logged into my device's Facebook authentication, like system Facebook on iOS. I obtain an access token.
How can I use the access token to login to Meteor's Facebook Oauth provider?
To login with Facebook using an access token obtained by another means, like iOS Facebook SDK, define a method on the server that calls the appropriate Accounts method:
$FB = function () {
if (Meteor.isClient) {
throw new Meteor.Error(500, "Cannot run on client.");
}
var args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
return;
}
var path = args[0];
var i = 1;
// Concatenate strings together in args
while (_.isString(args[i])) {
path = path + "/" + args[i];
i++;
}
if (_.isUndefined(path)) {
throw new Meteor.Error(500, 'No Facebook API path provided.');
}
var FB = Meteor.npmRequire('fb');
var fbResponse = Meteor.sync(function (done) {
FB.napi.apply(FB, [path].concat(args.splice(i)).concat([done]));
});
if (fbResponse.error !== null) {
console.error(fbResponse.error.stack);
throw new Meteor.Error(500, "Facebook API error.", {error: fbResponse.error, request: args});
}
return fbResponse.result;
};
Meteor.methods({
/**
* Login to Meteor with a Facebook access token
* #param accessToken Your Facebook access token
* #returns {*}
*/
facebookLoginWithAccessToken: function (accessToken) {
check(accessToken, String);
var serviceData = {
accessToken: accessToken
};
// Confirm that your accessToken is you
try {
var tokenInfo = $FB('debug_token', {
input_token: accessToken,
access_token: Meteor.settings.facebook.appId + '|' + Meteor.settings.facebook.secret
});
} catch (e) {
throw new Meteor.Error(500, 'Facebook login failed. An API error occurred.');
}
if (!tokenInfo.data.is_valid) {
throw new Meteor.Error(503, 'This access token is not valid.');
}
if (tokenInfo.data.app_id !== Meteor.settings.facebook.appId) {
throw new Meteor.Error(503, 'This token is not for this app.');
}
// Force the user id to be the access token's user id
serviceData.id = tokenInfo.data.user_id;
// Returns a token you can use to login
var loginResult = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
// Login the user
this.setUserId(loginResult.userId);
// Return the token and the user id
return loginResult;
}
}
This code depends on the meteorhacks:npm package. You should call meteor add meteorhacks:npm and have a package.json file with the Facebook node API: { "fb": "0.7.0" }.
If you use demeteorizer to deploy your app, you will have to edit the output package.json and set the scrumptious dependency from "0.0.1" to "0.0.0".
On the client, call the method with the appropriate parameters, and you're logged in!
In Meteor 0.8+, the result of Accounts.updateOrCreateUserFromExternalService has changed to an object containing {userId: ...} and furthermore, no longer has the stamped token.
You can get the accessToken in the Meteor.user() data at Meteor.user().services.facebook.accessToken (be aware this can only be accessed on the server side as the services field is not exposed to the client.
So when a user logs in with facebook on your meteor site these fields would be populated with the user's facebook data. If you check your meteor user's database with mongo or some other gui tool you could see all the fields which you have access to.
Building on DrPangloss' most excellent answer above, combining it with this awesome post: http://meteorhacks.com/extending-meteor-accounts.html
You'll run into some issues using ObjectiveDDP in trying to get the client persist the login. Include the header:
#import "MeteorClient+Private.h"
And manually set the required internals. Soon I'll make a meteorite package and an extension to MyMeteor (https://github.com/premosystems/MyMeteor) but for now it's manual.
loginRequest: {"accessToken":"XXXXXb3Qh6sBADEKeEkzWL2ItDon4bMl5B8WLHZCb3qfL11NR4HKo4TXZAgfXcySav5Y8mavDqZAhZCZCnDDzVbdNmaBAlVZAGENayvuyStkTYHQ554fLadKNz32Dym4wbILisPNLZBjDyZAlfSSgksZCsQFxGPlovaiOjrAFXwBYGFFZAMypT9D4qcZC6kdGH2Xb9V1yHm4h6ugXXXXXX","fbData":{"link":"https://www.facebook.com/app_scoped_user_id/10152179306019999/","id":"10152179306019999","first_name":"users' first name","name":"user's Full Name","gender":"male","last_name":"user's last name","email":"users#email.com","locale":"en_US","timezone":-5,"updated_time":"2014-01-11T23:41:29+0000","verified":true}}
Meteor.startup(
function(){
Accounts.registerLoginHandler(function(loginRequest) {
//there are multiple login handlers in meteor.
//a login request go through all these handlers to find it's login hander
//so in our login handler, we only consider login requests which has admin field
console.log('loginRequest: ' + JSON.stringify(loginRequest));
if(loginRequest.fbData == undefined) {
return undefined;
}
//our authentication logic :)
if(loginRequest.accessToken == undefined) {
return null;
} else {
// TODO: Verfiy that the token from facebook is valid...
// https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0#checktoken
// graph.facebook.com/debug_token? input_token={token-to-inspect}&access_token={app-token-or-admin-token}
}
//we create a user if not exists, and get the userId
var email = loginRequest.fbData.email || "-" + id + "#facebook.com";
var serviceData = {
id: loginRequest.fbData.id,
accessToken: loginRequest.accessToken,
email: email
};
var options = {
profile: {
name: loginRequest.fbData.name
}
};
var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options);
console.log('Logged in from facebook: ' + user.userId);
//send loggedin user's user id
return {
userId: user.userId
}
});
}
);
This answer could be improved further as we can now directly debug the token from a REST http request using futures. Credit still goes to #DoctorPangloss for the principal steps necessary.
//Roughly like this - I removed it from a try/catch
var future = new Future();
var serviceData = {
accessToken: accessToken,
email: email
};
var input = Meteor.settings.private.facebook.id + '|' + Meteor.settings.private.facebook.secret
var url = "https://graph.facebook.com/debug_token?input_token=" + accessToken + "&access_token=" + input
HTTP.call( 'GET', url, function( error, response ) {
if (error) {
future.throw(new Meteor.Error(503, 'A error validating your login has occured.'));
}
var info = response.data.data
if (!info.is_valid) {
future.throw(new Meteor.Error(503, 'This access token is not valid.'));
}
if (info.app_id !== Meteor.settings.private.facebook.id) {
future.throw(new Meteor.Error(503, 'This token is not for this app.'));
}
// Force the user id to be the access token's user id
serviceData.id = info.user_id;
// Returns a token you can use to login
var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
if(!user.userId){
future.throw(new Meteor.Error(500, "Failed to create user"));
}
//Add email & user details if necessary
Meteor.users.update(user.userId, { $set : { fname : fname, lname : lname }})
Accounts.addEmail(user.userId, email)
//Generate your own access token!
var token = Accounts._generateStampedLoginToken()
Accounts._insertLoginToken(user.userId, token);
// Return the token and the user id
future.return({
'x-user-id' : user.userId,
'x-auth-token' : token.token
})
});
return future.wait();
Use this instead of the JS lib suggested by #DoctorPangloss. Follow the same principles he suggested but this avoids the need to integrate an additional library