New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

ldapauth-fork

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ldapauth-fork - npm Package Compare versions

Comparing version 3.0.1 to 4.0.0

.eslintrc.json

6

CHANGES.md
# node-ldapauth-fork Changelog
## 4.0.0
- Added TypeScript types
- Switch to Bunyan logger since ldapjs uses Bunyan as well
- Pass all ldapjs client options to it. The available options were taken from the ldapjs TypeScript types.
## 3.0.1

@@ -4,0 +10,0 @@

48

lib/cache.js

@@ -15,3 +15,2 @@ /*

var debug = console.warn;
var assert = require('assert');

@@ -24,7 +23,7 @@ var LRU = require('lru-cache');

*
* @param size {Number} Max number of entries to cache.
* @param expiry {Number} Number of seconds after which to expire entries.
* @param log {log4js Logger} Optional.
* All logging is at the Trace level.
* @param name {string} Optional name for this cache. Just used for logging.
* @param {number} size Max number of entries to cache.
* @param {number} expiry Number of seconds after which to expire entries.
* @param {object} log Optional. All logging is at the Trace level.
* @param {string} name Optional name for this cache. Just used for logging.
* @constructor
*/

@@ -41,7 +40,7 @@ function Cache(size, expiry, log, name) {

// Debugging stuff: .getAll isn't in official lru-cache
//Cache.prototype.getAll = function getAll() {
// return this.items.getAll();
//}
/**
* Clear cache
*
* @returns {undefined}
*/
Cache.prototype.reset = function reset() {

@@ -52,4 +51,10 @@ if (this.log) {

this.items.reset();
}
};
/**
* Get object from cache by given key
*
* @param {string} key - The cache key
* @returns {*} The cached value or null if not found
*/
Cache.prototype.get = function get(key) {

@@ -70,4 +75,11 @@ assert.ok(key !== undefined);

return null;
}
};
/**
* Set a value to cache
*
* @param {string} key - Cache key
* @param {*} value - The value to cache
* @returns {*} The given value
*/
Cache.prototype.set = function set(key, value) {

@@ -84,4 +96,10 @@ assert.ok(key !== undefined);

return item;
}
};
/**
* Delete a single entry from cache
*
* @param {string} key - The cache key
* @returns {undefined}
*/
Cache.prototype.del = function del(key) {

@@ -92,5 +110,5 @@ if (this.log) {

this.items.del(key);
}
};
module.exports = Cache;
/**
* Copyright 2011 (c) Trent Mick.
* Modified Work Copyright 2013 Vesa Poikajärvi.
*

@@ -10,10 +11,8 @@ * LDAP auth.

* ...
* auth.authenticate(username, password, function (err, user) { ... });
* auth.authenticate(username, password, function(err, user) { ... });
* ...
* auth.close(function (err) { ... })
* auth.close(function(err) { ... })
*/
var assert = require('assert');
var ldap = require('ldapjs');
var debug = console.warn;
var format = require('util').format;

@@ -24,4 +23,24 @@ var bcrypt = require('bcryptjs');

