In Meteor, I'd like to add a referrer field to new user signups. I can do this for users that register with a password, by passing in extra options to be used with Accounts.onCreateUser(function (options, user).
But this doesn't seem to work with social login. Any ideas how I can get this working?
I think I had a similar issue, and ended doing something like
create the user without the field you would like to pass as an option
ask the client for the value of that field
update the user
So:
//server
Accounts.onCreateUser(function(options, user) {
if (user.services.facebook)
user.askReferer = true;
retur user;
});
//client
var userId;
Tracker.autorun(function() {
var user = Meteor.user();
if (!user || user.id === userId || !user.askReferer) {return;}
userId = user.id;
var referer = ...;
Users.update({_id: user.id}, {$set: {referer: referer}}, {$unset: {askReferer: 1}});
});
Would love if someone comes up with a better solution!
Related
There are some irreversible actions that user can do in my app. To add a level of security, I'd like to verify that the person performing such an action is actually the logged in user. How can I achieve it?
For users with passwords, I'd like a prompt that would ask for entering user password again. How can I later verify this password, without sending it over the wire?
Is a similar action possible for users logged via external service? If yes, how to achieve it?
I can help with the first question. As of this writing, meteor doesn't have a checkPassword method, but here's how you can do it:
On the client, I'm going to assume you have a form with an input called password and a button called check-password. The event code could look something like this:
Template.userAccount.events({
'click #check-password': function() {
var digest = Package.sha.SHA256($('#password').val());
Meteor.call('checkPassword', digest, function(err, result) {
if (result) {
console.log('the passwords match!');
}
});
}
});
Then on the server, we can implement the checkPassword method like so:
Meteor.methods({
checkPassword: function(digest) {
check(digest, String);
if (this.userId) {
var user = Meteor.user();
var password = {digest: digest, algorithm: 'sha-256'};
var result = Accounts._checkPassword(user, password);
return result.error == null;
} else {
return false;
}
}
});
For more details, please see my blog post. I will do my best to keep it up to date.
I haven't done this before, but I think you will need something like this on your server
Accounts.registerLoginHandler(function(loginRequest) {
console.log(loginRequest)
var userId = null;
var username = loginRequest.username;
// I'M NOT SURE HOW METEOR PASSWORD IS HASHED...
// SO YOU NEED TO DO A BIT MORE RESEARCH ON THAT SIDE
// BUT LET'S SAY YOU HAVE IT NOW
var password = loginRequest.password;
var user = Meteor.users.findOne({
$and: [
{username: username},
{password: password}
]
});
if(!user) {
// ERROR
} else {
// VERIFIED
}
});
then you can call this function from the client side like this:
// FETCH THE USERNAME AND PASSWORD SOMEHOW
var loginRequest = {username: username, password: password};
Accounts.callLoginMethod({
methodArguments: [loginRequest]
});
I have a project on github for different purpose, but you can get a sense of how it is structured: https://github.com/534N/apitest
Hope this helps,
I have found the best way to validate the users password is to use the Accounts.changePassword command and
pass in the same password for old and new password. https://docs.meteor.com/api/passwords.html#Accounts-changePassword
Accounts.changePassword(this.password, this.password, (error) => {
if(error) {
//The password provided was incorrect
}
})
If the password provided is wrong, you will get an error back and the users password will not be changed.
If the password is correct, the users password will be updated with the same password as is currently set.
I just got done with the rough draft of my app, and thought it was time to remove autopublish and insecure mode. I started transfering all the stray update and insert methods I had been calling on the client to methods. But now I'm having trouble returning a username from an ID.
My function before: (that worked, until I removed autopublish)
challenger: function() {
var postId = Session.get('activePost');
var post = Posts.findOne(postId);
if (post.challenger !== null) {
var challenger = Meteor.users.findOne(post.challenger);
return challenger.username;
}
return false;
}
Now what I'm trying:
Template.lobby.helpers({
challenger: function() {
var postId = Session.get('activePost');
var post = Posts.findOne(postId);
if (post.challenger !== null) {
var userId = post.challenger;
Meteor.call('getUsername', userId, function (err, result) {
if (err) {
console.log(err);
}
return result;
});
}
return false;
},
Using:
Meteor.methods({
getUsername: function(userId) {
var user = Meteor.users.findOne({_id: userId});
var username = user.username;
return username;
},
...
})
I have tried blocking the code, returning values only once they're defined, and console.logging in the call-callback (which returned the correct username to console, but the view remained unchanged)
Hoping someone can find the obvious mistake I'm making, because I've tried for 3 hours now and I can't figure out why the value would be returned in console but not returned to the template.
Helpers need to run synchronously and should not have any side effects. Instead of calling a method to retrieve the user, you should ensure the user(s) you need for that route/template are published. For example your router could wait on subscriptions for both the active post and the post's challenger. Once the client has the necessary documents, you can revert to your original code.
I want to publish a collection to the owner which is accessed by this.userId (that part is working) and to those that the owner has invited. The only way I can do this is through the invited.email in my collection because the user being invited may not be a registered user yet.
Meteor.publish("meals", function () {
var current_user = Meteor.user();
return MealModel.find(
{$or: [{"invited.email": current_user.emails[0].address}, {owner: this.userId}]});
});
I don't want to publish the entire collection and query on the client side because I am afraid the collection could potentially get too big.
Any suggestions? Thanks.
Unfortunately, you can't use Meteor.user() inside of a publish function. Instead, you need to do a findOne with the userId. Give this a try:
Meteor.publish('meals', function() {
if (!this.userId)
return this.ready();
var user = Meteor.users.findOne(this.userId);
var email = user.emails[0].address;
return MealModel.find({$or: [{'invited.email': email}, {owner: this.userId}]});
});
In my Meteor app I use the default accounts package, which gives me the default login and registration functionality. Now I want to add an extra field to user, say nickname, and for the logged in user the possibility to edit this information.
For editing the profile I suppose I should be doing something like this:
Template.profileEdit.events({
'submit form': function(e) {
e.preventDefault();
if(!Meteor.user())
throw new Meteor.Error(401, "You need to login first");
var currentUserId = this._id;
var user = {
"profile.nickname": $(e.target).find('[name=nickname]').val()
};
Meteor.users.update(currentUserId, {
$set: user
}, function(error){
if(error){
alert(error.reason);
} else {
Router.go('myProfile', {_id: currentUserId});
}
});
}
});
But I doesn't store the info if I look in Mongo. Also when showing the profile, {{profile.nickname}} returns empty. What is wrong here?
Edit: added collections\users.js to show permissions:
Meteor.users.allow({
update: function (userId, doc) {
if (userId && doc._id === userId) {
return true;
}
}
});
Meteor.users.deny({
update: function(userId, user, fieldNames) {
return (_.without(fieldNames, 'profile.nickname').length > 0);
}
});
Yeah, I believe that should do the job, although I haven't actually run the code. The idea is certainly right.
The main things to be aware of are:
The necessity to allow the user doc to be edited from the client with an appropriate Meteor.users.allow() block on the server, assuming you're going to remove the "insecure" package (which you need to before doing anything in production).
The fact that "by default the server publishes username, emails, and profile", so you'll need to write a Meteor.publish function on the server and subscribe to it if you want to expose any other fields within the user document to the client once you've removed the "autopublish" package (which again, you really should).
I am using Meteor and people can connect to the site via Facebook. I'm using people username to identify them. But, some of them don't have a username. For example, a new user has null as a username. What I'm trying to do is, if the person has a username then OK we use their username. If not, I wanna use their Facebook id as their username. The problem is, my if condition is not working properly. If the person has a username, the if condition considers that the person doesn't. The weird thing is, if I do a console.log of the username before the if condition, it will show the username. But once in the if, it considers that the username is null. Here is the code :
Accounts.onCreateUser(function(options, user) {
var fb = user.services.facebook;
var token = user.services.facebook.accessToken;
if (options.profile) {
options.profile.fb_id = fb.id;
options.profile.gender = fb.gender;
options.profile.username = fb.username
console.log( 'username : ' + options.profile.username);
if ( !(options.profile.username === null || options.profile.username ==="null" || options.profile.username === undefined || options.profile.username === "undefined")) {
console.log('noooooooo');
options.profile.username = fb.id;
} else {
console.log('yessssssss');
options.profile.username = fb.username;
}
options.profile.email = fb.email;
options.profile.firstname = fb.first_name;
user.profile = options.profile;
}
sendWelcomeEmail(options.profile.name, options.profile.email);
return user;
});
With this code, if I log in with my Facebook that has a username. The condition will show 'noooooooo' but the console.log( 'username : ' + options.profile.username); will show my username. Why does it do that? :l
It's because creating is called before logging and logging is asynchronous .. so you cannot ensure that your if will be or will not true/false. Your putting information from fb service is redundant, because all these informations are already saved with user.
http://docs.meteor.com/#meteor_user
You should obtain information about user after he is loggedIn because in that moment you will be able to recognise what kind of identifier you can use username/id.
//Server side
Meteor.publish("userData", function () {
return Meteor.users.find({_id: this.userId});
// You can publish only facebook id..
/*return Meteor.users.find({_id: this.userId},
{
fields: {
'services.facebook.id': true
}
}
);*/
});
//Client side
Meteor.subscribe("userData");
// .. you can see more informations about logged user
console.log(Meteor.users.find({}).fetch());