I have a simple little user registration form that looks like this:
// POST Register new user
exports.new = function(req, res) {
var db = require('mongojs').connect('localhost/busapp', ['users']);
db.users.ensureIndex({email:1}, {unique: true})
function User(email, username, password, dateCreated) {
this.email = email;
this.username = username;
this.password = password;
this.dateCreated = new Date();
this.admin = 0;
this.activated = 0
}
if (req.body.user.password !== req.body.user.passwordc) {
res.send('Passwords do not match');
} else {
var user = new User(req.body.user.email, req.body.user.username,
req.body.user.password);
// TODO: Remove this after we clarify that it works.
console.log(user.email + " " + user.username + " " +
user.password);
// Save user to database
db.users.save(user, function(err, savedUser) {
if (err) {
res.send(err);
} else {
console.log("User " + savedUser.email + " saved");
}
});
}
}
But I'm having trouble validating information submitted, like unique values, is empty, that sort of thing, so nobody can send post requests to the database to bypass the jQuery validation functions. I've read through the docs but I cannot seem to get it right. I tried setting a ensureIndex, but, that doesn't seem to work. Any information on how to validate the input on the database side would be great thanks!
One of the strengths/features of MongoDB is flexible schema. MongoDB does not impose any specific contraints on fields types. In general with web applications, you should try to do validation as early as possible .. so first at the client (JavaScript) level, then the application, and as a last resort in the database server.
MongoDB validation
MongoDB can do a limited amount of validation such as ensuring a unique index. Any data validation such as required fields or field types (string, integer, ..) should be done in your application code.
Clientside/application validation
You could use jQuery validation, but that would only be effective in the client (browser view). Any validation should also be done in your application code/model, otherwise disabling JavaScript in the browser would be a simple way to insert invalid data.
why cant you do stuff like password != "". as for unique values you should do use the find or findOne functions to see if that name exists in the db.
i would highly recommend installing mongoose. it is really useful as it allows you to create schemas. so if you are familiar with MVC, in your models, you would have user.js which contains the schema for the user. basically it gives guidelines on how the user object will be stored in the database. in your controllers, you would try to do what you are doing in the code you have above. you would do a user = require(user.js) and then you would do user.find() or user.findOne() to find that thing in the database. for example. if the username was already in the database, then its not unique. so dont add him.
Related
As part of my studies I need to develop web-app (javasript language) Where we work with firebase realtime database.
currently inside the database,I have a tree of users objects that representing all the users who registered to the system. And what I'm trying to do is a simple user login function.
After the user entered his username and his password, I created an array to enter the entire user tree from a database. The problem is that when im calling the function from the Firebase it's not enough to ended.. and what happens is that the array remains empty and you can not verify that the user is registered on the system.
now I have used a temporary solution that Im using setTimeout function,I understand that this is wrong programming, and also i do not want the user to wait 2 seconds every time he wants to login to the system.
Can someone please help me? how to do it right without the setTimeout function?
I want that the function of the Firebase ends so only then start with the Authentication process.
Here is the code I wrote so far,
var correntUser;
var userlist = [];
var usersRef = database.ref('users');
// Query that inserts all users keys and names to an array.
usersRef.orderByChild("username").on("child_added", function(snapshot)
{
userlist.push({userKey:snapshot.key,username:snapshot.val().username,password:snapshot.val().password});
});
setTimeout(function()
{
//check if user exist in userlist.
for(var i=0; i<userlist.length;i++)
if (userlist[i].username == usernameArg && userlist[i].password == passwordArg)
correntUser = userlist[i].userKey;
if(correntUser == undefined)
{
//check if undefined
alert("wrong username or password");
document.getElementById("username").value = "";
document.getElementById("password").value = "";
return;
}
mainPage.addHeader();
},2000);
thank you all.
There is no need to manually check all users and see if there is a match with the attempted login credentials when Firebase provides built-in authentication. Password-based accounts require email addresses, although you can combine the username with any domain name to satisfy that requirement as suggested here.
You did not explain what your database structure looks like under the users path, but one way to handle that is to incorporate the user's unique id that gets returned as part of the createUserWithEmailAndPassword password:
function createAccount(email, password) {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(function(userData) {
// account created successfully
usersRef.child(userData.uid).set({
email: email,
creation_date: new Date(),
...
})
}
.catch(function(error) {
...
})
}
Then for login attempts:
function login(email, password) {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function(userData) {
// login successful
mainPage.addHeader();
})
.catch(function(error) {
alert("wrong username or password");
document.getElementById("username").value = "";
document.getElementById("password").value = "";
})
}
I'm working on making a real-time application for my website in NodeJS, allowing my users to log in with their accounts, etc.
However, I am having some issues with the logging in part.
When I register/login the users on the main site, I hashed their password using PHP's hash() function like so:
$passwordSalt = mcrypt_create_iv(100);
$hashed = hash("sha256", $password.$passwordSalt.$serverSalt);
and it works great on my site
However I need to be able to grab the user's salt from the database in NodeJS and be able to hash the user's inputted password, check it against the database's password, and make sure they match to log the user in.
I do this by doing something like this:
//Check if Username exists, then grab the password salt and password
//Hash the inputted password with the salt in the database for that user
//and the salt I used for $serverSalt in PHP when creating passwords
//check if hashed result in NodeJS is equal to the database password
function checkPass(dbPassword, password, dbSalt){
var serverSalt = "mysupersecureserversalt";
var hashed = crypto.createHash("sha256").update(password+dbSalt+serverSalt).digest('hex');
if(hashed === dbPassword)
return true;
return false;
}
However, when I console.log() the hashed variable and the dbPassword variable, they're not equal - so it always return false/responds with incorrect password.
So, my question:
Is there any way I can accurately hash a sha256 string in NodeJS the same way I do in PHP?
PS:
For now I am using Ajax/jQuery to login via PHP Script but I want to be able to completely move from Apache/PHP hosting to the site just being hosted with NodeJS (SocketIO, Express, MySQL).
I just started working with NodeJS recently and I have made functionality on my Apache site to work with NodeJS, but I heard that hosting the whole site itself using NodeJS would be a lot better/more efficient.
Edit:
So, I decided to make a quick test.js without using the database/socketio/express.
var crypto = require("crypto");
var serverSalt = "";
var passwordSalt = ""; //The salt directly copied from database
var checkPassword = "password123"+passwordSalt+serverSalt; //All added together
var password = ""; //The hashed password from database
var hashed = crypto.createHash("sha256").update(checkPassword).digest('hex');
console.log(password);
console.log(hashed); //This doesn't match the hash in the database
if(password == hashed){
console.log("Logged in!");
} else {
console.log("Error logging in!");
}
As for how I'm connecting to the database, I'm doing so:
connection.query("SELECT password,passwordSalt FROM users WHERE username = "+connection.escape(data.username), function(err,res){
if(err){console.log(err.stack);socket.emit("message", {msg:"There was an error logging you in!", mType:"error"});}else{
if(res.length != 0){
var dbSalt = res[0]['passwordSalt'];
var serverSalt = ""; //My server salt
var dbPassword = res[0]['password'];
var checkPassword = data.password+dbSalt+serverSalt;
console.log("CheckPass: "+checkPassword);
var hashed = crypto.createHash("sha256").update(checkPassword).digest('hex');
console.log("Hashed: "+hashed);
if(hashed === dbPassword){
console.log("Worked!");
socket.emit("message", {msg: "Logged in!", type:"success"});
} else {
console.log("Error logging in!");
socket.emit("message", {msg: "Your password is incorrect!", type:"error"});
}
} else {
socket.emit("message", {msg: "That user ("+data.username+") doesn't exist!", mType:"error"});
}
}
});
MySQL version: 5.5.44-0+deb7u1 (Debian)
The column the password salt is stored in is type of text, and has a collation of utf8_unicode_ci
Note: When I change
var hashed = crypto.createHash("sha256").update(checkPassword).digest('hex');
To:
var hashed = crypto.createHash("sha256").update(checkPassword, "utf8").digest('hex');
The hashes are different, however, the hashed variable doesn't match the database password still.
TL;DR
2 possibilities:
The ideal solution: change the database field for the salt from TEXT to BLOB.
The compromise: cast the TEXT to binary latin1 using:
BINARY(CONVERT(passwordSalt USING latin1)) as passwordSalt
Then in both cases, use Buffer values everywhere:
var hashed = crypto.createHash("sha256").update(
Buffer.concat([
new Buffer(password),
dbSalt, // already a buffer
new Buffer(serverSalt)
])
).digest('hex');
It's tested and working.
The longer version
And of course the culprit is character encoding, what a surprise. Also a terrible choice on your part to store raw binary to a TEXT field.
Well, that was annoying to debug. So, I set up a MySQL table with a TEXT field and a BLOB field and stored the output of mcrypt_create_iv(100) in both. Then I made the same queries from both PHP and NodeJS.
PHP presents both values as identical.
JavaScript presents 2 different values.
In both cases, the BLOB was accurate and I even managed to get the proper hash under JavaScript by using Buffer values for all 3 components of the input.
But this did not explain why PHP and JavaScript were seeing 2 differents values for the TEXT field.
The TEXT value had a length of 143 octets.
The BLOB had a length of 100 octets.
Clearly the BLOB was correct and the TEXT wasn't, yet PHP didn't seem bothered by the difference.
If we look at the MySQL connection status under PHP:
$mysqli->get_charset();
Partial output:
[charset] => latin1
[collation] => latin1_swedish_ci
Unsurprising really, it is notorious that PHP operates under ISO-8859-1 by default (or latin1 in MySQL), which is why both values where the same there.
For some reason, it seems that setting the charset in the MySQL module for NodeJS doesn't work, at least for me. The solution was to convert at the field level and preserve the data by casting to BINARY:
BINARY(CONVERT(passwordSalt USING latin1)) as passwordSalt
This returned exactly the same output as the BLOB.
But this is not enough yet. We have a mixture of strings and binary to feed to the hashing function, we need to consolidate that. We cast the password and the server salt to Buffer and concatenate:
var hashed = crypto.createHash("sha256").update(
Buffer.concat([
new Buffer(password),
dbSalt, // already a buffer
new Buffer(serverSalt)
])
).digest('hex');
This returns the same output as PHP.
While this works, the best solution is still to use BLOB in the database. In both cases, casting to Buffer is necessary.
I am building an application that has two types of users. Professional users and their clients. The account types are completely distinct with no overlap. So, emails registered as Pro could still be used to register as a Client, and if a Pro user tried to log in using the client form, their account would not exist.
Is there any way I can create two distinct user sets using the meteor accounts package? I have tried to add an attribute like 'user.isPro' and then check if the Pro user exists when a user is created, like this:
Accounts.onCreateUser(function(options, user) {
var email = user.email;
if (Meteor.users.find({emails: email, isPro : true}).count() > 0) {
throw new Meteor.Error(403, "This email address is already registered");
}
user.isPro = true;
return user;
});
This is not working. It assigns user.isPro correctly, but meteor prevents duplicate emails from being registered instead of using the validation I created above. Any ideas on how I can achieve two distinct user sets? Thanks for your help!
To use an example that demonstrates the question, assume I have a User model defined by the following schema:
var UserSchema = new Schema({
username: String,
email: String
}
mongoose.model('User', UserSchema);
I know that to update a user using the save method, I could query the user and then save changes like so:
User.findOne({username: usernameTofind}, function(err, user) {
//ignore errors for brevity
user.email = newEmail;
user.save(function(err) { console.log('User email updated') });
});
But if I try to create a new User object with the exact same field values (including the _id) is there any possibility of overwriting the database document? I would assume not, because in theory this would mean that a malicious user could exploit an insecure api and overwrite existing documents (for instance using a 'Create a New Account' request, which wouldn't/couldn't rely on the user already being authenticated) , but more importantly, when I try to do this using a request tool (I'm using Postman, but I'm sure a similar curl command would suffice), I get a duplicate _id error
MongoError: insertDocument :: caused by :: 11000 E11000 duplicate key error index
So I just want to clarify that the only way to update an existing document is to query for the document, modify the returned instance, then call the save method on that instance, OR use the static update(). Both of these could be secured by requiring authentication.
If it helps, my motivation for this question is mentioned above, in that I want to make sure a user is not able to overwrite an existing document if a method such as the following is exposed publicly:
userCtrl.create = function(req, res, next) {
var user = new User(req.body);
user.save(function(err) {
if (err) {
return next(err);
} else {
res.json(user);
}
});
};
Quick Edit: I just realized, if this is the case, then how does the database know the difference between the queried instance and a new User object with the exact same keys and properties?
Does modelObject.save() only update an existing database document when
the modelObject was obtained from the database itself?
Yes, it does. There is a flag that indicates if the document is new or not. If it is new, Mongoose will insert the document. If not, then it will update the document. The flag is Document#isNew.
When you find a document:
User.findOne({username: usernameTofind}, function(err, user) {
//ignore errors for brevity
console.log(user.isNew) // => will return false
});
When you create a new instance:
var user = new User(req.body);
console.log(user.isNew) // => will return true
So I just want to clarify that the only way to update an existing
document is to query for the document, modify the returned instance,
then call the save method on that instance, OR use the static
update(). Both of these could be secured by requiring authentication.
There are other ways you can update documents, using Model#update, Model.findOneAndUpdate and others.
However, you can't update an _id field. MongoDB won't allow it even if Mongoose didn't already issue the proper database command. If you try it you will get something like this error:
The _id field cannot be changed from {_id: ObjectId('550d93cbaf1e9abd03bf0ad2')} to {_id: ObjectId('550d93cbaf1e9abd03bf0ad3')}.
But assuming you are using the last piece of code in your question to create new users, Mongoose will issue an insert command, so there is no way someone could overwrite an existing document. Even if it passes an _id field in the request body, MongoDB will throw a E11000 duplicate key error index error.
Also, you should filter the fields a user can pass as payload before you use them to create the user. For example, you could create a generic function that receives an object and an array of allowed parameters:
module.exports = function(object, allowedParams) {
return Object.keys(object).reduce(function(newObject, param) {
if (allowedParams.indexOf(param) !== -1)
newObject[param] = object[param];
return newObject;
}, {});
}
And then you only require and use the function to filter the request body:
var allow = require('./parameter-filter');
function filter(params) {
return allow(params, ["username", "email"]);
}
var user = new User(filter(req.body));
I'm trying to use the Meteor Roles package: https://github.com/alanning/meteor-roles
to obviously create a new field in user model.
The user is created no problem but the 'roles' field I'm trying to define isn't created. I can add things like 'Profile' and details within that too. But for some reason I can't make a roles field. Here's my form:
Template.signup.events({
'submit #signup-form' : function(e, t) {
e.preventDefault();
var roles = ['admin'],
email = t.find('#email').value,
password = t.find('#password').value;
Accounts.createUser({email: email, password : password, roles: roles}, function(err){
if (err) {
alert("User Not Added")
} else {
console.log("User Added.")
}
});
}
});
Eventually I'll need to publish this to the client but for right now I just want the field to show in MongoDb, which it's not.
3 things:
I feel like the code above should work but I'm clearly missing something
In the package docs it mentions this Roles.addUsersToRoles which I
tried but no luck
Or do I need to possibly update the record, after it's been created?
I did go into the DB and manually added the field and associated string to update it (with $set) and it worked. But from the form itself though, no luck.
Any pointers would be much appreciated. Thank you.
The Accounts.createUser function only lets you add arbitrary user properties via the profile option which is where they end up getting stored in mongo. That is why Meteor is ignoring the roles: roles part of your Accounts.createUser call.
It is true that the meteor-roles package stores the list of roles assigned to a user directly in the users collection, but that is almost just an implementation detail and you are probably best off sticking to the API that meteor-roles provides for adding users to a role:
Roles.addUsersToRoles(<userId>,[<list of roles>])
The userId passed to Roles.addUsersToRoles is the value returned by Accounts.createUser when its called on the server which is probably where you want to be doing this as that feels way more secure.
The Accounts.createUser function only takes username, email, password and profile as params for the user object. See the documentation here. So, to add another field to a new user object, you need to add it in a second step:
var uid = Accounts.createUser({email: email, password: password});
Meteor.users.update(uid, {$set: {roles: roles}});