Parse iOS SDK - Login using Mobile phone verification code like Watsapp - ios

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.

Related

Execute a charge from existing customer with card in Stripe out of iOS

I have a very hard time figuring out how to do this from the iOS app programmatically.
Im using (besides other) the following classes from github:
https://github.com/hybrdthry911/ELStripe
Class: ELCharge.m
+(ELCharge *)charge{
return [[ELCharge alloc]init];
}
(*****************code in between ************************)
//Will attempt to charge either a customer or card. Exactly one must exist
per charge. If multiple or neither exist an exception will be raised.
//Warning: This is the final step it will APPLY A CHARGE TO THE
ACCOUNT.
-(void)createChargeWithCompletion:(ELChargeCompletionBlock)handler{
[ELCharge createCharge:self completion:handler];
}
+(void)createCharge:(ELCharge *)charge completion
(ELChargeCompletionBlock)handler{
NSError *chargeError;
if (![charge validForProcessingWithError:&chargeError]) {
handler(nil,chargeError);
return;
}
if (!chargeError) {
[ELStripe executeStripeCloudCodeWithMethod:#"POST"
prefix:#"charges" parameters:[ELCharge
dictionaryForCreatingCharge:charge] completionHandler:^(id
jsonObject, NSError *error) {
handler([ELCharge
chargeFromParseStripeDictionary:jsonObject],error);
}];
}
}
In the iOS class, I do the following in order to create a test charge:
//create an automatic charge in stripe from an existing customer with card
attached to him
-(void) executeChargeInStripe:(UIButton*)sender
{
ELCharge *charge = [ELCharge charge];
//Assign the charge properties
charge.customerID = #"cus_72xvQI6Q5IC9it";
charge.currency = #"USD";
NSNumber *chargeAmount = [NSNumber numberWithInt:111];
charge.amountInCents = chargeAmount;
//Call ChargeMethod from Github Framework
[ELCharge createCharge:charge completion:^(ELCharge *charge, NSError
*error) {
if (!error) {
//code for normal handling
NSLog(#"Charge has been made successfully");
} else {
// error code handling
NSLog(#"Charge NOT made");
}
}];
}
Im passing this to the following clould code:
Parse.Cloud.define("stripeHTTPRequest", function(request, response)
{
//Check for valid pre/suf/postfixes, if they are not there do not include
them.
var prefix = request.params["prefix"];
var suffix = "";
var postfix = "";
var secondPostfix = "";
if (!isEmpty(request.params["suffix"])) suffix =
'/'+request.params['suffix'];
if (!isEmpty(request.params["postfix"])) postfix =
'/'+request.params['postfix'];
if (!isEmpty(request.params["secondPostfix"])) secondPostfix =
'/'+request.params['secondPostfix'];
//call from parse to stripe done by http request as parse/stripe api
uncomplete
Parse.Cloud.httpRequest(
{
method: request.params["method"],
//Create URL from base url and pre/suf/postfixes
url: 'https://'+STRIPE_API_BASE_URL + prefix + suffix + postfix +
secondPostfix,
headers: {
'Authorization': "Bearer " + STRIPE_SECRET_KEY
},
params:request.params["params"],
success: function(httpResponse)
{
//response text is a json dictionary
response.success(httpResponse.text);
},
error: function(httpResponse)
{
response.error(httpResponse.text);
}
});
});
Im now getting the following error:
Charge NOT made (my error message from the IOS class I created).
Im really surprised that I recieve neither an error in Parse Cloud nor in Stripe.
If for example, I use an already used token instead of the customerID, I have an error in both. So the connection seems to work I assume, maybe there is something which I do wrong in the iOS class.
Thank you!
have you reverted your Parse JavaScript SDK to 1.5.0? Anything past 1.5.0 is no longer supported.

Create new PFUser using code without logging in as this new PFUser

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

How do I register a JWT token expired event with AngularFire? (AngularFire Custom Authentication)

How do I register a JWT token expired event with AngularFire?
Right now I have my Rails server sending a JWT to the AngularJS client, which is set to expire in 60 seconds. I'm connecting to Firebase on my client successfully. After 60 seconds, it expires and I get a "FIREBASE WARNING: auth() was canceled: Auth token is expired." message in my browser console.
It is not clear to me what to watch for this event. My goal would be to call my server for a new token when my current one expires.
client code snippet: here is me initializing a firebase obj and watching a url
var ref = new Firebase(URL);
// Create a callback which logs the current auth state
var authDataCallback = function (authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out", authData);
}
}
// Register the callback to be fired every time auth state changes
ref.onAuth(authDataCallback);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
var firebaseBal = $firebase(ref).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);
server code snippet (I use firebase-token-generator to generate my JWT token):
generator = Firebase::FirebaseTokenGenerator.new(secret);
payload = { :uid => "123" }
# expired in a minute, for testing
expires = Time.now.to_i + (60*1)
options = {:expires => expires}
token = generator.create_token(payload, options)
// this is then sent to the client
Problem to be solved soon, see GitHub issue - https://github.com/firebase/angularfire/issues/514
This GitHub issue exists for the exact reason to help you accomplish
what you want to accomplish :) The short answer is that we don't
really support this that well right now.
For the time being, you will have to use $extendFactory() and put your
logic in the $$error()method. Here are the slim docs on that. The
$$error() method will fire once authentication fails due to token
expiry or security rules.
You should see improvements in this area in the coming weeks and this
is tagged for the 1.0.0 release.
var ref = new Firebase(URL);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
// create a factory to pass into $firebase
var timeoutErrorFactory = $FirebaseObject.$extendFactory({
$$error: function(data) {
// TODO: not sure if super needs to be applied
// var err = $FirebaseObject.prototype.$$error.apply(this, arguments);
console.log('firebase timeout error cleanup', data.code);
// TODO: call server for a new token
}
});
// create an instance which uses the customized factory
firebaseBal = $firebase(ref, {objectFactory: timeoutErrorFactory}).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);

