Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solve #482 Let developer use default authenticate step or override it. #483

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ So far, `everyauth` enables you to login via:
<tr> <td> <img src="https://github.com/bnoguchi/everyauth/raw/master/media/openid.ico" style="vertical-align:middle" width="16px" height="16px"> OpenId <td> <a href="https://github.com/rocketlabsdev">RocketLabs Development</a>, <a href="https://github.com/starfishmod">Andrew Mee, <a href="https://github.com/bnoguchi">Brian Noguchi</a>
<tr> <td> LDAP / ActiveDirectory <td> <a href="https://github.com/marek-obuchowicz">Marek Obuchowicz</a> from <a href="https://www.korekontrol.eu/">Korekontrol</a>
<tr> <td> Windows Azure Access Control Service (ACS)<td> <a href="https://github.com/darrenzully">Dario Renzulli</a>, <a href="https://github.com/jpgarcia">Juan Pablo Garcia</a>, <a href="https://github.com/woloski">Matias Woloski</a> from <a href="http://blogs.southworks.net/">Southworks</a>
<tr> <td> LDAP / ActiveDirectory <br> (using [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork))
<td> [Aaron Calderon](https://github.com/aaroncalderon) from Planet Earth
</tbody>
</table>

Expand Down Expand Up @@ -2282,6 +2284,169 @@ connect(
).listen(3000);
```

### LDAPFORK

The `ldapfork` module is not tested throughly yet, however it inherits all the qualities from the `ldap` module plus some enhansements. Feedback is very welcome.

I was not requiered to do this on windows.
Install OpenLDAP client libraries:

$ sudo apt-get install ldap-utils

We are installing a different `ldapauth`
Install [node-ldapauth-fork](https://github.com/vesse/node-ldapauth-fork):

This is where things change a little bit.

#### Option 1

Pretty much you can use the same setup as with `ldap`

```javascript
var everyauth = require('everyauth')
, connect = require('connect');

everyauth.ldapfork
.ldapUrl('ldap(s)://your.ldap.host') // check if you need ldap or ldaps
.adminDn('DN for bind')
.adminPassword('Password for bind user')
.searchBase('e.g. ou=users,dc=example,dc=com')
.searchFilter('e.g. (uid={{username}})')
.requireGroupDn('e.g. cn=Administrators,ou=Groups,dc=example,dc=com')

// The `ldap` module inherits from the `password` module, so
// refer to the `password` module instructions several sections above
// in this README.
// You do --not-- need to configure the `authenticate` step as instructed
// by `password` because the `ldap` module --already-- does [not] that for you.
// in this _fork_ of this module, I have chossen to allow the authenticate step
// to be __optional__. So, you cah use the the default, or clone it if you need
// special handling of the authentication. See below... [USERID]
// Moreover, all the registration related steps and configurable parameters
// are no longer valid
.getLoginPath(...)
.postLoginPath(...)
.loginView(...)
.loginSuccessRedirect(...);

var routes = function (app) {
// Define your routes here
};

connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);
```
#### Option 2

You can override the authenticate method:

```javascript
everyauth.ldapfork
.ldapUrl('ldap://your.ldap.host:andport')
.adminDn('DN for bind')
.adminPassword('Password for bind user')
.searchBase('e.g. ou=users,dc=example,dc=com')
.searchFilter('e.g. (uid={{username}})')
.requireGroupDn('e.g. cn=Administrators,ou=Groups,dc=example,dc=com')

