I am using ldapjs in two sections of my project. The first is using the passport-windowsauth strategy for passportjs and the second is part of my authentication function where I connect to retrieve user roles. I have attached an event handler to the latter to catch error events, but I have never had it trigger. I suspect the error is occurring on the passport strategy, but I am unsure how to attach an event handler to the strategy. How would I go about catching the error event on the strategy? Console log shows the following after about 15 minutes and repeats every couple of minutes indefinitely. Other than the errors in the log, the implementation works perfect.
LDAP connection error: { [Error: read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
ldap.js
var ldap = require('ldapjs'),
fs = require('fs');
ldapClient = null;
exports.createClient = function() {
if(ldapClient) return ldapClient;
else {
ldapClient = ldap.createClient({
url: 'ldaps://srv01.contoso.local:636',
reconnect: true,
tlsOptions: {
rejectUnauthorized: true,
ca: [ fs.readFileSync(__dirname + '/../config/ca.pem') ]
}
});
ldapClient.bind('binduser','C0nn3ctM3', function(err) {if(err) {
console.log('Bind error: ' + err); assert.ifError(err); }
});
ldapClient.on('error', function(err) { console.log('Caught ', err.code) })
return ldapClient;
}
}
exports.destroyClient = function() {
if(!ldapClient) return true;
else {
ldapClient.unbind();
ldapClient.destroy();
return true;
}
}
passport.js
var passport = require('passport'),
mongoose = require('mongoose'),
WindowsStrategy = require('passport-windowsauth'),
User = mongoose.model('User'),
fs = require('fs');
module.exports = function() {
passport.use(new WindowsStrategy({
ldap: {
url: 'ldaps://srv01.contoso.local:636/',
base: 'DC=Contoso,DC=local',
bindDN: 'binduser',
bindCredentials: 'C0nn3ctM3',
reconnect: true,
tlsOptions: {
rejectUnauthorized: true,
ca: [ fs.readFileSync(__dirname + '/ca.pem') ]
}
},
integrated: false
}, function(profile, done) {
if(!profile) { console.log('Error: Cannot retrieve profile. Bad password?'); }
else {
User.findOne({'userName': profile._json.sAMAccountName}, function(err, user) {
if(user) {
done(null, user);
}
else {
var newuser = new User();
newuser.getRoles(profile._json.sAMAccountName).then(function(userRoles) {
var account = {
userName: profile._json.sAMAccountName,
firstName: profile._json.givenName,
lastName: profile._json.sn,
emailAddress: profile._json.mail,
roles: userRoles
};
User.create(account, function (err, user) {
if(err) {
if(err.toString().indexOf('E11000') > -1) {
err = new Error('Duplicate username');
}
}
done(null, user);
});
});
}
});
}
}));
passport.serializeUser(function(user, done) {
if(user) {
done(null, user.userName);
}
});
passport.deserializeUser(function(id, done) {
User.findOne({'userName': id}).exec(function(err, user) {
if(user) {
return done(null, user);
}
else {
return done(null, false);
}
})
});
}
Update
I ended up finding the line responsible for the repeated console spamming in the passport-windowsauth file LdapLookup.js and modified it. I would still like to now how I would attach an event handler to a strategy or what the alternative solution would be.
Maybe its too late to reply but for other reader's sake, I'm adding this reply.
ECONNRESET error occurs when after some idle time, the connection to LDAP service is reset. This is a common problem since a prolonged connection to ldap cannot be maintained. I too faced this problem recently.
There are multiple ways to solve this problem.
Set reconnect option true. I see that you've already done that b
Next option would be to destroy the client when ERRCONNRESET occur and recreate. You've already implemented the destroy client and recreating it every time.
You could also find this link which discuss this problem is detail.
To further add to the response #prajnavantha gave, for the createClient {reconnect: true} option to function, you also have to bind an error event listener to the client or the reconnect will get short circuited by the default error event handler.
For instance:
const ldapjs = require("ldapjs");
const client = ldapjs.createClient({url: 'ldap://host', reconnect: true});
client.on('error', err => {
console.debug('connection failed, retrying');
});
client.bind('user', 'password', err => {
if (err) process.exit();
});
// ...
Related
When i try to use JWT with socket.io it wont respsonse when i get connection. I dont get any errors, but i dont get any respsonse when doing things on localhost. The localstorage is empty also. Please help, i've tried everything:)
client.js
var socket = io('http://localhost:4000', {query: 'auth_token=THE_JWT_TOKEN'});
socket.on('error', function(err) {
console.log("test")
throw new Error(err);
});
// Connection succeeded
socket.on('success', function(data) {
console.log(data.message);
console.log('user info: ' + data.user);
console.log('logged in: ' + data.user.logged_in)
})
server.js
var io = require("socket.io")(server);
var socketioJwt = require("socketio-jwt");
// set authorization for socket.io
var io = require('socket.io')();
var jwtAuth = require('socketio-jwt-auth');
var jwt = require('jsonwebtoken')
// using middleware
io.use(jwtAuth.authenticate({
secret: 'Your Secret', // required, used to verify the token's signature
algorithm: 'HS256' // optional, default to be HS256
}, function(payload, done) {
// done is a callback, you can use it as follows
User.findOne({id: payload.sub}, function(err, user) {
if (err) {
// return error
return done(err);
}
if (!user) {
// return fail with an error message
return done(null, false, 'user does not exist');
}
// return success with a user info
return done(null, user);
});
}));
io.on('connection', function(socket) {
console.log('Authentication passed!');
// now you can access user info through socket.request.user
// socket.request.user.logged_in will be set to true if the user was authenticated
socket.emit('success', {
message: 'success logged in!',
user: socket.request.user
});
I am writing a MEAN API at the minute and I have a route called /authenticate where users can log in. Below I have shown the route it's self.
// User Login Route - http://localhost:8080/api/authenticate
router.post('/authenticate', function(req, res) {
User.findOne({ username: req.body.username }).select('email username password').exec(function(err, user) {
if (err) throw err;
if (!user) {
res.json({ success: false, message: 'Could not authenticate user.'});
} else if (user) {
//Using comparePassword Method
if (req.body.password) {
var validatePassword = user.comparePassword(req.body.password);
if (!validatePassword) {
res.json({success : false, message : 'Could not authenticate password!'});
}
else{
res.status(200).json({success : true, message : 'User authenticated!'});
}
}
else{
res.json({success : false, message : 'Not password provided!'});
}
}
});
});
I am now trying to test the route using the mocha and chai libraries. Below I have shown the describe block that I am using to test this function.
describe('POST /authenticate', function() {
it('should return an error', function (done) {
var newUser = {
"username" : "testetet",
"password" : "hgfhghg"
};
chai.request(server)
.post('/api/authenticate')
.send(newUser)
.end(function (err, res) {
expect(res).to.have.status(201);
expect(res.body).to.have.property('message').equal('User Created.');
});
done();
});
});
As you can see from the test above the test should fail because none of the response criteria in the test matches the response that should be coming back from the API, but it still continues to pass every time I run it.
Does anyone have any ideas why this would be happening?
Thanks for any help in advance.
Hi guys found the problem the done() in the test code was in the wrong place. It should look like the code below.
it('should return a user authenticated message', function (done) {
var user = {
"username": "testuser1",
"password": "testPassword1"
};
chai.request(server)
.post('/api/authenticate')
.send(user)
.end(function (err, res) {
expect(res).to.have.status(200);
expect(res.body).to.have.property('message').equal('User Authenticated!');
done();
});
});
In my ReactJS project, I am currently running the server with NodeJS and ExpressJS, and connecting to the MongoDB using MongoClient. I have a login API endpoint set up that accepts a request with user's username and password. And if a user is not found, should catch the error and respond with an error (status(500)) to the front-end.
But rather than responding to the front-end with an json error, the server gets crashed. I have tried everything to figure out why but still no luck.
How can I fix the following error? Any guidance or insight would be greatly appreciated, and will upvote and accept the answer.
I intentionally made a request with a username and a password ({ username: 'iopsert', password: 'vser'}) that does not exist in the database.
Here is the login endpoint:
//login endpoint
app.post('/api/login/', function(req, res) {
console.log('Req body in login ', req.body)
console.log('THIS IS WHAT WAS PASSED IN+++++', req._id)
db.collection('users').findOne({username: req.body.username}, function(err, user) {
console.log('User found ')
if(err) {
console.log('THIS IS ERROR RESPONSE')
// Would like to send this json as an error response to the front-end
res.status(500).send({
error: 'This is error response',
success: false,
})
}
if(user.password === req.body.password) {
console.log('Username and password are correct')
res.status(500).send({
username: req.body.username,
success: true,
user: user,
})
} else {
res.status(500).send({
error: 'Credentials are wrong',
success: false,
})
}
})
And here is the terminal error log:
Req body in login { username: 'iopsert', password: 'vset' }
THIS IS WHAT WAS PASSED IN+++++ undefined
User found
/Users/John/practice-project/node_modules/mongodb/lib/utils.js:98
process.nextTick(function() { throw err; });
^
TypeError: Cannot read property 'password' of null
at /Users/John/practice-project/server/server.js:58:12
at handleCallback (/Users/John/practice-project/node_modules/mongodb/lib/utils.js:96:12)
at /Users/John/practice-project/node_modules/mongodb/lib/collection.js:1395:5
at handleCallback (/Users/John/practice-project/node_modules/mongodb/lib/utils.js:96:12)
at /Users/John/practice-project/node_modules/mongodb/lib/cursor.js:675:5
at handleCallback (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:165:5)
at setCursorNotified (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:505:3)
at /Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:578:16
at queryCallback (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:226:18)
at /Users/John/practice-project/node_modules/mongodb-core/lib/connection/pool.js:430:18
And /Users/John/practice-project/node_modules/mongodb/lib/utils.js:98 is referring to the following:
var handleCallback = function(callback, err, value1, value2) {
try {
if(callback == null) return;
if(value2) return callback(err, value1, value2);
return callback(err, value1);
} catch(err) {
process.nextTick(function() { throw err; });
return false;
}
return true;
}
EDIT
Here are everything that's being imported to the server:
"use strict"
var express = require('express');
var path = require('path');
var config = require('../webpack.config.js');
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var bodyParser = require('body-parser');
var MongoClient = require('mongodb').MongoClient;
var ObjectId = require('mongodb').ObjectID;
const jwt = require('jsonwebtoken')
var app = express();
var db;
var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));
app.use(webpackHotMiddleware(compiler));
app.use(express.static('dist'));
app.use(bodyParser.json());
And this is how the request is made and error is caught:
loginUser(creds) {
var request = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(creds),
}
fetch(`http://localhost:3000/api/login`, request)
.then(res => res.json())
.then(user => {
console.log(user);
console.log('Successful')
})
.catch(err => {
console.log('Error is', err)
})
},
It looks to me like the error is being thrown on this line because user is not defined.
if(user.password === req.body.password) {...}
Take a harder look at your console statements.
1. Req body in login { username: 'iopsert', password: 'vset' }
2. THIS IS WHAT WAS PASSED IN+++++ undefined
3. User found
4. /Users/John/practice-project/node_modules/mongodb/lib/utils.js:98
5. process.nextTick(function() { throw err; });
^
6. TypeError: Cannot read property 'password' of null
7. at /Users/John/practice-project/server/server.js:58:12
Line 2 shows that req._id is undefined
Your User found statement is printed before you check if there is an error or if the user actually exists, so it isn't representative of there actually being a user.
Line 6 shows that the error is being thrown because you're trying to read a property of password from a null object.
I'd recommend modifying your login logic to look more like this:
//login endpoint
app.post('/api/login/', function(req, res) {
console.log('Performing login with req.body=');
console.log(JSON.stringify(req.body, null, 4));
// check for username
if (!req.body.username) {
return res.status(401).send({message: 'No username'});
}
// find user with username
db.collection('users').findOne({username: req.body.username}, function(err, user) {
// handle error
if(err) {
console.log('Error finding user.');
return res.status(500).send({message: 'Error finding user.'});
}
// check for user
if (!user) {
console.log('No user.');
return res.status(500).send({message: 'No user.'});
}
console.log('User found.');
// check password
if(user.password !== req.body.password) {
console.log('Wrong password.');
return res.status(401).send({message: 'Wrong password.'});
}
// return user info
return res.status(200).send(user);
});
Some final thoughts:
Make sure to handle the error (if it exists) and check that user exists before proceeding.
Always include return in your return res.status(...).send(...) statements, otherwise the subsequent code will execute.
It's generally not a good idea to save passwords as simple strings. Work toward encrypting them. Look at passport or bcrypt.
Hope this helps.
I'm using express-session module to handle my node.js user sessions.
By default it allows multiple sessions per user. I need limit one session per user. I came to the following solution: store user_id:session_id pairs in redis, when user logins check if session for that user_id exists and delete it then create a new one and save it to redis. Everything works excellent until I tried to stress test my server using siege. I emulated simultaneous 1000 login attempts and I see that some sessions are not cleared and still is in redis store.
This allow one user have several sessions. What am I doing wrong?
Please find some code below.
var FileStreamRotator = require('file-stream-rotator'),
app = require('express')(),
fs = require("fs"),
bodyParser = require('body-parser'),
config = require("./providers/config"),
morgan = require('morgan'), //HTTP request logger middleware for node.js
cookieParser = require('cookie-parser'),
redis = require('redis'),
session = require('express-session'),
redisStore = require('connect-redis')(session),
publicRouter = require('./routes/public.js')();
var port = process.env.PORT || config.port;
var client = redis.createClient();
app.disable('x-powered-by');
app.use(cookieParser(config.session.secret));
app.use(session(
{
secret: config.session.secret,
store: new redisStore({host: config.redis.host, port: config.redis.port, client: client}),
saveUninitialized: false, // don't create session until something stored,
resave: false // don't save session if unmodified
}
));
app.use(morgan('combined', {stream: accessLogStream}));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
*****
app.all('/api/*', [require('./middlewares/validateRequest')]);
******
app.use('/api/public', publicRouter);
******
app.listen(port, function (err) {
if (!err) console.log('Find me on port ' + port + ' and say "Hello"');
else console.log(err);
});
auth.js
var User = require('./../models/user.js');
var Promise = require('bluebird');
var redis = require("./../providers/redis.js");
var util = require('util');
var auth = {
login: function (req, res) {
var login = req.body.login || '';
var password = req.body.password || '';
if (login === '') {
res.status(401);
res.json({
"status": 401,
"message": "login required"
});
return;
}
if (password === '') {
res.status(401);
res.json({
"status": 401,
"message": "password required"
});
return;
}
User.login(login, password)
.then(function (user) {
if (!user) {
res.status(401);
res.json({
"status": 401,
"message": "Incorrect login data."
});
}
return redis.get(util.format("usersess:%s", user.id))
.then(function (currentSession) {
if (currentSession === null) {
redis.set(util.format("usersess:%s", user.id), req.session.id)
.then(function () {
delete user.password;
req.session.user = user;
res.json({
"status": 200,
"message": "User successfully logged in."
});
});
} else {
if (currentSession !== req.session.id) {
return redis.del(util.format("sess:%s", currentSession))
.then(function () {
return redis.set(util.format("usersess:%s", user.id), req.session.id);
})
.then(function () {
delete user.password;
req.session.user = user;
res.json({
"status": 200,
"message": "User successfully logged in."
});
})
} else {
res.json({
"status": 200,
"message": "User successfully logged in."
});
}
}
})
})
.catch(function (err) {
console.log(err);
res.status(500);
res.json({
error: true,
data: {
message: err.message
}
});
});
},
logout: function (req, res) {
req.session.destroy(function (err) {
if (err) {
console.log("Can't destroy the session. See details below");
console.log(err);
res.status(500);
res.json({
"status": 500,
"message": err.message
})
} else {
res.status(200);
res.json({
"status": 200,
"message": "User successfully logged out."
})
}
});
}
};
module.exports = auth;
user model user.js
var Promise = require('bluebird'),
bcrypt = Promise.promisifyAll(require('bcrypt')),
db = require("./../providers/db.js");
var User = {
tableName: 'users',
login: function (login, password) {
if (!login || !password) throw new Error('login and password are both required');
return db.execStoredProcedure("user_get_by_login", [login.trim()])
.then(
function (rows) {
var user = rows[0][0];
return bcrypt.compareAsync(password, user.password)
.then(function (res) {
if (!res) user = null;
return user;
});
}
);
}
};
module.exports = User;
redis provider redis.js
var config = require('./../providers/config');
var Promise = require("bluebird"),
redis = require('promise-redis')(function(resolver) {
return new Promise(resolver);
}),
redisClient = redis.createClient(config.redis.port, config.redis.host),
util = require('util');
redisClient.on('connect', function () {
console.log(util.format('redis connected on %s:%s', config.redis.host, config.redis.port));
});
module.exports = redisClient;
I was unable to find the exact reason why some sessions are not deleted but after a lot of debugging and logs investigating I think it is due to node async nature. While mySQL getting operation require some time, some login actions could run in parallel and get the same values for current user session_id.
To solve this, I created middleware that check if current user session id is in redis store, if it is not--it just destroys the session and logout user asking for a new login attempt. This may be not a good solution but it completely solved the original issue.
So I'm currently learning Sails.js through following some tutorials and I have encountered this problem many times now.
I tried searching for the solutions but none of them seemed to work.
module.exports = {
signup: function(req, res) {
var username = req.param("username");
var password = req.param("password");
Users.findByUsername(username).done(function(err, usr){
if (err) {
res.send(500, { error: "DB Error" });
} else if (usr) {
res.send(400, {error: "Username already Taken"});
} else {
var hasher = require("password-hash");
password = hasher.generate(password);
Users.create({username: username, password: password}).done(function(error, user) {
if (error) {
res.send(500, {error: "DB Error"});
} else {
req.session.user = user;
res.send(user);
}
});
}
});
}
}
The problems seems to be on this line, it says that:
undefined is not a function.
Users.findByUsername(username).done(function(err, usr)
It seems to me that the problem is on the .done because when I try to make it like this:
Users.findByUsername(username, function(err, usr)
it works.
This is my Users.js under model
module.exports = {
attributes: {
username: 'STRING',
password: 'STRING'
}
};
Any idea on how to fix this?
Also in chrome console the status code is :
500 Internal Server Error
Where could be the problem?
May be you are following some tutorial which is written for previous version of SailsJS. In previous version there was a function named done, which is replaced by exec in newer versions. Try replacing done with exec. Then the code will be like,
module.exports = {
signup: function(req, res) {
var username = req.param("username");
var password = req.param("password");
Users.findByUsername(username).exec(function(err, usr){
if (err) {
res.send(500, { error: "DB Error" });
} else if (usr) {
res.send(400, {error: "Username already Taken"});
} else {
var hasher = require("password-hash");
password = hasher.generate(password);
Users.create({username: username, password: password}).done(function(error, user) {
if (error) {
res.send(500, {error: "DB Error"});
} else {
req.session.user = user;
res.send(user);
}
});
}
});
}
}
This is a common problem caused by the fact that the .done() function is now deprecated in sails.js and it's probably removed but you can replace it with .exec()function.
That's why the Exception TypeError: Undefined is not a function is raised here, because this function does no longer exists and belongs to old versions of sails.js and it's now Deprecated.
You can find more abiout it in this discussion here.
So your code should be:
Users.findByUsername(username).exec(function(err, usr){