// Get option that may be defined under different names, but accept
// the first one that is actually defined in the given object
/**
* Void callback
*
* @callback voidCallback
* @param {(Error|undefined)} Possible error
*/
/**
* Result callback
*
* @callback resultCallback
* @param {(Error|undefined)} Possible error
* @param {(Object|undefined)} Result
*/
/**
* Get option that may be defined under different names, but accept
* the first one that is actually defined in the given object
*
* @param {object} obj - Config options
* @param {string[]} keys - List of keys to look for
* @return {*} The value of the first matching key
*/
var getOption = function(obj, keys) {

@@ -39,71 +58,4 @@ for (var i = 0; i < keys.length; i++) {

*
* @param opts {Object} Config options. Keys (required, unless says
* otherwise) are:
* url {String}
* E.g. 'ldaps://ldap.example.com:663'
* bindDn {String}
* Optional, e.g. 'uid=myapp,ou=users,o=example.com'. Alias: adminDn
* bindCredentials {String}
* Password for bindDn. Aliases: Credentials, adminPassword
* bindProperty {String}
* Optional, default 'dn'. Property of user to bind against client
* e.g. 'name', 'email'
* searchBase {String}
* The base DN from which to search for users by username.
* E.g. 'ou=users,o=example.com'
* searchScope {String}
* Optional, default 'sub'. Scope of the search, one of 'base',
* 'one', or 'sub'.
* searchFilter {String}
* LDAP search filter with which to find a user by username, e.g.
* '(uid={{username}})'. Use the literal '{{username}}' to have the
* given username be interpolated in for the LDAP search.
* searchAttributes {Array}
* Optional, default all. Array of attributes to fetch from LDAP server.
* groupDnProperty {String}
* Optional, default 'dn'. The property of user object to use in
* '{{dn}}' interpolation of groupSearchFilter.
* groupSearchBase {String}
* Optional. The base DN from which to search for groups. If defined,
* also groupSearchFilter must be defined for the search to work.
* groupSearchScope {String}
* Optional, default 'sub'.
* groupSearchFilter {String | function(User): String }
* Optional. LDAP search filter for groups. The following literals are
* interpolated from the found user object: '{{dn}}' the property
* configured with groupDnProperty. Optionally you can also assign a function instead,
* which passes a user object, from this a dynamic groupsearchfilter can be retrieved.
* groupSearchAttributes {Array}
* Optional, default all. Array of attributes to fetch from LDAP server.
* log4js {Module}
* Optional. The require'd log4js module to use for logging. If given
* this will result in TRACE-level logging for ldapauth.
* verbose {Boolean}
* Optional, default false. If `log4js` is also given, this will add
* TRACE-level logging for ldapjs (quite verbose).
* cache {Boolean}
* Optional, default false. If true, then up to 100 credentials at a
* time will be cached for 5 minutes.
* timeout {Integer}
* Optional, default Infinity. How long the client should let
* operations live for before timing out.
* connectTimeout {Integer}
* Optional, default is up to the OS. How long the client should wait
* before timing out on TCP connections.
* idleTimeout {Integer}
* Optional, milliseconds after last activity before client emits idle event.
* queueDisable {Boolean}
* Optional, disables the queue in LDAPJS making connection requests instantly fail
* instead of sitting in the queue with no timeout.
* tlsOptions {Object}
* Additional options passed to the TLS connection layer when
* connecting via ldaps://. See
* http://nodejs.org/api/tls.html#tls_tls_connect_options_callback
* for available options
* includeRaw {boolean}
* Optional, default false. Set to true to add property '_raw'
* containing the original buffers to the returned user object.
* Useful when you need to handle binary attributes
* reconnect {object}
* Optional, node-ldap reconnect option.
* @param {Object} opts - Config options
* @constructor
*/

@@ -115,3 +67,3 @@ function LdapAuth(opts) {

this.log = opts.log4js && opts.log4js.getLogger('ldapauth');
this.log = opts.log && opts.log.child({ component: 'ldapauth' }, true);

@@ -126,21 +78,28 @@ this.opts.searchScope || (this.opts.searchScope = 'sub');

if (opts.cache) {
// eslint-disable-next-line global-require
var Cache = require('./cache');
this.userCache = new Cache(100, 300, this.log, 'user');
this._salt = bcrypt.genSaltSync();
}
// TODO: This should be fixed somehow
this.clientOpts = {
url: opts.url,
tlsOptions: opts.tlsOptions,
socketPath: opts.socketPath,
log: opts.log,
timeout: opts.timeout,
connectTimeout: opts.connectTimeout,
timeout: opts.timeout,
idleTimeout: opts.idleTimeout,
queueDisable: opts.queueDisable,
tlsOptions: opts.tlsOptions,
bindDn: getOption(opts, ['bindDn', 'adminDn']),
bindCredentials: getOption(opts, ['bindCredentials', 'Credentials', 'adminPassword']),
reconnect: opts.reconnect
reconnect: opts.reconnect,
strictDN: opts.strictDN,
queueSize: opts.queueSize,
queueTimeout: opts.queueTimeout,
queueDisable: opts.queueDisable
};
if (opts.log4js && opts.verbose) {
this.clientOpts.log4js = opts.log4js;
}
// Not passed to ldapjs, don't want to autobind
// https://github.com/mcavage/node-ldapjs/blob/v1.0.1/lib/client/client.js#L343-L356
this.bindDN = getOption(opts, ['bindDn', 'bindDN', 'adminDn']);
this.bindCredentials = getOption(opts, ['bindCredentials', 'Credentials', 'adminPassword']);

@@ -154,6 +113,2 @@ this._adminClient = ldap.createClient(this.clientOpts);

if (opts.cache) {
this._salt = bcrypt.genSaltSync();
}
if (opts.groupSearchBase && opts.groupSearchFilter) {

@@ -171,14 +126,21 @@ if (typeof opts.groupSearchFilter === 'string') {

// the authenticate function to have cache set up.
this._getGroups = function (user, callback) {
this._getGroups = function(user, callback) {
return callback(null, user);
}
};
}
};
}
inherits(LdapAuth, EventEmitter);
LdapAuth.prototype.close = function (callback) {
/**
* Unbind connections
*
* @param {errorCallback} callback - Callback
* @returns {undefined}
*/
LdapAuth.prototype.close = function(callback) {
var self = this;
// It seems to be OK just to call unbind regardless of if the
// client has been bound (e.g. how ldapjs pool destroy does)
self._adminClient.unbind(function(err) {
self._adminClient.unbind(function() {
self._userClient.unbind(callback);

@@ -191,2 +153,5 @@ });

* Mark admin client unbound so reconnect works as expected and re-emit the error
*
* @param {Error} err - The error to be logged and emitted
* @returns {undefined}
*/

@@ -201,6 +166,9 @@ LdapAuth.prototype._handleError = function(err) {

* Ensure that `this._adminClient` is bound.
*
* @param {voidCallback} callback - Callback that checks possible error
* @returns {undefined}
*/
LdapAuth.prototype._adminBind = function (callback) {
LdapAuth.prototype._adminBind = function(callback) {
// Anonymous binding
if (typeof this.clientOpts.bindDn === 'undefined' || this.clientOpts.bindDn === null) {
if (typeof this.bindDN === 'undefined' || this.bindDN === null) {
return callback();

@@ -212,11 +180,14 @@ }

var self = this;
this._adminClient.bind(this.clientOpts.bindDn, this.clientOpts.bindCredentials,
function (err) {
if (err) {
self.log && self.log.trace('ldap authenticate: bind error: %s', err);
return callback(err);
this._adminClient.bind(
this.bindDN,
this.bindCredentials,
function(err) {
if (err) {
self.log && self.log.trace('ldap authenticate: bind error: %s', err);
return callback(err);
}
self._adminBound = true;
return callback();
}
self._adminBound = true;
return callback();
});
);
};

@@ -228,19 +199,25 @@

*
* @param searchBase {String} LDAP search base
* @param options {Object} LDAP search options
* @param {Function} `function (err, result)`.
* @param {string} searchBase - LDAP search base
* @param {Object} options - LDAP search options
* @param {string} options.filter - LDAP search filter
* @param {string} options.scope - LDAP search scope
* @param {(string[]|undefined)} options.attributes - Attributes to fetch
* @param {resultCallback} callback - The result handler callback
* @returns {undefined}
*/
LdapAuth.prototype._search = function (searchBase, options, callback) {
LdapAuth.prototype._search = function(searchBase, options, callback) {
var self = this;
self._adminBind(function (err) {
if (err)
return callback(err);
self._adminBind(function(bindErr) {
if (bindErr) {
return callback(bindErr);
}
self._adminClient.search(searchBase, options, function (err, result) {
if (err)
return callback(err);
self._adminClient.search(searchBase, options, function(searchErr, searchResult) {
if (searchErr) {
return callback(searchErr);
}
var items = [];
result.on('searchEntry', function (entry) {
searchResult.on('searchEntry', function(entry) {
items.push(entry.object);

@@ -252,5 +229,5 @@ if (self.opts.includeRaw === true) {

result.on('error', callback);
searchResult.on('error', callback);
result.on('end', function (result) {
searchResult.on('end', function(result) {
if (result.status !== 0) {

@@ -266,5 +243,12 @@ var err = 'non-zero status from LDAP search: ' + result.status;

// https://tools.ietf.org/search/rfc4515#section-3
var sanitizeInput = function (username) {
return username
/**
* Sanitize LDAP special characters from input
*
* {@link https://tools.ietf.org/search/rfc4515#section-3}
*
* @param {string} input - String to sanitize
* @returns {string} Sanitized string
*/
var sanitizeInput = function(input) {
return input
.replace(/\*/g, '\\2a')

@@ -281,15 +265,15 @@ .replace(/\(/g, '\\28')

*
* @param username {String}
* @param callback {Function} `function (err, user)`. If no such user is
* found but no error processing, then `user` is undefined.
*
* @param {string} username - Username to search for
* @param {resultCallback} callback - Result handling callback. If user is
* not found but no error happened, result is undefined.
* @returns {undefined}
*/
LdapAuth.prototype._findUser = function (username, callback) {
LdapAuth.prototype._findUser = function(username, callback) {
var self = this;
if (!username) {
return callback("empty username");
return callback(new Error('empty username'));
}
var searchFilter = self.opts.searchFilter.replace(/{{username}}/g, sanitizeInput(username));
var opts = {filter: searchFilter, scope: self.opts.searchScope};
var opts = { filter: searchFilter, scope: self.opts.searchScope };
if (self.opts.searchAttributes) {

@@ -299,3 +283,3 @@ opts.attributes = self.opts.searchAttributes;

self._search(self.opts.searchBase, opts, function (err, result) {
self._search(self.opts.searchBase, opts, function(err, result) {
if (err) {

@@ -310,3 +294,3 @@ self.log && self.log.trace('ldap authenticate: user search error: %s %s %s', err.code, err.name, err.message);

case 1:
return callback(null, result[0])
return callback(null, result[0]);
default:

@@ -320,6 +304,13 @@ return callback(format(

/**
* Find groups for given user
*
* @param {Object} user - The LDAP user object
* @param {resultCallback} callback - Result handling callback
* @returns {undefined}
*/
LdapAuth.prototype._findGroups = function(user, callback) {
var self = this;
if (!user) {
return callback("no user");
return callback(new Error('no user'));
}

@@ -329,7 +320,7 @@

var opts = {filter: searchFilter, scope: self.opts.groupSearchScope};
var opts = { filter: searchFilter, scope: self.opts.groupSearchScope };
if (self.opts.groupSearchAttributes) {
opts.attributes = self.opts.groupSearchAttributes;
}
self._search(self.opts.groupSearchBase, opts, function (err, result) {
self._search(self.opts.groupSearchBase, opts, function(err, result) {
if (err) {

@@ -346,9 +337,14 @@ self.log && self.log.trace('ldap authenticate: group search error: %s %s %s', err.code, err.name, err.message);

/**
* Authenticate given credentials against LDAP server
*
* @param {string} username - The username to authenticate
* @param {string} password - The password to verify
* @param {resultCallbac} callback - Result handling callback
* @returns {undefined}
*/
LdapAuth.prototype.authenticate = function (username, password, callback) {
LdapAuth.prototype.authenticate = function(username, password, callback) {
var self = this;
if (typeof password === 'undefined' || password === null || password === '') {
return callback('no password given');
return callback(new Error('no password given'));
}

@@ -360,3 +356,3 @@

if (cached && bcrypt.compareSync(password, cached.password)) {
return callback(null, cached.user)
return callback(null, cached.user);
}

@@ -366,29 +362,34 @@ }

// 1. Find the user DN in question.
self._findUser(username, function (err, user) {
if (err)
return callback(err);
if (!user)
self._findUser(username, function(findErr, user) {
if (findErr) {
return callback(findErr);
} else if (!user) {
return callback(format('no such user: "%s"', username));
}
// 2. Attempt to bind as that user to check password.
self._userClient.bind(user[self.opts.bindProperty], password, function (err) {
if (err) {
self.log && self.log.trace('ldap authenticate: bind error: %s', err);
return callback(err);
self._userClient.bind(user[self.opts.bindProperty], password, function(bindErr) {
if (bindErr) {
self.log && self.log.trace('ldap authenticate: bind error: %s', bindErr);
return callback(bindErr);
}
// 3. If requested, fetch user groups
self._getGroups(user, function(err, user) {
if (err) {
self.log && self.log.trace('ldap authenticate: group search error %s', err);
return callback(err);
self._getGroups(user, function(groupErr, userWithGroups) {
if (groupErr) {
self.log && self.log.trace('ldap authenticate: group search error %s', groupErr);
return callback(groupErr);
}
if (self.opts.cache) {
bcrypt.hash(password, self._salt, function (err, hash) {
self.userCache.set(username, {password: hash, user: user});
return callback(null, user);
bcrypt.hash(password, self._salt, function(err, hash) {
if (err) {
self.log && self.log.trace('ldap authenticate: bcrypt error, not caching %s', err);
} else {
self.userCache.set(username, { password: hash, user: userWithGroups });
}
return callback(null, userWithGroups);
});
} else {
return callback(null, user);
return callback(null, userWithGroups);
}
})
});
});

@@ -395,0 +396,0 @@ });

{
"name": "ldapauth-fork",
"version": "3.0.1",
"version": "4.0.0",
"main": "./lib/ldapauth.js",
"types": "./lib/ldapauth.d.ts",
"description": "Authenticate against an LDAP server",

@@ -18,10 +19,26 @@ "author": "Vesa Poikajärvi <vesa.poikajarvi@iki.fi>",

},
"engines": [
"node >=0.8.0"
],
"engines": {
"node": ">=0.8.0"
},
"scripts": {
"prepublish": "npm run lint",
"lint": "eslint ./lib",
"lint:watch": "watch 'npm run lint' ./lib --wait 0.5",
"pretest": "cd test && tsc",
"test": "cd test && node test.js"
},
"dependencies": {
"bcryptjs": "2.4.0",
"ldapjs": "~1.0.1",
"lru-cache": "4.0.2"
"@types/ldapjs": "^1.0.0",
"@types/node": "^7.0.21",
"bcryptjs": "^2.4.0",
"ldapjs": "^1.0.1",
"lru-cache": "^4.0.2"
},
"devDependencies": {
"@types/bunyan": "0.0.36",
"bunyan": "^1.8.10",
"eslint": "^3.19.0",
"typescript": "^2.3.3",
"watch": "^1.0.2"
}
}

@@ -18,3 +18,3 @@ # ldapauth-fork

var options = {
url: 'ldaps://ldap.example.com:636',
url: 'ldaps://ldap.example.org:636',
...

@@ -32,2 +32,4 @@ };

`LdapAuth` inherits from `EventEmitter`.
## Install

@@ -37,13 +39,54 @@

## `LdapAuth` Config Options
## License
Required ldapjs client options:
MIT. See "LICENSE" file.
- `url` - LDAP server URL, eg. *ldaps://ldap.example.org:663*
ldapauth-fork options:
## `LdapAuth` Config Options
- `bindDN` - Admin connection DN, e.g. *uid=myapp,ou=users,dc=example,dc=org*. Optional. If not given at all, admin client is not bound. Giving empty string may result in anonymous bind when allowed.
- `bindCredentials` - Password for bindDN.
- `searchBase` - The base DN from which to search for users by username. E.g. *ou=users,dc=example,dc=org*
- `searchFilter` - LDAP search filter with which to find a user by username, e.g. *(uid={{username}})*. Use the literal *{{username}}* to have the given username interpolated in for the LDAP search.
- `searchAttributes` - Optional, default all. Array of attributes to fetch from LDAP server.
- `bindProperty` - Optional, default *dn*. Property of the LDAP user object to use when binding to verify the password. E.g. *name*, *email*
- `searchScope` - Optional, default *sub*. Scope of the search, one of *base*, *one*, or *sub*.
[Use the source Luke](https://github.com/vesse/node-ldapauth-fork/blob/master/lib/ldapauth.js#L35-L99)
ldapauth-fork can look for valid users groups too. Related options:
- `groupSearchBase` - Optional. The base DN from which to search for groups. If defined, also `groupSearchFilter` must be defined for the search to work.
- `groupSearchFilter` - Optional. LDAP search filter for groups. Place literal *{{dn}}* in the filter to have it replaced by the property defined with `groupDnProperty` of the found user object. Optionally you can also assign a function instead. The found user is passed to the function and it should return a valid search filter for the group search.
- `groupSearchAttributes` - Optional, default all. Array of attributes to fetch from LDAP server.
- `groupDnProperty` - Optional, default *dn*. The property of user object to use in *{{dn}}* interpolation of `groupSearchFilter`.
- `groupSearchScope` - Optional, default *sub*.
Other ldapauth-fork options:
- `includeRaw` - Optional, default false. Set to true to add property `_raw` containing the original buffers to the returned user object. Useful when you need to handle binary attributes
- `cache` - Optional, default false. If true, then up to 100 credentials at a time will be cached for 5 minutes.
- `log` - Bunyan logger instance, optional. If given this will result in TRACE-level error logging for component:ldapauth. The logger is also passed forward to ldapjs.
Optional ldapjs options, see [ldapjs documentation](https://github.com/mcavage/node-ldapjs/blob/v1.0.1/docs/client.md):
- `tlsOptions` - Needed for TLS connection. See [Node.js documentation](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
- `socketPath`
- `timeout`
- `connectTimeout`
- `idleTimeout`
- `reconnect`
- `strictDN`
- `queueSize`
- `queueTimeout`
- `queueDisable`
## How it works
The LDAP authentication flow is usually:
1. Bind the admin client using the given `bindDN` and `bindCredentials`
2. Use the admin client to search for the user by substituting `{{username}}` from the `searchFilter` with given username
3. If user is found, verify the given password by trying to bind the user client with the found LDAP user object and given password
4. If password was correct and group search options were provided, search for the groups of the user
## express/connect basicAuth example

@@ -56,7 +99,7 @@

var ldap = new LdapAuth({
url: "ldaps://ldap.example.com:636",
bindDn: "uid=myadminusername,ou=users,o=example.com",
bindCredentials: "mypassword",
searchBase: "ou=users,o=example.com",
searchFilter: "(uid={{username}})",
url: 'ldaps://ldap.example.org:636',
bindDN: 'uid=myadminusername,ou=users,dc=example,dc=org',
bindCredentials: 'mypassword',
searchBase: 'ou=users,dc=example,dc=org',
searchFilter: '(uid={{username}})',
reconnect: true

@@ -88,2 +131,6 @@ });

## License
MIT
`ldapauth-fork` has been partially sponsored by [Leonidas Ltd](https://leonidasoy.fi/opensource).

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc