I am trying to adapt some code taken from:
https://github.com/expressjs/express/blob/master/examples/auth/index.js
and incorporate into a login form. The issue is that when running the code and selecting the login button the following is logged in the console:
POST /login
Authenticating undefined:foobar
Authentication failed, please check your username and password. (use "tj" and "foobar")
I'm unsure as to why the user is being returned as undefined when I am inputting tj and foobar as the login and password.
HTML:
<div class="login">
<form method="post" action="/login">
<p><input type="text" name="login" value="" placeholder="Username"></p>
<p><input type="password" name="password" value="" placeholder="Password"></p>
<p class="remember_me">
<label>
<input type="checkbox" name="remember_me" id="remember_me">
Remember me on this computer
</label>
</p>
<p class="submit"><input type="submit" name="commit" value="Login"></p>
</form>
</div>
<div class="login-help">
<p>Forgot your password? Click here to reset it.</p>
</div>
JS:
/**
* Module dependencies.
*/
var express = require('express');
var hash = require('./pass').hash;
var bodyParser = require('body-parser');
var session = require('client-sessions');
var app = module.exports = express();
// dummy database
var users = {
tj: { name: 'tj' }
};
// when you create a user, generate a salt
// and hash the password ('foobar' is the pass here)
hash('foobar', function (err, salt, hash){
if (err) throw err;
// store the salt & hash in the "db"
users.tj.salt = salt;
users.tj.hash = hash;
});
// check if user is logged in, if not redirect them to the index
function requireLogin (req, res, next) {
if (!req.user) {
res.redirect('/');
} else {
next();
}
};
// Authenticate using our plain-object database
function authenticate(name, pass, fn) {
if (!module.parent) console.log('Authenticating %s:%s', name, pass);
var user = users[name];
// query the db for the given username
if (!user) return fn(new Error('cannot find user'));
// apply the same algorithm to the POSTed password, applying
// the hash against the pass / salt, if there is a match we
// found the user
hash(pass, user.salt, function(err, hash){
if (err) return fn(err);
if (hash == user.hash) return fn(null, user);
fn(new Error('invalid password'));
});
}
app.post('/login', function (req, res){
console.log("POST /login")
authenticate(req.body.username, req.body.password, function(err, user){
if (user) {
// Regenerate session when signing in
// to prevent fixation
req.session.regenerate(function(){
// Store the user's primary key
// in the session store to be retrieved,
// or in this case the entire user object
req.session.user = user;
/*req.session.success = 'Authenticated as ' + user.name
+ ' click to logout. '
+ ' You may now access /restricted.';*/
res.redirect('/queryInterface.html');
});
} else {
console.log('Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")');
res.redirect('/');
}
});
});
You are seeing the user as undefined because you are using req.body to access an undefined input field.
You are authenticating with req.body.username, but your input field has the name login.
req.body is populated with the names of your input fields. Your input should look like this when trying to access the username by req.body.username.
<input type="text" name="username" value="" placeholder="Username">
Related
I'm implementing a password reset following a tutorial but running into a problem. My req.body.email is coming back undefined. I have body-parser installed and my other routes are running perfectly.
Here is my code summary:
var express = require('express');
var router = express.Router({ mergeParams: true });
var Kids = require('../models/kid');
var User = require('../models/user');
var async = require('async');
var nodemailer = require('nodemailer');
var crypto = require('crypto');
var middleware = require('../middleware');
router.post('/password_reset', function(req, res, next) {
function(token, done) {
User.findOne({ email: req.body.email }, function(err, user) {
console.log(req.body.email); <====== Returning and undefined
console.log(user); <====== Returning as null
if (!user) {
req.flash('error', 'No account with that email address exists.');
return res.redirect('/password_reset');
}
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
});
});
}
});
and my form
<form action="/password_reset" method="POST" >
<div class="form-group">
<label for="exampleInputEmail1">Enter your email address</label>
<input type="email" class="form-control" id="email" aria-describedby="emailHelp" placeholder="Enter email" required>
</div>
<button type="submit" class="btn btn-warning">Submit</button>
</form>
You have two problems:
You aren't submitting any data
Your <input> has no name attribute, so it can't be a successful control.
If you want req.body.email to have data in it then you need to say name="email".
Related to this, you said <label for="exampleInputEmail1"> but id="email". The for attribute needs to match the id of the element it is labelling. Then aria-describedby="emailHelp" needs to match the ID of the element that is labelling the current element … and isn't needed when you have a real <label>.
You aren't parsing the submitted data
See the documentation for req.body:
Contains key-value pairs of data submitted in the request body. By default, it is undefined, and is populated when you use body-parsing middleware such as express.json() or express.urlencoded().
You haven't used any body-parsing middleware.
Your form is submitting urlencoded data (the default) so use express.urlencoded():
router.use(express.urlencoded())
I'm using passport.js with mongoose (mongoDB) (and passport-local-mongoose) to create and authenticate user accounts in a node/express backend.
The strategy I'm currently using is:
User completes a signup form, with a username input type="email" name="username" and a password input name='password' (among others)*
User submits form.
Add new user to a Profile schema let newUser = new Profile({username: req.body.username, ...})
Generate a token using crypto.randomBytes(...)
Profile.register(newUser, password) & passport.authenticate user and set token field generated from above set, along with an expiry date
Send an email to new user, with a url containing their mongoose _id and their token (/u/${id}/is-valid/${token}).
On visiting this url, Profile.findOne({_id: req.params.id}) and check that the tokens match, and has not expired
If not, send them to the login page.
Then problem with this approach, is that in calling Profile.register and passport.authenticate together, a pre authenticated account is created before the user has validated their email.
How can I decouple these two methods so that I can authenticate users only after they've verified their email address?
Here is how I call Profile.register and passport.authenticate (as part of an async.waterfall([]):
Profile.register(user, pw)
.then(response => {
passport.authenticate("local")(req, res, function(err) {
if (err) return callback(err);
user.resetEmailToken = token;
user.resetEmailExpires = Date.now() + 8.64e+7; // 1 day
user.save(function(err) {
callback(err, token, user);
});
});
})
.catch(err => {
return callback(err);
});
I would like to Profile.register as I do above, but only passport.authenticate once I've checked the tokens.
Here is how I check the tokens (on visiting link contained in email - /u/${id}/is-valid/${token}):
async verifyEmail(req, res, next){
let user = await Profile.findOne({ '_id': req.params.id })
if(!user) return res.redirect("back");
if (user.resetEmailToken != req.params.token){
req.flash("error", "Tokens don't match");
return res.redirect("back");
}
if(user.resetEmailExpires < Date.now()){
console.log("This date has expired", moment(user.resetEmailExpires).calendar());
return res.redirect(`/signup?token-expired=${user.email}`);
}
user.isVerified = true;
// Here is where I want to passport.authenticate() and redirect to their new profile
user.save();
return res.render('login', { user: user });
}
If this is not possible, what are other approaches I could take to achieve this? It must be a fairly common signup strategy.
* This is a bit of a hack as passport.js assumes registration with username & password (not email)
I am trying to get the token value from the following URL http://localhost:3000/users/reset/e3b40d3e3550b35bc916a361d8487aefa30147c8. I have a get request that checks if the token is valid and redirects the user to a reset password screen. I also have a post request but when I console req.params.token, it outputs :token instead of e3b40d3e3550b35bc916a361d8487aefa30147c8. I am wondering if the form action is correct but don't know how to get the token value from it.
Reset Password Get Request
router.get('/reset/:token', (req, res) => {
console.log(req.params.token) // e3b40d3e3550b35bc916a361d8487aefa30147c8
User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: Date.now()
}
}, (err, user) => {
if (!user) {
req.flash('error_msg', 'The password reset token is invalid or has expired.')
return res.redirect('/users/forgot')
}
res.render('reset')
})
})
reset.ejs
<% include ./partials/messages %>
<form action="/users/reset/:token" method="POST">
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Please enter a password."
value="<%= typeof password != 'undefined' ? password : '' %>" />
</div>
<button type="submit" class="btn btn-primary btn-block">Register</button>
</form>
Reset Password Post Request
router.post('/reset/:token', (req, res) => {
console.log(req.params.token) // :token
User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: Date.now()
}
}, (err, user) => {
if (!user) {
req.flash('error_msg', 'The password reset token is invalid or has expired.')
return res.redirect('/users/forgot')
}
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
user.save(function (err) {
req.flash('success_msg', 'Working.')
return res.redirect('/users/login')
})
})
})
In your form in your HTML, you have this:
<form action="/users/reset/:token" method="POST">
That's going to make the actual URL that gets requested when the form is posted be:
/users/reset/:token
There's no code doing any substitution for the :token here. That's just getting sent directly to the server as the URL.
So, when you then have:
router.post('/reset/:token', (req, res) => {
console.log(req.url); // "/user/reset/:token"
console.log(req.params.token); // ":token"
});
What req.params.token is showing you is whatever is in the URL that's after /users/reset. In your case, that is the literal string ":token". For req.params.token to actually have to token in it, you would have to insert the actual token into the URL so your form tag looks like this:
<form action="/users/reset/e3b40d3e3550b35bc916a361d8487aefa30147c8" method="POST">
Or, you will have to get access to the token some other way such as from the express session, from a cookie, from a field in the form, etc...
To get a URL parameter's value
app.get('/reset/:token', function(req, res) {
res.send("token is " + req.params.token);
});
To get a query parameter ?token=Adhgd5645
app.get('/reset/?token=Adhgd5645', function(req, res) {
res.send("token is " + req.query.token);
});
It's my first time implementing passport strategies (using this tutorial https://scotch.io/tutorials/easy-node-authentication-setup-and-local) and I think I made a small mistake that cause a weird problem. First time login with email and password, no problem (db connected, user login successful) second time with same email and password I get rangeError: Invalid status code: 1 and crash nodemon.
I tried to find more info on this error but there really isn't any out there. I did come across someone else with similiar issue but no one answered his question since October. Anyone care to take a crack at this?
routes.js
app.post('/login', passport.authenticate('local'), function (req, res {
console.log("passport user" + req.user);
res.status(200).json({
user: req.user
});
});
app.get('/user/auth', auth.isAuthenticated, function (req, res) {
if (req.user) {
res.status(200).json({
user: req.user
});
} else {
res.sendStatus(401);
}
});
app.post("/api/user", function (req, res) {
const user = req.body;
console.log(user);
User.findOne({ 'local.email': user.email },
function (err, result) {
if (err) {
console.log(err);
handleError(err, res);
return;
}
if (result) {
res.status(500).send("Email already exists in database");
} else {
var newUser = new User();
newUser.local.password = createHash(user.password);
newUser.local.email = user.email;
newUser.local.name = user.name;
newUser.local.mobile = user.mobile;
newUser.save(function (err, result) {
res.status(201).send("User added to database");
});
}
});
});
auth.js
passport.use(new LocalStrategy({ // redefine the field names the strategy (passport-local) expects
usernameField: 'username',
passwordField: 'password',
passReqToCallback : true
}, function(req, email, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser= new User();
// set the user's local credentials
newUser.local.email= email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}
var isAuthenticated = function(req, res, next) {
//console.log("isAuthenticated(): ", req.user);
if (req.isAuthenticated()) {
next(); //good moves to the next one
}
else {
res.sendStatus(401);
}
}
return {
isAuthenticated: isAuthenticated,
}
};
user.js
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
// define the schema for our user model
var userSchema = mongoose.Schema({
local: {
id: String,
email: String,
password: String,
name: String,
mobile: String
},
google: {
id: String,
token: String,
email: String,
name: String
}
});
// methods ======================
// generating a hash
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);
html
<div class="panel panel-default">
<div class="panel-body">
<form name='loginForm' ng-submit='ctrl.login()' novalidate>
<div class="form-group">
<input class="form-control" type="text" name="username" placeholder="EMAIL" id="username" ng-model='ctrl.user.username'></div>
<div class="form-group">
<input class="form-control" type="password" name="password" placeholder="PASSWORD" id="password" ng-model='ctrl.user.password'></div>
<div class="form-group">
<input id="submit" name="submit" type="submit" class="btn btn-full btn-block" value="Log In"></div>
I'm learning Node.js and all its functionalities and I'm using scotch's tutorial (https://scotch.io/tutorials/easy-node-authentication-setup-and-local), I built what he proposed and it was really good, but then I wanted to get more informations for user, like name and a special number called SIAPE as my user model code shows: user.js
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var userSchema = mongoose.Schema({
local : {
name : String,
email : String,
password : String,
siape : String
}
});
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8),null);
};
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
module.exports = mongoose.model('User', userSchema);
And then set up my passport.js to update those thing to my mangodb database with mongoose, but it dosen't work and I dont know why.
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var User = require('../app/models/users');
// expose this function to our app using module.exports
module.exports = function(passport) {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'siape',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, siape,password, email,name, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
console.log('USERNAME:' + name );
console.log('EMAIL:' + email );
console.log('PASSWORD:' + password );
console.log('SIAPE:' + siape );
console.log('DONE:' + done);
console.log('REQ:' + req);
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.siape' : siape }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'Email já existe'));
} else {
// if there is no user with that Email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.name = name;
newUser.local.email = email;
//newUser.local.password = newUser.generateHash(password);
newUser.local.password = password;
newUser.local.siape = siape;
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error before anything else
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
if (!user.validPassword(password))
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
return done(null, user);
});
}));
};
Don't know if it helps, but I'll update also the html form:
<!-- views/signup.ejs -->
<!doctype html>
<html>
<head>
<title>Cadastro de professor</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
<style>
body { padding-top:80px; }
</style>
</head>
<body>
<div class="container">
<div class="col-sm-6 col-sm-offset-3">
<h1><span class="fa fa-sign-in"></span> Cadastro De Professor</h1>
<!-- show any messages that come back with authentication -->
<% if (message.length > 0) { %>
<div class="alert alert-danger"><%= message %></div>
<% } %>
<!-- LOGIN FORM -->
<form action="/signup" method="post">
<div class="form-group">
<label>Nome</label>
<input type="text" class="form-control" name="username">
</div>
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" name="email">
</div>
<div class="form-group">
<label>Senha</label>
<input type="password" class="form-control" name="password">
</div>
<div class="form-group">
<label>SIAPE</label>
<input type="text" class="form-control" name="siape">
</div>
<button type="submit" class="btn btn-warning btn-lg">Cadastrar Professor</button>
</form>
<hr>
<p>Professor ja cadastrado?? Entre</p>
<p>Ou volte.</p>
</div>
</div>
</body>
</html>
A lot of things are happening that I don't quite understand, for example, I've put a console.log to print all the information sent in the html form, and this was what was print:
USERNAME:undefined
EMAIL:function verified(err, user, info) {
if (err) { return self.error(err); }
if (!user) { return self.fail(info); }
self.success(user, info);
}
PASSWORD:senha
SIAPE:SIAPE
DONE:undefined
REQ:[object Object]
And for the last part, the error printed in my terminal server:
/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/lib/utils.js:413
throw err;
^
TypeError: undefined is not a function
at Promise.<anonymous> (/home/aluno/28882/naest/AdmnistrationModule/config/passport.js:83:31)
at Promise.<anonymous> (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mpromise/lib/promise.js:177:8)
at Promise.emit (events.js:98:17)
at Promise.emit (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mpromise/lib/promise.js:84:38)
at Promise.fulfill (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mpromise/lib/promise.js:97:20)
at handleSave (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/lib/model.js:133:13)
at /home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/lib/utils.js:408:16
at /home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/collection/core.js:128:9
at /home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1197:7
at /home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1905:9
at Server.Base._callHandler (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:453:41)
at /home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:488:18
at MongoReply.parseBody (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:68:5)
at null.<anonymous> (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:446:20)
at emit (events.js:95:17)
at null.<anonymous> (/home/aluno/28882/naest/AdmnistrationModule/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:207:13)
aluno#lab2208-pc32:~/28882/naest/AdmnistrationModule$ clear
Just solved my problem, the problem was that local-signup only accepts 2 parameters as default, username and password for authentication, other variable are to be catched using the "req.body" prefix. This link helped me a lot
Does passportjs LocalStrategy allow more parameters than the default username and password?