I am using SimpleSchema (the node-simpl-schema package) in an isomorphic way. Validation messages show up on the client as well as from meteor shell.
My question is whether or not this set up is actually secure, and if I need to also write allow/deny rules.
For example:
SimpleSchema.setDefaultMessages
messages:
en:
"missing_user": "cant create a message with no author"
MessagesSchema = new SimpleSchema({
content: {
type: String,
label: "Message",
max: 200,
},
author_id: {
type: String,
autoform:
defaultValue: ->
Meteor.userId()
custom: ->
if !Meteor.users.findOne(_id: #obj.author_id)
"missing_user"
},
room_id: {
type: String,
}
}, {tracker: Tracker})
In meteor shell I test it out and it works as intended.
> Messages.insert({content: "foo", author_id: "asd"})
/home/max/Desktop/project/meteor/two/.meteor/local/build/programs/server/packages/aldeed_collection2-core.js:501
throw error; // 440
^
Error: cant create a message with no author
Should I duplicate this validation logic in my allow/deny rules? Or can I let my allow function always return true, like I'm doing now?
I have some very simple rules that ensures the application is secure:
Do not use allow/deny rules - deny all client-side write requests.
If the client needs to write something in the database, they must do so through Meteor methods.
Ideally, the Meteor methods would call a function (which can be shared code, or server-specific code), and then check for the validity of the database modifier (using the Schema) would be done inside these functions.
Optionally, you can also create client-side methods, which would clean the object and carry out its own validation using the schema before calling the server-side method.
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;
});
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
I am using the 'accounts-base' and 'accounts-password' packages and the Accounts.createUser method to create users from a login form (i.e. I am not using the accounts-ui package).
the documentation explains that the user thus created includes a 'services' object
"containing data used by particular login services. For example, its
reset field contains tokens used by forgot password links, and its
resume field contains tokens used to keep you logged in between
sessions."
This is true and accounts created using my login form all have loginTokens. However, when I refresh the browser, these tokens are deleted and the user is logged-out.
The documentation appears to suggest that resume tokens are handled automatically by the accounts-base / accounts-password packages. What have I missed?
Accounts.createUser({
username: username,
email: username,
password: password
}, function (err) {
if (err) {
alert(err)
} else {
Router.go('/member/' + Meteor.userId() +'/edit')
}
});
creates:
"resume" :
{ "loginTokens" :
[
{
"when" : ISODate("2014-04-17T22:13:50.832Z"),
"hashedToken" : "KstqsW9aHqlw6pjfyQcO6jbGCiCiW3LGAXJaVS9fQ+o="
}
]
}
...but on refresh:
"resume" : { "loginTokens" : [ ] } },
After an exhaustive audit of my code I found that I was (idiotically) invoking the Accounts.logout method outside the confines of the log-out button event. It had somehow become 'orphaned' during an earlier re-factoring of the code
So all my fault.
I have a custom user table for managing users.
User:
connection: doctrine
tableName: user
columns:
user_login:
type: string(50)
notnull: true
primary: true
user_pass:
type: string(100)
notnull: true
after user click login with login form, username and password is checked against the database. If it is matched the user is set as authenticated with below line of code..
$this->getUser()->setAuthenticated(true);
Now how would I set the credential of the user using the following function? and is it necessary?
$this->getUser()->addCredential($WHAT ARE_THE_VALUES_THIS_ARRAY_SHOULD_CONTAINS);
what are the values should be in argument of the above method? Please explain more about this.
It's up to you whether to use credentials or not. Credentials just unique strings cached in the session.
$this->getUser()->addCredentials(array('admin', 'user', 'chief', 'asd'));
// or
$this->getUser()->addCredentials('admin', 'user', 'chief', 'asd');
For mode examples look at the tests and/or the sfDoctrineGuardUser plugin.
You can use credentials to secure actions, but it's in the docs.