Workflow funktioniert nun wieder. Es gab Probleme nach Aenderungen.
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:
2026-07-03 13:24:08 +02:00
parent 97a22cf704
commit b518ae8edb
1845 changed files with 292358 additions and 57 deletions
+105
View File
@@ -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
View File
@@ -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
View File
@@ -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;