How to validate data in onCreateUser without losing the form data? - meteor

I'm writing an Appliction using Meteor. In this App I want to implement a server-side validation of the user data using Accounts.onCreateUser. There is some data passed which can only be verified on the server side.
At client side I call:
Template.register.events({
'submit form': function (e) {
e.preventDefault();
var attributes = {
username: $("#inputUsername").val(),
password: $("#inputPassword").val(),
confirmation: $("inputConfirmation").val(),
email: $("#inputEmail").val(),
...
};
Accounts.createUser(attributes, function(err){
if (err) {
throwError(err);
} else {
}
});
}
});
And on the server side:
Accounts.onCreateUser(function(options, user) {
if(!verifyData(options))
throw new Meteor.Error(403, "Wrong input");
return user;
});
After the server side verification fails, all form data is lost. What is the best way to keep the data?

I went ahead and reproduced your code on a Meteorpad and from what I can tell, the form data does still persist. You just need to access it via the attributes variable in the client-side.
There may be something I am missing, but i took what you posted above and put it in there.

Related

Trying to publish users in Meteor

I'm quite new in Meteor and I got an issue: I added some information to users and I'd like to be able to see it in a view.
So I create my users, here is my event:
Accounts.createUser({
username: username,
email: email,
password: password,
firstname:firstname,
lastname:lastname,
chief:chief
},
function(error){
if(error){
alert(error.reason)
}else{
Router.go('home');
}
});
Accounts.onCreateUser(function(options,user){
user.firstname = options.firstname;
user.lastname = options.lastname;
user.chief = options.chief;
return user;
});
then I publish my collection to get access on my client side of the app:
Meteor.publish("personaldata", function () {
return Meteor.users.find({_id: this.userId});
});
And without doing anything else I got an issue on my app: there is no error message on my command prompt, but when I open my app I got an Ironn:Router message. Here is the message:
'organize your application'
Router.route('/', function () {
this.render('Home', {
data: function () {
return Items.findOne({_id: this.params._id});
}
});
});
Yes the whole part is the message. I tried to subscribe to the collection in 'home' to settle the issue but it doesn't work, the same message is displayed.Does someone know why this message is displayed ? How can I have access to those data? Isn't it the proper way?
Hope you guys can figure out what the problem is, thanks.
Found the problem...
As I'm stupid I put my publish in the lib directory, so this was on both the server and the client side... Meteor didn't know what to do with it on the client side.

Meteor / Jasmine / Velocity : how to test a server method requiring logged in user?

