Skip to content

Commit

Permalink
Merge branch 'Korcett-master'
Browse files Browse the repository at this point in the history
Closes #35
  • Loading branch information
vesse committed Oct 4, 2015
2 parents 68351cb + 5950e80 commit 42c1ea6
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 11 deletions.
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Currently the latest released version of [ldapjs](https://github.com/mcavage/nod

This also comes form `ldapjs` (see [issue #258](https://github.com/mcavage/node-ldapjs/issues/258)), and the same workaround solves it.

### Microsoft AD LDAP protocol

Error 49 handles much more than just Invalid credentials, partial support for MS AD LDAP has been implemented (see [issue #35](https://github.com/vesse/passport-ldapauth/issues/35)). Any additional supported data/comments could be added in the future.

## Install

```
Expand Down Expand Up @@ -75,12 +79,22 @@ Use `passport.authenticate()`, specifying the `'ldapauth'` strategy, to authenti

#### `authenticate()` options

In addition to [default authentication options](http://passportjs.org/guide/authenticate/) the following options are available for `passport.authenticate()`:
In addition to [default authentication options](http://passportjs.org/guide/authenticate/) the following flash message options are available for `passport.authenticate()`:

* `badRequestMessage`: missing username/password (default: 'Missing credentials')
* `invalidCredentials`: `InvalidCredentialsError`, `NoSuchObjectError`, and `/no such user/i` LDAP errors (default: 'Invalid username/password')
* `userNotFound`: LDAP returns no error but also no user (default: 'Invalid username/password')
* `constraintViolation`: user account is locked (default: 'Exceeded password retry limit, account locked')

And for [Microsoft AD messages](http://www-01.ibm.com/support/docview.wss?uid=swg21290631), these flash message options can also be used (used instead of `invalidCredentials` if matching error code is found):

* `badRequestMessage` flash message for missing username/password (default: 'Missing credentials')
* `invalidCredentials` flash message for `InvalidCredentialsError`, `NoSuchObjectError`, and `/no such user/i` LDAP errors (default: 'Invalid username/password')
* `userNotFound` flash message when LDAP returns no error but also no user (default: 'Invalid username/password')
* `constraintViolation` flash message when user account is locked (default: 'Exceeded password retry limit, account locked')
* `invalidLogonHours`: not being allowed to login at this current time (default: 'Not Permitted to login at this time')
* `invalidWorkstation`: not being allowed to login from this current location (default: 'Not permited to logon at this workstation')
* `passwordExpired`: expired password (default: 'Password expired')
* `accountDisabled`: disabled account (default: 'Account disabled')
* `accountExpired`: expired account (default: 'Account expired')
* `passwordMustChange`: password change (default: 'User must reset password')
* `accountLockedOut`: locked out account (default: 'User account locked')

## Express example

Expand Down
27 changes: 26 additions & 1 deletion lib/passport-ldapauth/strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,38 @@ var handleAuthentication = function(req, options) {
return this.fail({message: options.badRequestMessage || 'Missing credentials'}, 400);
}

/**
* AD possible messages
* http://www-01.ibm.com/support/docview.wss?uid=swg21290631
*/
var messages = {
'530': options.invalidLogonHours || 'Not Permitted to login at this time',
'531': options.invalidWorkstation || 'Not permited to logon at this workstation',
'532': options.passwordExpired || 'Password expired',
'533': options.accountDisabled || 'Account disabled',
'534': options.accountDisabled || 'Account disabled',
'701': options.accountExpired || 'Account expired',
'773': options.passwordMustChange || 'User must reset password',
'775': options.accountLockedOut || 'User account locked',
default: options.invalidCredentials || 'Invalid username/password'
};

ldap = new LdapAuth(this.options.server);
ldap.authenticate(username, password, function(err, user) {
ldap.close(function(){}); // We don't care about the closing

if (err) {
// Invalid credentials / user not found are not errors but login failures
if (err.name === 'InvalidCredentialsError' || err.name === 'NoSuchObjectError' || (typeof err === 'string' && err.match(/no such user/i))) {
return this.fail({message: options.invalidCredentials || 'Invalid username/password'}, 401);
var message = options.invalidCredentials || 'Invalid username/password';

if(err.message) {
var ldapComment = err.message.match(/data ([0-9a-fA-F]*), v[0-9a-fA-F]*/);
if(ldapComment && ldapComment[1]){
message = messages[ldapComment[1]] || messages['default'];
}
}
return this.fail({message: message}, 401);
}
if (err.name === 'ConstraintViolationError'){
return this.fail({message: options.constraintViolation || 'Exceeded password retry limit, account locked'}, 401);
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@
"body-parser": "1.12.x",
"chai": "2.1.x",
"express": "4.9.x",
"express-session": "^1.11.3",
"ldapjs": "0.7.x",
"mocha": "2.1.x",
"supertest": "0.15.x",
"passport": "~0.2.0"
"passport": "~0.2.0",
"supertest": "0.15.x"
},
"scripts": {
"test": "NODE_PATH=lib mocha --reporter spec test/*-test.js"
Expand Down
28 changes: 27 additions & 1 deletion test/appserver.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
var express = require('express'),
passport = require('passport'),
LdapStrategy = require('passport-ldapauth').Strategy,
bodyParser = require('body-parser');
bodyParser = require('body-parser'),
session = require('express-session');

var server = null;

Expand All @@ -15,19 +16,44 @@ var init_passport = function(opts, testopts) {
}
};

passport.serializeUser(function(user, cb) {
cb(null, user.dn);
});

passport.deserializeUser(function(dn, cb) {
cb(null, {dn: dn});
});

exports.start = function(opts, testopts, cb) {

var app = express();

init_passport(opts, testopts);

app.use(bodyParser.json());
app.use(session({
secret: 'cat',
saveUninitialized: false,
resave: false
}));
app.use(passport.initialize());
app.use(passport.session());

app.post('/login', passport.authenticate('ldapauth', {session: false}), function(req, res) {
res.send({status: 'ok'});
});

app.post('/custom-cb-login', function(req, res, next) {
passport.authenticate('ldapauth', function(err, user, info) {
if (err) return next(err);
if (!user) return res.status(401).send(info);
req.logIn(user, function(err) {
if (err) return next(err);
return res.json(user);
})
})(req, res, next);
});

if (typeof cb === 'function') return cb(app);
return;
};
Expand Down
2 changes: 2 additions & 0 deletions test/ldapserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ exports.start = function(port, cb) {
server.search(SUFFIX, authorize, function(req, res, next) {
if (req.filter.attribute === 'uid' && req.filter.value === 'valid') {
res.send(db['valid']);
} else if (req.filter.attribute === 'uid' && req.filter.value === 'ms-ad') {
return next(new ldap.InvalidCredentialsError("0090308: LdapErr: DSID-0C09030B, comment: AcceptSecurityContext error, data 533, v893 HEX: 0x533 - account disabled"));
} else if (req.filter.attribute === 'member' && req.filter.value === db.valid.dn) {
res.send({
dn: 'cn=Group 1, ou=passport-ldapauth',
Expand Down
16 changes: 14 additions & 2 deletions test/strategy-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,30 @@ describe("LDAP authentication strategy", function() {
it("should return unauthorized with invalid credentials", function(cb) {
request(expressapp)
.post('/login')
.send({username: 'valid', password: 'invvalid'})
.send({username: 'valid', password: 'invalid'})
.expect(401)
.end(cb);
});

it("should return unauthorized with non-existing user", function(cb) {
request(expressapp)
.post('/login')
.send({username: 'nonexisting', password: 'invvalid'})
.send({username: 'nonexisting', password: 'invalid'})
.expect(401)
.end(cb);
});

it("should return more specific flash message for AD reply", function(cb) {
request(expressapp)
.post('/custom-cb-login')
.send({username: 'ms-ad', password: 'invalid'})
.expect(401)
.end(function(err, res) {
should.not.exist(err);
res.body.message.should.equal('Account disabled')
cb(err, res);
});
});
});

describe("without a verify callback", function() {
Expand Down

0 comments on commit 42c1ea6

Please sign in to comment.