Browse files

Added Node LDAP authentication library.

  • Loading branch information...
0 parents commit 57364be5a5b34236b4459112a3c45928150c32c3 @joewalnes committed Aug 14, 2010
Showing with 238 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +41 −0 README.md
  3. +22 −0 example.js
  4. +157 −0 ldapauth.cc
  5. +16 −0 wscript
2 .gitignore
@@ -0,0 +1,2 @@
+.lock-wscript
+build
41 README.md
@@ -0,0 +1,41 @@
+LDAP Authentication for Node.JS
+===============================
+
+Provides a simple function for validating username/password credentials
+on an LDAP server.
+
+This can be used in web-applications that authenticate users from a central directory.
+
+It binds to the native OpenLDAP library (libldap) and calls ldap_simple_bind().
+
+For more info, see:
+
+ man 3 ldap_bind
+
+Building
+--------
+
+ node-waf configure build
+
+Usage
+-----
+
+Ensure ldapauth.node is in your node path.
+
+ var ldapauth = require('ldapauth');
+ ldapauth.authenticate('some.host', 389 /*port*/, 'someuser', 'somepassword',
+ function(err, result) {
+ if (err) {
+ print('Error');
+ } else {
+ print('Credentials valid = ' + result); // true or false
+ }
+ });
+
+Resources
+---------
+
+http://nodejs.org/
+http://www.openldap.org/
+
+*2010, Joe Walnes, joe@walnes.com, http://joewalnes.com/*
22 example.js
@@ -0,0 +1,22 @@
+#!/usr/bin/env node
+
+// Include build path
+require.paths.unshift('build/default');
+
+var sys = require('sys'),
+ ldapauth = require('ldapauth');
+
+var ldap_host = 'ldap.mycompany.com',
+ ldap_port = 389,
+ username = 'someuser',
+ password = 'somepass';
+
+sys.puts('Before authenticate()');
+
+ldapauth.authenticate(ldap_host, ldap_port, username, password,
+ function(err, result) {
+ sys.puts('Callback from authenticate(): ' + err + ',' + result);
+ });
+
+sys.puts('After authenticate()');
+
157 ldapauth.cc
@@ -0,0 +1,157 @@
+// Provides Node.JS binding for ldap_simple_bind().
+// See README
+// 2010, Joe Walnes, joe@walnes.com, http://joewalnes.com/
+
+
+/*
+Here's the basic flow of events. LibEIO is used to ensure that
+the LDAP calls occur on a background thread and do not block
+the main Node event loop.
+
+ +----------------------+ +------------------------+
+ | Main Node Event Loop | | Background Thread Pool |
+ +----------------------+ +------------------------+
+
+ User application
+ |
+ V
+ JavaScript: authenticate()
+ |
+ V
+ ldapauth.cc: Authenticate()
+ |
+ +-------------------------> libauth.cc: EIO_Authenticate()
+ | |
+ V V
+ (user application carries ldap_simple_bind()
+ on doing its stuff) |
+ | (wait for response
+ (no blocking) from server)
+ | |
+ (sometime later) (got response)
+ | |
+ ldapauth.cc: EIO_AfterAuthenticate() <----------+
+ |
+ V
+Invoke user supplied JS callback
+
+*/
+
+#include <v8.h>
+#include <node.h>
+#include <node_events.h>
+#include <ldap.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+using namespace v8;
+
+#define THROW(message) ThrowException(Exception::TypeError(String::New(message)))
+
+// Data passed between threads
+struct auth_request
+{
+ // Input params
+ char *host;
+ int port;
+ char *username;
+ char *password;
+ // Callback function
+ Persistent<Function> callback;
+ // Result
+ bool connected;
+ bool authenticated;
+};
+
+// Runs on background thread, performing the actual LDAP request.
+static int EIO_Authenticate(eio_req *req)
+{
+ struct auth_request *auth_req = (struct auth_request*)(req->data);
+
+ // Node: OpenLDAP does actually provide _some_ async API calls,
+ // But ldap_open does NOT have an async equivalent, so we have to
+ // do this in a background thread. Seeing as we're in a background
+ // thread anyway, it's just simpler to call the rest of the calls
+ // synchronously.
+
+ // Connect to LDAP server
+ LDAP *ldap = ldap_open(auth_req->host, auth_req->port);
+ if (ldap == NULL) {
+ auth_req->connected = false;
+ auth_req->authenticated = false;
+ } else {
+ // Bind with credentials, passing result into auth_request struct
+ int ldap_result = ldap_simple_bind_s(ldap, auth_req->username, auth_req->password);
+ // Disconnect
+ ldap_unbind(ldap);
+
+ auth_req->connected = true;
+ auth_req->authenticated = (ldap_result == LDAP_SUCCESS);
+ }
+
+ return 0;
+}
+
+// Called on main event loop when background thread has completed
+static int EIO_AfterAuthenticate(eio_req *req)
+{
+ ev_unref(EV_DEFAULT_UC);
+ HandleScope scope;
+ struct auth_request *auth_req = (struct auth_request *)(req->data);
+
+ // Invoke callback JS function
+ Handle<Value> callback_args[2];
+ callback_args[0] = auth_req->connected ? (Handle<Value>)Undefined() : Exception::Error(String::New("LDAP connection failed"));
+ callback_args[1] = Boolean::New(auth_req->authenticated);
+ auth_req->callback->Call(Context::GetCurrent()->Global(), 2, callback_args);
+
+ // Cleanup auth_request struct
+ auth_req->callback.Dispose();
+ free(auth_req);
+
+ return 0;
+}
+
+// Exposed authenticate() JavaScript function
+static Handle<Value> Authenticate(const Arguments& args)
+{
+ HandleScope scope;
+
+ // Validate args.
+ if (args.Length() < 5) return THROW("Required arguments: ldap_host, ldap_port, username, password, callback");
+ if (!args[0]->IsString()) return THROW("ldap_host should be a string");
+ if (!args[1]->IsInt32()) return THROW("ldap_port should be a string");
+ if (!args[2]->IsString()) return THROW("username should be a string");
+ if (!args[3]->IsString()) return THROW("password should be a string");
+ if (!args[4]->IsFunction()) return THROW("callback should be a function");
+
+ // Input params.
+ String::Utf8Value host(args[0]);
+ int port = args[1]->Int32Value();
+ String::Utf8Value username(args[2]);
+ String::Utf8Value password(args[3]);
+ Local<Function> callback = Local<Function>::Cast(args[4]);
+
+ // Store all parameters in auth_request struct, which shall be passed across threads.
+ struct auth_request *auth_req = (struct auth_request*) calloc(1, sizeof(struct auth_request));
+ auth_req->host = strdup(*host);
+ auth_req->port = port;
+ auth_req->username = strdup(*username);
+ auth_req->password = strdup(*password);
+ auth_req->callback = Persistent<Function>::New(callback);
+
+ // Use libeio to invoke EIO_Authenticate() in background thread pool
+ // and call EIO_AfterAuthententicate in the foreground when done
+ eio_custom(EIO_Authenticate, EIO_PRI_DEFAULT, EIO_AfterAuthenticate, auth_req);
+ ev_ref(EV_DEFAULT_UC);
+
+ return Undefined();
+}
+
+// Entry point for native Node module
+extern "C" void
+init (Handle<Object> target)
+{
+ HandleScope scope;
+ target->Set(String::New("authenticate"), FunctionTemplate::New(Authenticate)->GetFunction());
+}
16 wscript
@@ -0,0 +1,16 @@
+srcdir = '.'
+blddir = 'build'
+VERSION = '0.0.1'
+
+def set_options(opt):
+ opt.tool_options('compiler_cxx')
+
+def configure(conf):
+ conf.check_tool('compiler_cxx')
+ conf.check_tool('node_addon')
+
+def build(bld):
+ obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
+ obj.cxxflags = ['-lldap', '-DLDAP_DEPRECATED']
+ obj.target = 'ldapauth'
+ obj.source = 'ldapauth.cc'

0 comments on commit 57364be

Please sign in to comment.