Workflow funktioniert nun wieder. Es gab Probleme nach Aenderungen.
Build and Publish Site / docker (push) Successful in 23s
Build and Publish Site / docker (push) Successful in 23s
ABER: Die Applikation funktioniert nur lokal. Die deployte Version geht noch nicht.
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2011 Joyent, Inc. All rights reserved.
|
||||
*
|
||||
* An expiring LRU cache.
|
||||
*
|
||||
* Usage:
|
||||
* var Cache = require('amon-common').Cache;
|
||||
* // size, expiry, log, name
|
||||
* this.accountCache = new Cache( 100, 300, log, 'account');
|
||||
* this.accountCache.set('hamish', {...});
|
||||
* ...
|
||||
* this.accountCache.get('hamish') // -> {...}
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var LRU = require('lru-cache');
|
||||
|
||||
/**
|
||||
* A LRU and expiring cache.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
function Cache(size, expiry, log, name) {
|
||||
assert.ok(size !== undefined);
|
||||
assert.ok(expiry !== undefined);
|
||||
this.size = size;
|
||||
this.expiry = expiry * 1000;
|
||||
this.log = log;
|
||||
this.name = name ? name + ' ' : '';
|
||||
this.items = new LRU({ max: this.size });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
Cache.prototype.reset = function reset() {
|
||||
if (this.log) {
|
||||
this.log.trace('%scache reset', this.name);
|
||||
}
|
||||
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) {
|
||||
assert.ok(key !== undefined);
|
||||
var cached = this.items.get(key);
|
||||
if (cached) {
|
||||
if (new Date().getTime() - cached.ctime <= this.expiry) {
|
||||
if (this.log) {
|
||||
this.log.trace('%scache hit: key="%s": %o', this.name, key, cached);
|
||||
}
|
||||
return cached.value;
|
||||
}
|
||||
}
|
||||
if (this.log) {
|
||||
this.log.trace('%scache miss: key="%s"', this.name, key);
|
||||
}
|
||||
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) {
|
||||
assert.ok(key !== undefined);
|
||||
var item = {
|
||||
value: value,
|
||||
ctime: new Date().getTime(),
|
||||
};
|
||||
if (this.log) {
|
||||
this.log.trace('%scache set: key="%s": %o', this.name, key, item);
|
||||
}
|
||||
this.items.set(key, item);
|
||||
return item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a single entry from cache
|
||||
*
|
||||
* @param {string} key - The cache key
|
||||
* @returns {undefined}
|
||||
*/
|
||||
Cache.prototype.del = function del(key) {
|
||||
if (this.log) {
|
||||
this.log.trace('%scache del: key="%s"', this.name, key);
|
||||
}
|
||||
this.items.del(key);
|
||||
};
|
||||
|
||||
module.exports = Cache;
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// Type definitions for ldapauth-fork 4.0
|
||||
// Project: https://github.com/vesse/node-ldapauth-fork
|
||||
// Definitions by: Vesa Poikajärvi <https://github.com/vesse>
|
||||
// TypeScript Version: 2.1
|
||||
|
||||
/// <reference types="node"/>
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { ClientOptions, ErrorCallback } from 'ldapjs';
|
||||
import { ConnectionOptions } from 'tls';
|
||||
|
||||
declare namespace LdapAuth {
|
||||
type Scope = 'base' | 'one' | 'sub';
|
||||
|
||||
interface Callback {
|
||||
(error: Error | string, result?: any): void;
|
||||
}
|
||||
|
||||
interface GroupSearchFilterFunction {
|
||||
/**
|
||||
* Construct a group search filter from user object
|
||||
*
|
||||
* @param user The user retrieved and authenticated from LDAP
|
||||
*/
|
||||
(user: any): string;
|
||||
}
|
||||
|
||||
interface Options extends ClientOptions {
|
||||
/**
|
||||
* Admin connection DN, e.g. uid=myapp,ou=users,dc=example,dc=org.
|
||||
* If not given at all, admin client is not bound. Giving empty
|
||||
* string may result in anonymous bind when allowed.
|
||||
*
|
||||
* Note: Not passed to ldapjs, it would bind automatically
|
||||
*/
|
||||
bindDN?: string;
|
||||
/**
|
||||
* Password for bindDN
|
||||
*/
|
||||
bindCredentials?: string;
|
||||
/**
|
||||
* The base DN from which to search for users by username.
|
||||
* E.g. ou=users,dc=example,dc=org
|
||||
*/
|
||||
searchBase: 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 interpolated in for the LDAP search.
|
||||
*/
|
||||
searchFilter: string;
|
||||
/**
|
||||
* Scope of the search. Default: 'sub'
|
||||
*/
|
||||
searchScope?: Scope;
|
||||
/**
|
||||
* Array of attributes to fetch from LDAP server. Default: all
|
||||
*/
|
||||
searchAttributes?: string[];
|
||||
|
||||
/**
|
||||
* The base DN from which to search for groups. If defined,
|
||||
* also groupSearchFilter must be defined for the search to work.
|
||||
*/
|
||||
groupSearchBase?: string;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
groupSearchFilter?: string | GroupSearchFilterFunction;
|
||||
/**
|
||||
* Scope of the search. Default: sub
|
||||
*/
|
||||
groupSearchScope?: Scope;
|
||||
/**
|
||||
* Array of attributes to fetch from LDAP server. Default: all
|
||||
*/
|
||||
groupSearchAttributes?: string[];
|
||||
|
||||
/**
|
||||
* Property of the LDAP user object to use when binding to verify
|
||||
* the password. E.g. name, email. Default: dn
|
||||
*/
|
||||
bindProperty?: string;
|
||||
/**
|
||||
* The property of user object to use in '{{dn}}' interpolation of
|
||||
* groupSearchFilter. Default: 'dn'
|
||||
*/
|
||||
groupDnProperty?: string;
|
||||
|
||||
/**
|
||||
* Set to true to add property '_raw' containing the original buffers
|
||||
* to the returned user object. Useful when you need to handle binary
|
||||
* attributes
|
||||
*/
|
||||
includeRaw?: boolean;
|
||||
|
||||
/**
|
||||
* If true, then up to 100 credentials at a time will be cached for
|
||||
* 5 minutes.
|
||||
*/
|
||||
cache?: boolean;
|
||||
|
||||
/**
|
||||
* If true, then intialize TLS using the starttls mechanism.
|
||||
*/
|
||||
starttls?: boolean;
|
||||
/**
|
||||
* Provides the secure TLS options passed to tls.connect in ldapjs
|
||||
*/
|
||||
tlsOptions?: ConnectionOptions;
|
||||
}
|
||||
}
|
||||
|
||||
declare class LdapAuth extends EventEmitter {
|
||||
/**
|
||||
* @constructor
|
||||
* @param opts
|
||||
*/
|
||||
constructor(opts: LdapAuth.Options);
|
||||
|
||||
/**
|
||||
* Authenticate against LDAP server with given credentials
|
||||
*
|
||||
* @param username Username
|
||||
* @param password Password
|
||||
* @param callback Standard callback
|
||||
*/
|
||||
authenticate(username: string, password: string, callback: LdapAuth.Callback): void;
|
||||
|
||||
/**
|
||||
* Unbind both admin and client connections
|
||||
*
|
||||
* @param callback Error callback
|
||||
*/
|
||||
close(callback?: ErrorCallback): void;
|
||||
}
|
||||
|
||||
export = LdapAuth;
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
var assert = require('assert');
|
||||
var ldap = require('ldapjs');
|
||||
var format = require('util').format;
|
||||
var bcrypt = require('bcryptjs');
|
||||
var inherits = require('util').inherits;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
/**
|
||||
* Copyright 2011 (c) Trent Mick.
|
||||
* Modified Work Copyright 2013 Vesa Poikajärvi.
|
||||
*
|
||||
* LDAP auth.
|
||||
*
|
||||
* Usage:
|
||||
* var LdapAuth = require('ldapauth');
|
||||
* var auth = new LdapAuth({url: 'ldaps://ldap.example.com:636', ...});
|
||||
* ...
|
||||
* auth.authenticate(username, password, function(err, user) { ... });
|
||||
* ...
|
||||
* auth.close(function(err) { ... })
|
||||
*/
|
||||
|
||||
/**
|
||||
* Void callback
|
||||
*
|
||||
* @callback voidCallback
|
||||
* @param {(Error|undefined)} err - Possible error
|
||||
*/
|
||||
/**
|
||||
* Result callback
|
||||
*
|
||||
* @callback resultCallback
|
||||
* @param {(Error|undefined)} err - Possible error
|
||||
* @param {(Object|undefined)} res - Result
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get option that may be defined under different names, but accept
|
||||
* the first one that is actually defined in the given object
|
||||
*
|
||||
* @private
|
||||
* @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) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (keys[i] in obj) {
|
||||
return obj[keys[i]];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize LDAP special characters from input
|
||||
*
|
||||
* {@link https://tools.ietf.org/search/rfc4515#section-3}
|
||||
*
|
||||
* @private
|
||||
* @param {string} input - String to sanitize
|
||||
* @returns {string} Sanitized string
|
||||
*/
|
||||
var sanitizeInput = function (input) {
|
||||
return input
|
||||
.replace(/\*/g, '\\2a')
|
||||
.replace(/\(/g, '\\28')
|
||||
.replace(/\)/g, '\\29')
|
||||
.replace(/\\/g, '\\5c')
|
||||
.replace(/\0/g, '\\00')
|
||||
.replace(/\//g, '\\2f');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an LDAP auth class. Primary usage is the `.authenticate` method.
|
||||
*
|
||||
* @param {Object} opts - Config options
|
||||
* @constructor
|
||||
*/
|
||||
function LdapAuth(opts) {
|
||||
this.opts = opts;
|
||||
assert.ok(opts.url, 'LDAP server URL not defined (opts.url)');
|
||||
assert.ok(opts.searchFilter, 'Search filter not defined (opts.searchFilter)');
|
||||
|
||||
this.log = opts.log && opts.log.child({ component: 'ldapauth' }, true);
|
||||
|
||||
this.opts.searchScope || (this.opts.searchScope = 'sub');
|
||||
this.opts.bindProperty || (this.opts.bindProperty = 'dn');
|
||||
this.opts.groupSearchScope || (this.opts.groupSearchScope = 'sub');
|
||||
this.opts.groupDnProperty || (this.opts.groupDnProperty = 'dn');
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
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,
|
||||
idleTimeout: opts.idleTimeout,
|
||||
reconnect: opts.reconnect,
|
||||
strictDN: opts.strictDN,
|
||||
queueSize: opts.queueSize,
|
||||
queueTimeout: opts.queueTimeout,
|
||||
queueDisable: opts.queueDisable,
|
||||
};
|
||||
|
||||
// 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']);
|
||||
|
||||
this._adminClient = ldap.createClient(this.clientOpts);
|
||||
this._adminBound = false;
|
||||
this._userClient = ldap.createClient(this.clientOpts);
|
||||
|
||||
this._adminClient.on('error', this._handleError.bind(this));
|
||||
this._userClient.on('error', this._handleError.bind(this));
|
||||
|
||||
var self = this;
|
||||
if (this.opts.starttls) {
|
||||
// When starttls is enabled, this callback supplants the 'connect' callback
|
||||
this._adminClient.starttls(this.opts.tlsOptions, this._adminClient.controls, function (err) {
|
||||
if (err) {
|
||||
self._handleError(err);
|
||||
} else {
|
||||
self._onConnectAdmin();
|
||||
}
|
||||
});
|
||||
this._userClient.starttls(this.opts.tlsOptions, this._userClient.controls, function (err) {
|
||||
if (err) {
|
||||
self._handleError(err);
|
||||
}
|
||||
});
|
||||
} else if (opts.reconnect) {
|
||||
this.once('_installReconnectListener', function () {
|
||||
self.log && self.log.trace('install reconnect listener');
|
||||
self._adminClient.on('connect', function () {
|
||||
self._onConnectAdmin();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this._adminClient.on('connectTimeout', this._handleError.bind(this));
|
||||
this._userClient.on('connectTimeout', this._handleError.bind(this));
|
||||
|
||||
if (opts.groupSearchBase && opts.groupSearchFilter) {
|
||||
if (typeof opts.groupSearchFilter === 'string') {
|
||||
var groupSearchFilter = opts.groupSearchFilter;
|
||||
opts.groupSearchFilter = function (user) {
|
||||
return groupSearchFilter
|
||||
.replace(/{{dn}}/g, sanitizeInput(user[opts.groupDnProperty] || ''))
|
||||
.replace(/{{username}}/g, sanitizeInput(user.uid || ''));
|
||||
};
|
||||
}
|
||||
|
||||
this._getGroups = this._findGroups;
|
||||
} else {
|
||||
// Assign an async identity function so there is no need to branch
|
||||
// the authenticate function to have cache set up.
|
||||
this._getGroups = function (user, callback) {
|
||||
return callback(null, user);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
inherits(LdapAuth, EventEmitter);
|
||||
|
||||
/**
|
||||
* Unbind connections
|
||||
*
|
||||
* @param {voidCallback} 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 () {
|
||||
self._userClient.unbind(callback);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark admin client unbound so reconnect works as expected and re-emit the error
|
||||
*
|
||||
* @private
|
||||
* @param {Error} err - The error to be logged and emitted
|
||||
* @returns {undefined}
|
||||
*/
|
||||
LdapAuth.prototype._handleError = function (err) {
|
||||
this.log && this.log.trace('ldap emitted error: %s', err);
|
||||
this._adminBound = false;
|
||||
this.emit('error', err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind adminClient to the admin user on connect
|
||||
*
|
||||
* @private
|
||||
* @param {voidCallback} callback - Callback that checks possible error, optional
|
||||
* @returns {undefined}
|
||||
*/
|
||||
LdapAuth.prototype._onConnectAdmin = function (callback) {
|
||||
var self = this;
|
||||
|
||||
// Anonymous binding
|
||||
if (typeof self.bindDN === 'undefined' || self.bindDN === null) {
|
||||
self._adminBound = true;
|
||||
return callback ? callback() : null;
|
||||
}
|
||||
|
||||
self.log && self.log.trace('ldap authenticate: bind: %s', self.bindDN);
|
||||
self._adminClient.bind(self.bindDN, self.bindCredentials, function (err) {
|
||||
if (err) {
|
||||
self.log && self.log.trace('ldap authenticate: bind error: %s', err);
|
||||
self._adminBound = false;
|
||||
return callback ? callback(err) : null;
|
||||
}
|
||||
|
||||
self.log && self.log.trace('ldap authenticate: bind ok');
|
||||
self._adminBound = true;
|
||||
if (self.opts.reconnect) {
|
||||
self.emit('_installReconnectListener');
|
||||
}
|
||||
return callback ? callback() : null;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that `this._adminClient` is bound.
|
||||
*
|
||||
* @private
|
||||
* @param {voidCallback} callback - Callback that checks possible error
|
||||
* @returns {undefined}
|
||||
*/
|
||||
LdapAuth.prototype._adminBind = function (callback) {
|
||||
if (this._adminBound) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// Call the connect handler with a callback
|
||||
return this._onConnectAdmin(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Conduct a search using the admin client. Used for fetching both
|
||||
* user and group information.
|
||||
*
|
||||
* @private
|
||||
* @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) {
|
||||
var self = this;
|
||||
|
||||
self._adminBind(function (bindErr) {
|
||||
if (bindErr) {
|
||||
return callback(bindErr);
|
||||
}
|
||||
|
||||
self._adminClient.search(searchBase, options, function (searchErr, searchResult) {
|
||||
if (searchErr) {
|
||||
return callback(searchErr);
|
||||
}
|
||||
|
||||
var items = [];
|
||||
searchResult.on('searchEntry', function (entry) {
|
||||
items.push(entry.object);
|
||||
if (self.opts.includeRaw === true) {
|
||||
items[items.length - 1]._raw = entry.raw;
|
||||
}
|
||||
});
|
||||
|
||||
searchResult.on('error', callback);
|
||||
|
||||
searchResult.on('end', function (result) {
|
||||
if (result.status !== 0) {
|
||||
var err = 'non-zero status from LDAP search: ' + result.status;
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, items);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the user record for the given username.
|
||||
*
|
||||
* @private
|
||||
* @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) {
|
||||
var self = this;
|
||||
if (!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 };
|
||||
if (self.opts.searchAttributes) {
|
||||
opts.attributes = self.opts.searchAttributes;
|
||||
}
|
||||
|
||||
// groupDnProperty will be accessed in the user returned by the search, and
|
||||
// so needs to be requested from the LDAP server.
|
||||
if (
|
||||
opts.attributes &&
|
||||
self.opts.groupDnProperty &&
|
||||
!opts.attributes.includes(self.opts.groupDnProperty)
|
||||
) {
|
||||
opts.attributes.push(self.opts.groupDnProperty);
|
||||
}
|
||||
|
||||
self._search(self.opts.searchBase, opts, function (err, result) {
|
||||
if (err) {
|
||||
self.log &&
|
||||
self.log.trace(
|
||||
'ldap authenticate: user search error: %s %s %s',
|
||||
err.code,
|
||||
err.name,
|
||||
err.message
|
||||
);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
switch (result.length) {
|
||||
case 0:
|
||||
return callback();
|
||||
case 1:
|
||||
return callback(null, result[0]);
|
||||
default:
|
||||
return callback(
|
||||
format('unexpected number of matches (%s) for "%s" username', result.length, username)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find groups for given user
|
||||
*
|
||||
* @private
|
||||
* @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(new Error('no user'));
|
||||
}
|
||||
|
||||
var searchFilter = self.opts.groupSearchFilter(user);
|
||||
|
||||
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) {
|
||||
if (err) {
|
||||
self.log &&
|
||||
self.log.trace(
|
||||
'ldap authenticate: group search error: %s %s %s',
|
||||
err.code,
|
||||
err.name,
|
||||
err.message
|
||||
);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
user._groups = result;
|
||||
callback(null, user);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticate given credentials against LDAP server
|
||||
*
|
||||
* @param {string} username - The username to authenticate
|
||||
* @param {string} password - The password to verify
|
||||
* @param {resultCallback} callback - Result handling callback
|
||||
* @returns {undefined}
|
||||
*/
|
||||
LdapAuth.prototype.authenticate = function (username, password, callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof password === 'undefined' || password === null || password === '') {
|
||||
return callback(new Error('no password given'));
|
||||
}
|
||||
|
||||
if (self.opts.cache) {
|
||||
// Check cache. 'cached' is `{password: <hashed-password>, user: <user>}`.
|
||||
var cached = self.userCache.get(username);
|
||||
if (cached && bcrypt.compareSync(password, cached.password)) {
|
||||
return callback(null, cached.user);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Find the user DN in question.
|
||||
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 (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 (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) {
|
||||
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, userWithGroups);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = LdapAuth;
|
||||
Reference in New Issue
Block a user