diff --git a/README.md b/README.md index b81c84ab..fc74d57d 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ So far, `everyauth` enables you to login via: OpenId RocketLabs Development, Andrew Mee, Brian Noguchi LDAP / ActiveDirectory Marek Obuchowicz from Korekontrol Windows Azure Access Control Service (ACS) Dario Renzulli, Juan Pablo Garcia, Matias Woloski from Southworks + LDAP / ActiveDirectory
(using [ldapauth-fork](https://github.com/vesse/node-ldapauth-fork)) + [Aaron Calderon](https://github.com/aaroncalderon) from Planet Earth @@ -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) diff --git a/lib/modules/ldapfork.js b/lib/modules/ldapfork.js new file mode 100644 index 00000000..df9f4077 --- /dev/null +++ b/lib/modules/ldapfork.js @@ -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) {}); + +;