// The `ldap` module inherits from the `password` module, so
// refer to the `password` module instructions several sections above
// in this README.
// You can to configure the `authenticate` step as instructed
// by `password` because the `ldapfork` module lets you customize it.
// in this _fork_ of this module, I have chossen to allow the authenticate step
// to be __optional__. So, you can use the the default, or override it if you need
// special handling of the authentication. See below... [USERID]
// Moreover, all the registration related steps and configurable parameters
// are no longer valid
.loginWith('email')
.loginFormFieldName('login') // Defaults to 'login'
.passwordFormFieldName('password') // Defaults to 'password'
.getLoginPath('/login')
.postLoginPath('/login')
.loginView('login.jade')
.loginLocals( function (req, res, done) {
setTimeout( function () {
done(null, {
title: 'Async login',
everyauth: everyauth
});
}, 200);
})
.loginSuccessRedirect('/')
.authenticate( function (login, password, req, res) {
var promise = this.Promise();
var ldapauth = this.ldapAuth;

ldapauth.authenticate(login, password, function (err, result) {
var user, errors;
if (err) {
// return promise.fail(err);
// debug

if (typeof err == 'string') {
return promise.fulfill(['LDAP Error: ' + err]);
} else {
return promise.fulfill(['LDAP Error: ' + err.message]);
}
}
if (result === false) {
errors = ['Login failed.'];
return promise.fulfill(errors);
} else if (typeof result == 'object') {
// RESULT.UID
// in my case I was not able to use the comparison `result.uid == login`
// since in my organization, there is no `uid` assigned to the users
// instead I used the `mail` and I used the `.toLowerCase()` to make
// sure my app is abit more flexible, sice emails are saved with Camel
// case, but it is not for sure that the user will use a cammel case
// email address, ussualy they use lowercase.
if (result.uid == login || result.mail.toLowerCase() == login.toLowerCase()) {
user = {};
user['id'] = login;
console.log("LDAP: positive authorization for user " + login + "")
userAttributes = {
name: result.name,
eID: result.employeeID,
permissions: result.memberOf,
dpt: result.description
}
// lets add the user
// I am not sure if this is supposed to happen here or not
// but for my testing it worked ok so far, so I left it there
user[login] = addUser(login, userAttributes);

return promise.fulfill(user);
} else {
return promise.fulfill(['LDAP Error: result does not match username', result])
}
} else {
console.log('ldapauth returned an unknown result:');
console.log(result);
return promise.fulfill(['Unknown ldapauth response']);
}
});
return promise;
});

var routes = function (app) {
// Define your routes here
};

connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);
```

### Windows Azure Access Control Service (ACS)

You will need to create a [Windows Azure ACS namespace](http://msdn.microsoft.com/en-us/library/windowsazure/hh674478.aspx). The only caveat when creating the namespace is setting the "Return URL". You will probably [create one Relying Party](http://msdn.microsoft.com/en-us/library/windowsazure/gg429779.aspx) for each environment (dev, qa, prod) and each of them will have a different "Return URL". For instance, dev will be `http://localhost:port/auth/azureacs/callback` and prod could be `https://myapp.com/auth/azureacs/callback` (notice the `/auth/azureacs/callback`, that's where the module will listen the POST with the token from ACS)
Expand Down
71 changes: 71 additions & 0 deletions lib/modules/ldapfork.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
var passwordModule = require('./password');
var LdapAuth = require('ldapauth-fork');

var ldap = module.exports =
passwordModule.submodule('ldapfork')
.definit( function () {
this.ldapAuth = new LdapAuth({
url: this._ldapUrl,
adminDn: this._adminDn,
adminPassword: this._adminPassword,
searchBase: this._searchBase,
searchFilter: this._searchFilter,
requireGroupDn: this._requireGroupDn,
cache: true
});
})
.configurable({
ldapUrl: 'ldap server url'
, adminDn: 'ldap bind dn'
, adminPassword: 'ldap bind password'
, searchBase: 'ldap search base'
, searchFilter: 'ldap search filter'
, requireGroupDn: 'ldap (option) required group DN'
})
.authenticate( function (login, password, req, res) {
var promise = this.Promise();
var ldapauth = this.LdapAuth;

ldapauth.authenticate(login, password, function (err, result) {
var user, errors;
if (err) {
// return promise.fail(err);
if (typeof err == 'string') {
return promise.fulfill(['LDAP Error: ' + err]);
} else {
return promise.fulfill(['LDAP Error: ' + err.message]);
}
}
if (result === false) {
errors = ['Login failed.'];
return promise.fulfill(errors);
} else if (typeof result == 'object') {
if (result.uid == login) {
user = {};
user['id'] = login;
console.log("LDAP: positive authorization for user " + result.uid + "")
return promise.fulfill(user);
} else {
return promise.fulfill(['LDAP Error: result does not match username', result])
}
} else {
console.log('ldapauth returned an unknown result:');
console.log(result);
return promise.fulfill(['Unknown ldapauth response']);
}
});
return promise;
})
.addToSession( function (sess, user, errors) {
var _auth = sess.auth || (sess.auth = {});
if (user)
_auth.ldapfork = {
userId: user[this._userPkey]
};
_auth.loggedIn = !!user;
})
.getRegisterPath('/nonexistant/register')
.postRegisterPath('/nonexistant/register')
.registerUser( function (newUserAttributes) {});

;