Using velocity/jasmine, I'm a bit stuck on how I should test a server-side method requiring that there be a currently logged-in user. Is there a way to make Meteor think a user is logged in via stub/fake ?
myServerSideModel.doThisServerSideThing = function(){
var user = Meteor.user();
if(!user) throw new Meteor.Error('403', 'not-autorized');
}
Jasmine.onTest(function () {
describe("doThisServerSideThing", function(){
it('should only work if user is logged in', function(){
// this only works on the client :(
Meteor.loginWithPassword('user','pwd', function(err){
expect(err).toBeUndefined();
});
});
});
});
What you could do is add users just to your test suite. You could do this by populating these users in a the server-side test script:
Something like:
Jasmine.onTest(function () {
Meteor.startup(function() {
if (!Meteor.users.findOne({username:'test-user'})) {
Accounts.createUser
username: 'test-user'
... etc
Then, a good strategy could be to use the beforeAll in your test to login (this is client side):
Jasmine.onTest(function() {
beforeAll(function(done) {
Meteor.loginWithPassword('test-user','pwd', done);
}
}
This is assuming your test isn't logged in yet. You can make this more fancy by checking for Meteor.user() and properly logging out in an afterAll, etc. Note how you can handily pass the done callback to many of the Accounts functions.
Essentially, you don't have to mock a user. Just make sure you have the right users, with the correct roles, available in the Velocity/Jasmine DB.
Lets say you have a server side method like this:
Meteor.methods({
serverMethod: function(){
// check if user logged in
if(!this.userId) throw new Meteor.Error('not-authenticated', 'You must be logged in to do this!')
// more stuff if user is logged in...
// ....
return 'some result';
}
});
You do not need to make a Meteor.loginWithPassword before executing the method. All you got to do is stub the this.userId by changing the this context of the method function call.
All defined meteor methods are available on the Meteor.methodMap object. So just call the function with a different this context
describe('Method: serverMethod', function(){
it('should error if not authenticated', function(){
var thisContext = {userId: null};
expect(Meteor.methodMap.serverMethod.call(thisContext).toThrow();
});
it('should return a result if authenticated', function(){
var thisContext = {userId: 1};
var result = Meteor.methodMap.serverMethod.call(thisContext);
expect(result).toEqual('some result');
});
});
EDIT: This solution was only tested on Meteor <= 1.0.x
What are you testing and why does it require a user to be logged in? Most of the methods I have that need a user object I pass the user object into. This allows me to call from a test without actually being logged in. So in the actual running of the code I would pass...
var r = myMethod(Meteor.user());
but when running from the test I would call like...
it('should be truthy', function () {
var r = myMethod({_id: '1', username: 'testUser', ...});
expect(r).toBeTruthy();
});
I think that Meteor.server.method_handlers["nameOfMyMethod"] allows you to call/apply a Meteor method and supply this as the first parameter at least in the current version (1.3.3)
this.userId = userId;
Meteor.server.method_handlers["cart/addToCart"].apply(this, arguments);

resetPassword issues in meteor

I sent enrollment email to the user and when he enters password and other details I'm trying to reset the password but it is throwing error
uncaught error extpected to find a document to change
As you can see in the mage
I've subscribed to the user record
my code
this.route('enroll', {
path: '/enroll-account/:token',
template: 'enroll_page',
onBeforeAction: function() {
Meteor.logout();
Session.set('_resetPasswordToken', this.params.token);
s = this.subscribe('enrolledUser', this.params.token).wait();
}
}),
After I'm displaying form and on the submit event
onSubmit: function(creds) {
var options = {
_id: Meteor.users.findOne()._id,
name: creds.name
}
var token=Session.get('_resetPasswordToken');
Meteor.call('updateUser', options, function(error, result) {
if(!error) {
Accounts.resetPassword(token, creds.password, function(error) {
if (error) {
toastr.error("Sorry we could not update your password. Please try again.");
return false;
}
else{
toastr.error("Logged In");
Router.go('/');
}
});
} else {
toastr.error("Sorry we could not update your password. Please try again.");
return false;
}
});
this.resetForm();
this.done();
return false;
}
Everything is working fine but resetpassword callback is not triggering and the above error is displaying in console.
my token is get deleted from the user record and I'm able to login using login form but
From the docs
Reset the password for a user using a token received in email. Logs the user in afterwards.
I'm not able to automatically login after resetting the password,above error is throwing
What am I missing here?
this.subscribe('enrolledUser', this.params.token).wait();
here you're subscribing using resetPassword token
when you call Accounts.resetPassword method the method will reset the password and delete the token from user record.
So your subscription is lost and there are no records available in client side to modify
(That is waht the error Expected to find a document to change)
Instead on first subscription save the user Id and subscribe to the user record using Id
so the subscription will not be lost
path: '/enroll-account/:token',
template: 'enroll_page',
onBeforeAction: function() {
Meteor.logout();
Session.set('_resetPasswordToken', this.params.token);
s = this.subscribe('enrolledUser', this.params.token).wait();
},
onAfterAction:function(){
if(this.ready()){
var userid=Meteor.users.findOne()._id;
Meteor.subscribe("userRecord",userid);
}
}
Alternatively, you could do something like as follows in your publication. This worked for me (but mine was a slightly more involved query than this).
Meteor.publish('enrolledUser', function (token) {
check(token, String);
return Meteor.users.find({
$or: [{
_id: this.userId
}, {
'services.password.reset.token': token
}]
});
});
From the docs, it says
Reset the password for a user using a token received in email. Logs the user in afterwards.
So basically, you have to subscribe to the logged in user after the fact as well. A little silly, but whatever.

Add extra user field

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).

Create a method not limited by subscriptions

I've created a method that checks if an email already has an account:
insertGroupMember: function(eventId, memberDetails) {
var memberAccount = Meteor.users.findOne({'emails.address': memberDetails.email});
if (memberAccount) {
console.log('Existing User')
} else {
console.log('Create User')
}
}
But will only receive a result when I am subscribed to a publication with all users.emails. How can I achieve the same results without having to publish everyone's email? I think thats kind of bad for security/privacy, right?
You are correct - you don't want to publish all of the users to the client just to accomplish this. The best solution is to create a method defined only on the server, and then call it from the client.
server/methods.js
Meteor.methods({
insertGroupMember: function(eventId, memberDetails) {
...
}
});
client/someTemplate.js
Meteor.call('insertGroupMember', eventId, memberDetails, function (err, result){
...
});

Resources