I have built a mini CMS system using Node Js, Express, Mongoose and Passport.js.
When the client is loging out, I get this error in the console: 'Cannot read property 'id' of null', the error comes from the PrivateThread.find().lean().then()...
// Pass User Data To Template
function sendUserData(req, res, next) {
if (req.isAuthenticated()) {
User.findById(req.user.id, doNotSend).then(user => {
switch(true) {
case user.banned:
req.logOut();
flashRedirect(req, res, '/login', 'res-err', 'Some message');
break;
case !user.activated:
req.logOut();
flashRedirect(req, res, '/login', 'res-err', 'Somem message');
break;
default:
res.locals.foundUser = user;
PrivateThread.find({ participants: req.user.id }).lean().then(foundThreads => {
let notifications = foundThreads.filter(thread => !thread.readBy.toString().includes(req.user.id));
res.locals.notifications = notifications.length;
});
next();
}
})
.catch((err) => res.send(err));
} else {
next();
}
}
This is the logout route:
router.get('/logout', (req, res) => {
req.logOut();
res.redirect('/');
});
I'm only getting the error when the client is loging out through the logout page.
Related
I am trying to use passport.js with mongoose. The data sent are correct but I get an error of code 401 saying unauthorized?
Here is the back end controller.
userController.login = (req, res) =>{
console.log("recieved");
console.log(req.body);
const user = new Users({
username: req.body.mail,
password: req.body.password
})
req.login(user, (err) => {
if (err){
res.status(404).send(err);
}else{
console.log("user found");
passport.authenticate("local", (err, user, info) => {
if (err) {
return res.status(401).json(err);
}
if (user) {
return res.status(200).send(user);
} else {
res.status(401).json(info);
}
})(req, res, () => {
console.log("Authenticated!");
res.status(200).send(user);
});
}
})
}
While posting I needed to rename the req.body.mail to just req.body.username because auth and req.login looks for req body directly and search for a username object.
I recently added authentication to my website using bcrypt. When authenticating, bcrypt compares the passwords.
If passwords match req.session is set as a cookie and written to mongodb using connect-mongodb-session. But when redirecting to /events the app crashes and gives me an error: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I'm using handlebars to render my html code
auth route:
Router.get('/', (req, res) => {
res.status(200).render('login', {pageTitle: 'Log In'});
})
Router.post('/', (req, res) => {
//Authenticate user
const username = req.body.username;
const pass = req.body.password;
User.findOne({username: username}).then((user) => {
if (user) {
//Validate password
console.log(user);
console.log(user.password);
bcrypt.compare(pass, user.password).then((doMatch) => {
console.log(doMatch);
//Check if password match
if (doMatch) {
//To use session use express-session package
req.session.isLoggedIn = true;
req.session.user = user;
req.session.save((err) => {
console.log(err);
res.redirect('/');
});
return res.redirect('/events');
} else {
res.redirect('/');
}
}).catch((err) => {
console.log(err);
});
} else {
return res.redirect('/');
}
}).catch((err) => {
console.log(err);
});
});
events route:
Router.get('/events', (req, res) => {
if (req.session.isLoggedIn) {
Event.find({}, (err, events) => {
res.render('events', {
prods: events,
pageTitle: 'Events',
path: '/events',
hasProducts: events.length > 0
});
}).catch((err) => {
console.log(err);
});
} else {
console.log('User not authenticated');
res.status(401).send('User not authenticated');
}
});
These lines are the culprit.
req.session.save((err) => {
console.log(err);
res.redirect('/');
});
return res.redirect('/events');
Here you pass a callback into save and immediately redirect res.redirect('/events').
After some time when callback redirect res.redirect('/'); throws an error.
I am able to authenticate via LDAP, but I can't seem to figure out how to catch an error when a user enters an invalid username/password. I have failureRedirect and other stuff setup, but it doesn't get to that step in the authenticate function when an error does occur. I have tried putting in try catch functions with no luck either.
Here is the link to the authentication strategy being used:
https://www.npmjs.com/package/passport-activedirectory
front end -I have tried changing the {{#if error}} to {{#if failWithError}} as well.
{{#if error}}
<div class="alert alert-danger">
Warning! {{error}}
</div>
{{/if}}
**error message that is displayed to the user **
InvalidCredentialsError: 80090308: LdapErr: DSID-0C0903C5, comment: AcceptSecurityContext error, data 52e, v2580
at messageCallback (d:\........\node_modules\ldapjs\lib\client\client.js:1419:45)
at Parser.onMessage (d:\........\node_modules\ldapjs\lib\client\client.js:1089:14)
at emitOne (events.js:96:13)
at Parser.emit (events.js:188:7)
at Parser.write (d:\........\node_modules\ldapjs\lib\messages\parser.js:111:8)
at Socket.onData (d:\........\node_modules\ldapjs\lib\client\client.js:1076:22)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at Socket.Readable.push (_stream_readable.js:134:10)
block of code in ..\node_modules\ldapjs\lib\client\client.js line 1419
if (expect.indexOf(msg.status) === -1) {
return sendResult('error', errors.getError(msg));
}
auth.js
module.exports = function(app,passport) {
let opts = {
failWithError: true,
failureRedirect: '/',
successRedirect: '/dashboard',
failureFlash: true
};
app.post('/login', passport.authenticate('ActiveDirectory', opts), function(req, res) {
res.json(req.user);
}, function(err) {
if (err) res.status(401).send('Not Authenticated');
});
};
passport.js
module.exports = function(passport) {
var ActiveDirectoryStrategy = require('passport-activedirectory');
passport.serializeUser(function (user, done) { done(null, user); });
passport.deserializeUser(function (user, done) { done(null, user); });
passport.use(new ActiveDirectoryStrategy({
integrated: false,
passReqToCallback: true,
ldap: {
url: 'ldap://a.b.awesome:389',
baseDN: 'DC=a,DC=b,DC=awesome',
bindDN: 'CN=rando user,OU=users,DC=a,DC=b,DC=awesome',
bindCredentials: 'ASecret',
searchBase: 'OU=users,DC=a,DC=b,DC=awesome',
searchFilter: '(sAMAccountName={{username}})',
attributes: ['dn', 'displayName', 'givenName', 'sn', 'title', 'userPrincipalName', 'sAMAccountName', 'mail', 'description', 'memberOf'],
logging: {
name: 'ActiveDirectory',
streams: [
{ level: 'debug',
stream: process.stdout }
]
}
}
}, function (req, profile, ad, done) {
ad.isUserMemberOf(profile._json.dn, '123', function (err, isMember) {
if (err) return done(err);
return done (null, profile)
})
}));
};
So I was able to 'catch' the error and no longer display the error message to user.
However, express handlebars does not want to display my custom error message, I may be doing that wrong.
The answer for catching the error:
https://github.com/bhoriuchi/passport-activedirectory/issues/4
auth.js
app.post('/login', passport.authenticate('ActiveDirectory', opts), function(req, res) {
res.json(req.user);
}, function(err, req, res, next) {
let statusCode = /InvalidCredentialsError/.test(err.stack)
? res.redirect('/') && res.send( {error: 'Invalid Credentials!'} )
: 500;
return res.status(statusCode)
});
EDIT
I also figured out how to send data to the front end to be displayed in a nice manner.
new auth.js
app.post('/login', passport.authenticate('ActiveDirectory', opts), function(req, res) {
res.json(req.user);
}, function(err, req, res, next) {
let statusCode = /InvalidCredentialsError/.test(err.stack)
? res.render('login', {failWithError: true, error: 'Invalid Username or Password!'})
: res.status(500);
return statusCode
});
front end .hbs
{{#if failWithError}}
<div class="alert alert-danger">
<strong>Error!</strong> {{error}}
</div>
{{/if}}
I used your solution for a bit, but I think this works better IMO:
router.post('/login', (req, res, next) => {
passport.authenticate('ActiveDirectory', opts, (err, user, info) => {
if (err) {
return next(err)
}
if (! user) {
return res.redirect('/login')
}
req.login(user, (err) => {
if(err) {
return next(err)
}
return res.json(req.user)
})
}) (req, res, next)
})
I'm trying to make Node.js app with Nuxt.js and Passport.js.
I login and set user data in vuex.
action:
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.session.authUser) {
commit('SET_USER', req.session.authUser)
}
}
mutation:
SET_USER: function (state, user) {
state.authUser = user
}
And it works fine, but when I tried to access page that can be accessed only by loged in user there came problems.
axios.get('http://localhost:3000/api/articles/add')
router.get('/api/articles/add', ensureAuthenticated,
(req, res) => {
res.send({
title: 'Add Article'
})
})
function ensureAuthenticated (req, res, next) {
if (req.isAuthenticated()) {
return next()
} else {
res.redirect('/users/login')
}
}
I login and go to http://localhost:3000/articles/add, make request for data restricted for this user (GET http://localhost:3000/api/articles/add). Checking by ensureAuthenticated if user is loged in and isAuthenticated() returns false all the time and when I want to send user data without ensureAuthenticated it's returning undefined
app.get('/api/articles/add', (req, res) => {
res.send(req.user) // undefined
})
BUT if I will send this data to the same api route as page, which I want access, I'm getting right data on screen.
app.get('/articles/add', function(req, res) {
res.send(req.user) // {...}
})
Can someone explain me that? And in some situation I can't make redirection from node.js side. Like in this prev example
function ensureAuthenticated (req, res, next) {
if (req.isAuthenticated()) {
return next()
} else {
res.redirect('/users/login') // ignored
}
}
I am currently trying to set up an admin role in order to access a simple admin page using the following documentation provided via : connect-roles
I ave been banging my head against it for a while and am still lost on how to set a role E.G As of right now am pulling a admin value out of the DB and storing it in a global var for the time being but I have no idea how to use that with connect-roles say to only allow access to my admin page for a specific user.
Can anyone clarify or show an example on how to do this/some guidance as I documentation didn't help me to ensure access to a web page only if the user is an admin?
Ave posted some of the code kinda showing what it looks like at the moment.
Code
var admin = 'Admin';
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'test'
});
var passport = require('passport');
var ConnectRoles = require('connect-roles');
var roles = new ConnectRoles();
var passportLocal = require('passport-local');
app.use(passport.initialize());
app.use(passport.session());
app.use(roles.middleware());
passport.use(new passportLocal.Strategy(function (username, password, done) {
connection.query({
sql : 'SELECT * from `userman_users` WHERE `username`= ?AND`password` = sha1(?)',
timeout : 40000, // 40s
values : [username, password]
}, function (error, results, rows) {
if (results.length > 0) {
response = "Success";
} else {
console.log('Error while performing Query.');
response = "Failed";
}
if (response === "Success") {
done(null, {
id : username
});
} else if (response === "Failed") {
done(null, null);
}
});
})
);
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
done(null, {
id : id
});
});
roles.use(function (req, action) {
if (!req.isAuthenticated()) return action === 'access home page';
})
roles.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
app.get('/', redirectToIndexIfLoggedIn, function (req, res) {
res.render('login');
});
app.get('/index', checkLoggedIn, function (req, res) {
res.render('index', {
isAuthenticated : req.isAuthenticated(),
user : req.user
});
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
function checkLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/');
}
this is an example:
var express = require('express');
...
var passport = require('passport');
var LocalStrategy = require('passport-local');
var ConnectRoles = require('connect-roles');
...
var app = express();
//===============PASSPORT=================
// Passport session setup.
passport.serializeUser(function(user, done) {
console.log("serializing " + user.username);
done(null, user);
});
passport.deserializeUser(function(obj, done) {
console.log("deserializing " + obj);
// simulate an admin user
obj.role = obj.username == 'admin' ? 'admin' : 'user';
done(null, obj);
});
...
//===============CONNECTION RULES=================
var user = new ConnectRoles({
failureHandler: function (req, res, action) {
// optional function to customise code that runs when
// user fails authorisation
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
}
});
...
app.use(passport.initialize());
app.use(passport.session());
app.use(user.middleware());
//anonymous users can only access the home page
//returning false stops any more rules from being
//considered
user.use(function (req, action) {
if (!req.isAuthenticated()) return action === 'access home page';
});
//users logged can access to public pages
user.use(function(req, action){
if(req.isAuthenticated() && action != 'access private page' && action != 'access admin page')
return true;
});
//moderator users can access private page, but
//they might not be the only ones so we don't return
//false if the user isn't a moderator
user.use('access private page', function (req) {
console.log('access private page');
if (req.user.role === 'moderator') {
return true;
}
});
//admin users can access all pages
user.use(function (req) {
if (req.user.role === 'admin') {
return true;
}
});
...
/* GET home page. */
app.get('/', user.can('access home page'), function(req, res, next) {
res.render('index', { title: 'Express' });
});
//displays our signup page
app.get('/signin', function(req, res){
res.render('signin');
});
//sends the request through our local signup strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/local-reg', passport.authenticate('local-signup', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
//sends the request through our local login/signin strategy, and if successful takes user to homepage, otherwise returns then to signin page
app.post('/login', passport.authenticate('local-signin', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
// Simple route middleware to ensure user is authenticated.
app.use(function(req, res, next) {
if (req.isAuthenticated()) { return next(); }
req.session.error = 'Please sign in!';
res.redirect('/signin');
});
//logs user out of site, deleting them from the session, and returns to homepage
app.get('/logout', function(req, res){
var name = req.user.username;
console.log("LOGGIN OUT " + req.user.username)
req.logout();
res.redirect('/');
req.session.notice = "You have successfully been logged out " + name + "!";
});
app.get('/private', user.can('access private page'), function (req, res) {
res.render('private');
});
app.get('/admin', user.can('access admin page'), function (req, res) {
res.render('admin');
});
app.use('/users', users);
....
module.exports = app;
With connect-rules you define the rules do you want to use (user.use in this case). If you pass an action as first parameter the strategy is only used if the action passed in the function is equal to it. Then you trigger the rules in the routes with user.can passing the action. In this example I define an extra filter strategy to grant access to users that are logged and request routes that are not marked with admin or moderator privileges e.g
/* GET home page. */
app.get('/', user.can('access home page'), function(req, res, next) {
res.render('index', { title: 'Express' });
});
After the user is logged, we need to have another strategy in case the user isn't admin or moderator.
U can use framework like sailsJS and npm module sails-generate-auth
And after setup, use your own middleware to block routes
//allow admin only localhost:PORT/admin at policies.js
'admin': ['passport', 'sessionAuth', 'isAdmin'],
'*': ['passport', 'sessionAuth'],
//isAdmin policy
module.exports = function(req, res, next) {
// User is allowed, proceed to the next policy,
// or if this is the last policy, the controller
if (req.user.role == 'admin') {
return next();
}
// User is not allowed
return res.forbidden('You are not permitted to perform this action.');
};
Using the following logic I was able to have admin functionality based on value within the DB:
app.get('/admin', function (req, res) {
connection.query({
sql : 'SELECT role from `auth_users` WHERE `username`= ?',
timeout : 40000, // 40s
values : [req.user['id']]
}, function (error, results, rows) {
if (results[0]['role'] === "admin") {
admin = (results[0]['role']);
res.render('admin', {
isAuthenticated : req.isAuthenticated(),
user : req.user
});
} else {
admin = "";
res.redirect('/index');
}
})
});