Trying to think of a logical way of structuring this. For simplicity, I am creating a user registration page utilising Node.js, Redis and Express.js.
User posts details to page.
Username is confirmed to be valid then Redis checks username is unique. If it is, we continue, if it isn't we return an error message and all the previous details.
Password is confirmed to be valid. If it isn't an error is returned and we don't continue.
Email is confirmed to be unique with Redis. If it is we continue, if it isn't we return an error message and stop.
If no errors at this point, the data is inserted into Redis.
This seems very simple, but using callbacks has generated a total mess - particularly when it comings to returning an error.
How can I structure this in a tidy way?
What you've experienced is callback hell. There are a variety of things you could do like name your callback functions instead of inlining them, follow the same pattern etc...
However, I would recommend you have a look at the async module.
In your, very typical, case I would use async.series like this:
var validateUserName = function(username, callback){
if(username!='valid'){
return callback('invalid username');
}
};
var checkRedis = function(username, callback){
// check to redis
};
var checkPassword = function(password, callback){
// if password valid callback();
// else callback('invalid password');
}
etc...
async.series([checkUserName, checkRedis, checkPassword, etc...], next);
Related
I'm sorry, I know this is pretty basic but I just don't understand this! I've just gotten done doing JS for two years and now I wanna learn node, so far nothing has troubled me but this code:
userSchema.pre("save", async (next) => {
let user = this;
if (user.isModified("password")) {
user.password = await bcrypt.hash(user.password, 9);
}
next();
});
My first question is what exactly does that isModified function do? Intuitively it makes most sense for "user.isModified("password")" to return true if in the middleware chain, from the first time we got the request object, the property 'password' was modified.
Is that right? And even if it is, I dont understand how will that property change, the only way user's password property is changing is if we do it explicitly and in our case the only time we'll do that is if we want to hash the password.
My second question is, why do we have to hash password in the hooks, pre in this case? And with the help of .isModified method? I'm following three instructors and all of them did it this way but no one really explained the use of it behind the scene.
FindByIdAndReplace, findByIdAndUpdate etc also experience the 'save' event but we dont want any hashing there so why we taking the risk of putting our hashing code in pre save hook? I'm doing it this way and it seems to be working fine:
app.post("/signup", async function (req, res) {
let { email, password } = req.body;
let user = await Bank.create({ email, password });
user.password = await bcrypt.hash(password, 11);
await user.save();
});
What's wrong with the code above?
Sorry if this is long but I really had to ask this, I've already spent weeks on this but I just dont get it!
I’m quite new to backend development…
With using my API I would like to be able to display a list of users and also indicate if they are currently logged in. I got the basic authentification working using passport and json web token
I’m not looking to get the current logged in user.
I want to be able to retrieve a list of users and see if they are logged in or not.
Like this:
var users = Users.find({});
// console.log(users) output:
{
name: 'foo'
password: ...
isLoggedIn: false
},
{
name: 'bar'
password: ...
isLoggedIn: true
},
{
name: 'baz'
password: ...
isLoggedIn: false
}
isLoggedIn would be set to true if the user is currently logged in and to falseif not.
How can I do that? Thank you!
It sounds like what you would like to do is update your MongoDB database based on login/logout events. To do this you could use something like mongoose to work with your Node backend to easily access your database in MongoDB.
You can include mongoose after installing with npm install mongoose like so:
var mongoose = require('mongoose');
var User = mongoose.model('User');
Note that User corresponds to whatever schema you create for storing user information.
Assuming you have some sort of router object for handling requests, you could construct route handlers for /logout and /login and use your imported mongoose User model to retrieve and then modify a specific User object as such:
// whenever user goes to '/login' (you can have, say, your 'login' button make a request to this URL
router.get('/login', function(req,res) {
// your authentication here; passport stores the currently authenticated user in req.user
var username = req.user.name; // here we assume the username is stored as 'name' as you have in your code but change this based on your schema
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = true;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to some page here maybe
}
});
});
});
// whenever user goes to '/logout' (you can have a logout button make a request to this URL
router.get('/logout', function(req,res) {
// currently authenticated user is still in req.user
var username = req.user.name;
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = false;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to login/register page maybe
}
});
});
});
So to summarize what this code would do:
based on the url a user would go to, our route handler would fetch one correct, unique User object from our database based on the name (username)
it would do so by accessing the username property of req.user which corresponds to the currently authenticated user with Passport, which, again will be different for all users
update the field that we use to keep track of login status (isLoggedIn)
and then save the changes, after which we are done updating the state to reflect whether the user is logged in or not, so we can now redirect to some other page or display other content
Finally then, you could retrieve a list of all users similarly to your code like so:
User.find({}, function(err, users, data) {
// all users from your database are in `users`
console.log(users);
});
Edit for expired sessions:
So, to track expired sessions, since you're using Passport, would in theory require functionality to signal with some sort of event / callback / message, etc. the moment the session is deemed invalid. Now that is tough to monitor and from my experience with Passport, stuff like that isn't implemented in all authentication strategies and might vary based on the strategy to be used by developers (think for instance if a browser window is closed, based on Passports authentication strategy, or just browser, it might destroy the cookie for the session right away and our server has no way of knowing about it). I do recommend checking out all the authentication strategies Passport offers in case there are some better ones here.
Now, if you would like to add functionality to track the users passive login/logout status with sessions yourself, you could use something related to cookies. Again, not necessarily one to use, but here's a couple handy Express modules: cookie-parser and cookie-session.
Then, you could set and read cookies like this, using cookie-parser:
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
You would put this code somewhere right after the user is authenticated:
// cookies are stored here
console.log(req.cookies);
// configure your cookie
var options = {
expires: 1000 * 60 * 60, // expires after one hour
httpOnly: true
}
// Set cookie
res.cookie('session', ('user-' + req.user.name), options);
And then, on the client side check if that cookie is valid continuously on some time interval, and if it expired Date.now() > cookie.expires then make a GET request to /logout, and there log out the user (currently still authenticated) by updating MongoDB and all.
However, since this would require making a mechanism to basically simulate an expired session, I would recommend using something analogous to a timeout, which would be much easier to implement. Just a note, this is sort of analogous to mechanisms on some pages you might have encountered where you get a pop-up saying 'You will be logged out due to inactivity'. In your main.js or whatever client-side script define a function to keep going on a time-out, unless the user does some action.
var inactivity = function () {
var t;
// user doing something on your page, so keep resetting time counter when events happen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
// this is a callback function that will get called once a time-out countdown is done
function timeOut() {
// make a request to '/logout' here and logout the current user (you still will have access to req.user from Passport)
// also can redirect from back-end route handler to the login page for instance
}
// this gets called whenever an event happens, resetting the counter of sorts
function resetTimer() {
t = 0;
t = setTimeout(timeOut, 1000 * 60 ) // set this to however long you should wait to log out your user time (in milliseconds)
}
};
So basically what this approach would let you do, is automatically invalidate sessions yourself, which means you would have much greater control over updating the state of your database and logging users out.
Hope this helps!
I'm currently develop an application with Sails.JS.
I want to count the number of online users and update it once they login/ logout or there session expire, but I don't know how to implement something like session destroyed event and can't update the number of online user whenever a session is expired without user logging out.
As other said above, there is no such events in the default session implementation, Sails session are close to ExpressJs Session, i recommend you to read this article about ExpressJs Sessions :
http://expressjs-book.com/forums/topic/express-js-sessions-a-detailed-tutorial/
Then one idea in order to achieve what you want could be to use a store and query inside of it.
Did you though about other solutions such as using socket.io (built in sails) and adding your users into a channel upon login and then simply counting user inside your channel ?
You can wrap the session.destroy() function like so:
var destroyWrapper = buildDestroyWrapper(function(req){
//do stuff after req.destroy was called
});
function buildDestroyWrapper(afterDestroy){
return function(req){
req.destroy();
afterDestroy(req);
};
}
//later, in your controller
function controllerAction(req,res,next){
destroyWrapper(req);
}
this method allows you to handle destruction differently, depending on what callback you pass to buildDestroyWrapper. For example:
var logAfterDestroy = buildDestroyWrapper(function(req){
console.log("session destroyed");
});
var killAfterDestroy = buildDestroyWrapper(function(req){
process.kill();
});
function buildDestroyWrapper(afterDestroy){
return function(req){
req.destroy();
afterDestroy(req);
};
}
//later, in your controller
function logoutAction(req,res,next){
logAfterDestroy(req);
}
function killAppAction(req,res,next){
killAfterDestroy(req);
}
I have a few questions about input validation with node, maybe there's a few concepts I'm just misunderstanding and need clarification. I'm currently using express-validator.
I currently have something like:
function validate(req) {
req.assert('name', 'Please enter a name').notEmpty()
req.assert('email', 'Please enter an email').notEmpty()
var errors = req.validationErrors();
return errors;
};
app.post('/contactform', function(req, res){
var err = validate(req);
if (!err) {
res.send('everything is good to go')
} else {
res.send(err)
}
});
I understand the validation part, however how do I go about actually using the information once I sanitize it?
I've tried stuff along the lines of
var email = req.sanitize('email').toString()
But this just gives me an [object object].
So I have a few questions:
How do I actually go about using sanitized strings? or does the express-validator module just validate/sanitize and I need to do something else to actually be able to use the sanitized output?
Also, should input be sanitized before being validated for security?
EDIT:
So I looked at the source code for express-validator and I found that the parameters are stored as req.param('name') or req.param('email'). I'm assuming that if you sanitize and/or validate the parameters and then access them, you receive the sanitized version. This answers my first question, still curious about the second question regarding general security.
I use trim() chained to sanitize():
var str = req.sanitize('input').trim();
Ok, I give up.
I've tried many things to create a simple login form. The form itself renders fine, it's just the processing of the POST datas which is wrong :-)
I have a Redis database with some keys in it. Firstly, there is a users set with a list of users (currently, only user:admin). Secondly, there is a hkey user:admin with just a password. See the screen below.
With the following code, when I submit my form, it doesn't get in the callback function of the redis' calls :
var username = req.body.username;
var password = req.body.password;
// Now, check if the user he entered exists
client.sismember(['users', 'user:' + username], function(err, reply) {
// IT NEVER GETS THERE
if (reply) {
// If he does, check if the password matches
client.hget(['user:' + username, 'password'], function(err, reply) {
if (reply === password) {
// If the password matches, add the session and redirects to home
req.session.username = username;
res.redirect('/home');
}
else {
options.error = "Password do not match.";
res.render('guest', options);
}
});
}
else {
options.error = "Username does not exist.";
res.render('guest', options);
}
});
I've used console.log to check some stuff, and username and password are well filled in.
There is no error concerning the redis server connection (which I have if I turn the server off), so the problem is not there.
I've tried using client.send_command() instead, but no change.
Any help would be greatly appreciated !
Ok, the answer is pretty stupid. I mean, I am stupid for not seeing this.
The asynchronous nature of this makes it pretty hard to get fully !
The problem was that later, in the code, at the end, I closed redis connection with client.end().
But this function was called before the callback could be fired, even though it was well after in the code.