Adding more fields to Meteor user accounts - meteor

I am using mrt add accounts-ui-bootstrap-dropdown and mrt add accounts-password to get a simple login page running on my app.
The accounts users gives me a nice hash containing ids, createdAt, emails, etc.
If I wanted to add other fields in this hash so I can make use of them later, how would I do that? For example, I want then to also enter their given name and surname:
"given_name": "John", "surname": "Doe"

Users are special objects in meteor ; you don't want to add fields in the user but in the users profile.
From the doc :
By default the server publishes username, emails, and profile.
If you want to add properties like surname when you create the account, you should use in the Account.onCreateUser server-side hook : http://docs.meteor.com/#accounts_oncreateuser
Accounts.onCreateUser(function(options, user) {
//pass the surname in the options
user.profile['surname'] = options.surname
return user
}
If you want to update a user after, you can do it from the client that way :
Meteor.users.update({_id:Meteor.user()._id}, { $set: {what you want to update} });
By default, the users base will allow that (the current user may update itself). If you don't trust your users and want to ensure that everything is properly update, you can also forbid any updates from the client and make them via a Meteor.call() and proceed to the checkings server-side. But this would be sad.
Edit :
As said in the comments, adding options via the standard account-ui won't be possible. You'll only be able to update the user after the registration. To add options when you subscribe, you'll have to make you own form.
I won't insult you by writing html markup, but here is what you want to have after the submit event (and after the various checking) :
var options = {
username: $('input#username')[0].value,
emails: [{
address: $('input#email')[0].value,
verified: false
}],
password: $('input#password')[0].value,
profile: {
surname: $('input#surname')
},
};
Accounts.createUser( options , function(err){
if( err ) $('div#errors').html( err.message );
});
You only need the account-base package ; not the account-ui.
Login with the social networks is cake :
Meteor.loginWithFacebook({
requestPermissions: ['email', 'user_birthday', 'user_location']
}, function(error){loginCallBack(error);});
About the answer ram1 made :
This is not the way meteor works. You do not "POST" a form. You want all your client / server communication done via the websocket. The equivalent of what you are talking about is making a "Meteor.call('myserverfunction', myarguments, mycallback)" of a server method from the client and you pass the arguments you want the server to use.
But this is not the way you will get the best of meteor. There is the philosophy you want to work with :
you have datas in your local mini mongo you got from the server
you update locally those datas in your base / view
meteor do his magic to transmit those updates to the server
there the server can answer : ok, updates saved, this is seamless for you. Or answer : nop ! reverse the changes (and you can implement an error notification system)
(it can answer no because you don't have the permission to update this field, because this update break a rule you did set up...)
All you do is setting permissions and controls on the databases server-side. That way, when an honest client make an update, he sees the result instantly ; way before it has been pushed to the server and send to the other clients. This is latency compensation, one of the seven principles of meteor.
If you modify a data via Meteor.call, you will do that :
send an update to the server
the server checks and update the base
the server send the update to the clients (including you)
your local base updates and your view update => you see your update
=> this is what you had in yesterday app ; meteor allow you to build a today app. Don't apply the old recipes :)

The accepted answer has the HOW right, but the WHERE is outdated information. (Yes, this would be better as a comment on the answer, but I can't do that yet.)
From the Meteor 1.2 documentation:
The best way to store your custom data onto the Meteor.users collection is to add a new uniquely-named top-level field on the user document.
And regarding using Meteor.user.profile to store custom information:
🔗Don’t use profile
There’s a tempting existing field called profile that is added by
default when a new user registers. This field was historically
intended to be used as a scratch pad for user-specific data - maybe
their image avatar, name, intro text, etc. Because of this, the
profile field on every user is automatically writeable by that user
from the client. It’s also automatically published to the client for
that particular user.
Basically, it's probably fine to store basic information such as name, address, dob, etc in the profile field, but not a good idea to store anything beyond that as it will, by default, be writeable by the client and vulnerable to malicious users.

I had the same problem and managed to do it only with Accounts.createUser:
Accounts.createUser({
email: email,
password: password,
profile: {
givenName: 'John',
surname: 'Doe',
gender: 'M'
}
}
Thats very simple way and it works. Just add your desired variables in the profile section and it should be ready. Hope it helps someone.

I ended up using https://atmospherejs.com/joshowens/accounts-entry which offers an extraSignUpFields config option.

From the documentation (https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3/blob/master/README.md):
Custom signup options
You can define additional input fields to appear in the signup form, and you can decide wether to save these values to the profile object of the user document or not. Specify an array of fields using Accounts.ui.config like so:
Accounts.ui.config({
requestPermissions: {},
extraSignupFields: [{
fieldName: 'first-name',
fieldLabel: 'First name',
inputType: 'text',
visible: true,
validate: function(value, errorFunction) {
if (!value) {
errorFunction("Please write your first name");
return false;
} else {
return true;
}
}
}, {
fieldName: 'last-name',
fieldLabel: 'Last name',
inputType: 'text',
visible: true,
}, {
fieldName: 'gender',
showFieldLabel: false, // If true, fieldLabel will be shown before radio group
fieldLabel: 'Gender',
inputType: 'radio',
radioLayout: 'vertical', // It can be 'inline' or 'vertical'
data: [{ // Array of radio options, all properties are required
id: 1, // id suffix of the radio element
label: 'Male', // label for the radio element
value: 'm' // value of the radio element, this will be saved.
}, {
id: 2,
label: 'Female',
value: 'f',
checked: 'checked'
}],
visible: true
}, {
fieldName: 'country',
fieldLabel: 'Country',
inputType: 'select',
showFieldLabel: true,
empty: 'Please select your country of residence',
data: [{
id: 1,
label: 'United States',
value: 'us'
}, {
id: 2,
label: 'Spain',
value: 'es',
}],
visible: true
}, {
fieldName: 'terms',
fieldLabel: 'I accept the terms and conditions',
inputType: 'checkbox',
visible: true,
saveToProfile: false,
validate: function(value, errorFunction) {
if (value) {
return true;
} else {
errorFunction('You must accept the terms and conditions.');
return false;
}
}
}]
});

The official Meteor Guide provides a comprehensive answer with an example code:
The best way to store your custom data onto the Meteor.users collection is to add a new uniquely-named top-level field on the user document.
https://guide.meteor.com/accounts.html#custom-user-data

Related

meteor autocomplete server-side

I'm writing a meteor app and I'm trying to add an autocomplete feature to a search box. The data is very large and is on the server, so I can't have it all on the client. It's basically a database of users. If I'm not wrong, the mizzao:autocomplete package should make that possible, but I can't seem to get it to work.
Here's what I have on the server:
Meteor.publish('autocompleteViewers', function(selector, options) {
Autocomplete.publishCursor(viewers.find(selector, options), this);
this.ready();
});
And here are the settings I use for the search box on the client:
getSettings: function() {
return {
position: 'bottom',
limit: 5,
rules: [{
subscription: 'autocompleteViewers',
field: '_id',
matchAll: false,
options: '',
template: Template.vLegend
}],
};
}
But I keep getting this error on the client:
Error: Collection name must be specified as string for server-side search at validateRule
I don't really understand the problem. When I look at the package code, it just seems like it's testing whether the subscription field is a string and not a variable, which it is. Any idea what the problem could be? Otherwise is there a minimum working example I could go from somewhere? I couldn't find one in the docs.
Error: Collection name must be specified as string for server-side search at validateRule
You get this error because you don't specify a Collection name in quotes.
getSettings: function() {
return {
position: 'bottom',
limit: 5,
rules: [{
subscription: 'autocompleteViewers',
field: '_id',
matchAll: false,
collection: 'viewers', // <- specify your collection, in your case it is a "viewers" collection.
options: '',
template: Template.vLegend
}],
};
}
For more information please read here.
Hope this helps!

Meteor: publish some user data

I want to publish some limited user information about my users, the idea is that the admin role of my web app can view the emailaddress and username (last one is in the profile data).
Meteor.publish("usersSpecificDataforAdmin", function () {
return Meteor.users.find({}, {fields: {
'profile': 1,
'emails': 1,
'roles': 1
}});
});
I'm then subscribing to this in my router:
adminRoutes.route('/users', {
name: 'adminUsersList',
subscriptions: function (params, queryParams) {
this.register('adminUsersList', Meteor.subscribe('usersSpecificDataforAdmin'));
},
action: function (params, queryParams) {
BlazeLayout.render('layout_frontend', {
top: 'menu',
main: 'adminUsersList',
footer: 'footer'
});
}
});
In the template, I'm using the following to display the email address of the user: '{{emails.address}}', but that doesn't work. I can display all other info.
I have following questions:
how can I display the email address of the user in the template
even when I don't add the password or services fields in the publishing, it is send to the client (doing Meteor.user()) is revealing all the info, including passwords etc, which is a security issue in my opinion. How can I disable the publication of this?
Several things:
You don't need to include _id in the list of fields to be published, it is always included
You're publishing allUserData but your router code is subscribing to usersAllforAdmin which you're not showing code for. I suspect that publication is including services
Passwords are not stored anywhere in Meteor, only the bcrypt hash of the password is stored in services
emails is an array, you can't access it with {{emails.address}} in spacebars, instead use {{emails.[0].address}} (reference)

Meteor Accounts-Entry how to prevent an extraSignupField from being stored to the database?

I'm using Meteor's account-entry package to handle the signin-signup action of my web app. To add a Confirm Password field to the sign up form, this is what I've done (in CoffeeScript):
AccountsEntry.config
logo: '/logo.png'
homeRoute: 'main'
dashboardRoute: 'main'
profileRoute: '/profile'
extraSignUpFields: [
field: "confirmPassword"
label: "Confirm Password"
type: "password"
,
field: "name"
label: "Full Name"
placeholder: "Full Name"
type: "text"
required: true
,
field: "position"
label: "Position"
placeholder: "Developer"
type: "text"
]
The problem with this approach is that: it also save the confirmPassword field to the database, so that when someone access the database > users collection, they can clearly see every users' password in confirmPassword field - which is very bad.
I don't know how to fix this problem yet. I think there may be an attribute which decide whether a specific field should be store in the database or not, but I haven't figured it out yet ! (the accounts-entry package documentation seems not detailed enough to me, I have to say :( )
Can you guys help me with this problem ? Thanks so much in advance !
The lack of a password confirmation field is a known issue with accounts-entry.
On the other hand, the publish function for the users collection should only publish the strictly necessary fields. By default, only username, emails and profile are published to the client.
Anyway, you should not store the confirmPassword in the database to begin with. To do that, hook into Accounts.onCreateUser and delete that field before returning the user object:
Accounts.onCreateUser(function (options, user) {
delete user.confirmPassword; // or: delete user.profile.confirmPassword;
return user;
});

Meteor collection2 deny rules : grant full permissions to the server

I have a user collection with some deny update rules :
// The roles object
Schema.roles = new SimpleSchema({
maker: {
type: Boolean,
denyUpdate: true
},
admin: {
type: Boolean,
denyUpdate: true
}
});
Those datas are in the user profile. And obviously, I don't want the random user to be able to modify profile.roles.admin. But the admin user should be able to.
It works partially : the user cannot modify this boolean. But it should be possible to modify it from the following server side code.
Meteor.users.update({_id: targetID'}, {$set: {'profile.roles.admin': true}});
Is there a way to tell collection2 to trust the code from the server ?
EDIT : the answer
Thanks to the answer below, here's the code I use now for my schema :
admin: {
type: Boolean,
autoValue: function() {
// If the code is not from the server (isFromTrustedCode)
// unset the update
if(!this.isFromTrustedCode)
this.unset();
}
}
The isFromTrustedCode boolean tell if the code should be trusted. Simple. By the way, the autoValue option return a complete object about the update (or insert or set or upsert) action. Here are the parametters :
isSet: true
unset: [Function]
value: true
operator: '$set'
field: [Function]
siblingField: [Function]
isInsert: false
isUpdate: true
isUpsert: false
userId: null
isFromTrustedCode: true
So it is possible to have a really fine-grained management of the writing rights rules.
As provided in the official documentation, you can bypass validation using a simple option:
To skip validation, use the validate: false option when calling insert or update. On the client (untrusted code), this will skip only client-side validation. On the server (trusted code), it will skip all validation.
But if you want more fine-grained control, instead of using a denyUpdate, you can use a custom validation type which has a this context with a isFromTrustedCode property which is true when called on the server.

Extjs 4 - Retrieve data in json format and load a Store. It sends OPTION request

I'm developing an app with Spring MVC and the view in extjs 4. At this point, i have to create a Grid which shows a list of users.
In my Spring MVC controller i have a Get method which returns the list of users in a jsonformat with "items" as a root.
#RequestMapping(method=RequestMethod.GET, value="/getUsers")
public #ResponseBody Users getUsersInJSON(){
Users users = new Users();
users.setItems(userService.getUsers());
return users;
}
If i try to access it with the browser i can see the jsondata correctly.
{"items":[{"username":"name1",".....
But my problem is relative to request of the Ext.data.Store
My Script is the following:
Ext.onReady(function(){
Ext.define('UsersList', {
extend: 'Ext.data.Model',
fields: [
{name:'username', type:'string'},
{name:'firstname', type:'string'}
]
});
var store = Ext.create('Ext.data.Store', {
storeId: 'users',
model: 'UsersList',
autoLoad: 'true',
proxy: {
type: 'ajax',
url : 'http://localhost:8080/MyApp/getUsers.html',
reader: {type: 'json', root: 'items'}
}
});
Ext.create('Ext.grid.Panel',{
store :store,
id : 'user',
title: 'Users',
columns : [
{header : 'Username', dataIndex : 'username'},
{header : 'Firstname', dataIndex: 'firstname'}
],
height :300,
width: 400,
renderTo:'center'
});
});
When the store tries to retrieve the data and launchs the http request, in my firebug console appears OPTIONS getUsers.html while the request in the browser launchs GET getUsers.html
As a result, Ext.data.Store has not elements and the grid appears with the columnames but without data. Maybe i've missed something
Thank you
You can change the HTTP methods that are used by the proxy for the different CRUD operations using actionMethods.
But, as you can see in the doc (and as should obviously be the case), GET is the default for read operations. So the OPTIONS request you are observing is quite puzzling. Are you sure that there's not another part of your code that overrides the default application-wide? Maybe do a search for 'OPTIONS' in all your project's JS files, to try and find a possible suspect. Apparently there's no match in the whole Ext code, so that probably doesn't come from the framework.
Edit:
Ok, I think I've got it. If your page is not accessed from the same domain (i.e. localhost:8080, the port is taken into account), the XHR object seems to resort to an OPTIONS request.
So, to fix your problem, either omit the domain name completely, using:
url: '/MyApp/getUsers.html'
Or double check that your using the same domain and port to access the page and make the requests.

Resources