Meteor acounts add info from Twitter accounts

I'm trying to figure out how I can add additional information from a user's Twitter account to the created account on a Meteor installation.
In particular I am trying to access the user's bio via Twitter Api v 1.1 and am not successful in doing so.
Therefore I am trying to extend Accounts.onCreateUser(function(options,user) {}); with the Twitter bio. How do I do that? And then access this data from a template?
Here's a perfect answer for returning data from Github, however I've had trouble porting this approach over to Twitter as the authenticating service: Meteor login with external service: how to get profile information?
You could do it on this way:
Accounts.onCreateUser(function (options, user){
user.profile = options.profile || {};
//Twitter returns some useful info as the username and the picture
if(user.services.twitter){
user.profile.picture= user.services.twitter.profile_image_url_https;
user.profile.username= user.services.twitter.screenName;
}
return user;
});
For getting the data from the Twitter API I´m using the node package oauth:
OAuth = Npm.require('oauth');
oauth = new OAuth.OAuth(
'https://api.twitter.com/oauth/request_token',
'https://api.twitter.com/oauth/access_token',
'consumerKey',
'secretKey',
'1.0A',
null,
'HMAC-SHA1'
);
getTwitterUserData: function (id) {
var accountUser = AccountsUserCollection.findOne({_id: id});
var url = "https://api.twitter.com/1.1/users/show.json?screen_name="+accountUser.screen_name;
oauth.get(url, 'accessToken', 'accessSecret', function (err, data, response) {
if(err){
console.log(err);
}
if(data){
Fiber(function () {
AccountsUserCollection.update({_id: accountUser._id}, {$set: {dataTwitter: JSON.parse(data)}});
}).run();
}
if(response){
Log.info(response);
}
});
}

How can I login to Meteor with native device Facebook?

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

Resources