A reusable LDAP gateway core with pluggable authentication and directory backends.
The LDAP Gateway Core provides a modular, event-driven LDAP server implementation that supports pluggable authentication and directory providers. It handles the LDAP protocol operations while delegating authentication and directory lookups to configurable backends.
The main LDAP server engine that handles:
- LDAP server creation and lifecycle management
- Bind operations (authentication)
- Search operations (directory queries)
- Error normalization and event emission
- SSL/TLS certificate management
const { LdapEngine } = require('@ldap-gateway/core');
const engine = new LdapEngine({
baseDn: 'dc=example,dc=com',
port: 636,
certificate: certContent,
key: keyContent,
logger: myLogger
});
await engine.start();Implement this interface to add custom authentication backends:
const { AuthProvider } = require('@ldap-gateway/core');
class MyAuthProvider extends AuthProvider {
async authenticate(username, password, req) {
// Your authentication logic here
// Return true for successful auth, false otherwise
// Throw error for system failures
return await myAuthService.verify(username, password);
}
}Contract Requirements:
authenticate(username, password, req)must return a boolean- Successful authentication returns
true - Invalid credentials return
false - System errors should throw exceptions (will be normalized to LDAP errors)
Implement this interface to add custom directory backends:
const { DirectoryProvider } = require('@ldap-gateway/core');
class MyDirectoryProvider extends DirectoryProvider {
async findUser(username) {
// Return user object or null
return await myDirectory.getUser(username);
}
async findGroups(filter) {
// Return array of group objects
return await myDirectory.searchGroups(filter);
}
async getAllUsers() {
// Return array of all user objects
return await myDirectory.listUsers();
}
async getAllGroups() {
// Return array of all group objects
return await myDirectory.listGroups();
}
}User Object Contract:
{
username: string, // Required: unique user identifier
full_name?: string, // Display name
surname?: string, // Last name
mail?: string, // Email address
uid_number?: number, // POSIX UID
gid_number?: number, // POSIX primary GID
home_directory?: string, // Home directory path
password?: string // Hashed password (optional)
}Group Object Contract:
{
name: string, // Required: group name
gid_number: number, // Required: POSIX GID
dn?: string, // LDAP DN (auto-generated if not provided)
objectClass?: string[], // LDAP object classes
memberUids?: string[], // Array of member usernames
members?: string[] // Array of member DNs
}The LdapEngine emits events for monitoring and integration:
bindRequest({ username, anonymous })- Bind attempt startedbindSuccess({ username, anonymous })- Authentication succeededbindFail({ username, reason })- Authentication failedbindError({ username, error })- Authentication system error
searchRequest({ filter, attributes, baseDn, scope })- Search startedsearchResponse({ filter, attributes, entryCount, duration })- Search completedsearchError({ filter, error, duration })- Search failedentryFound({ type, entry })- Individual entry found
started({ port, baseDn, hasCertificate })- Server started successfullystopped()- Server stoppedstartupError(error)- Server startup failedserverError(error)- General server errorclientError({ error, socket })- Client connection error
notificationRequest({ username })- MFA notification sentnotificationResponse({ username, action })- MFA response received
extractCredentials(req)- Extract username/password from bind requestgetUsernameFromFilter(filterStr)- Parse username from search filterisAllUsersRequest(filterStr, attributes)- Detect user listing requestsisGroupSearchRequest(filterStr, attributes)- Detect group search requestsisMixedSearchRequest(filterStr)- Detect mixed search requests
createLdapEntry(user, baseDn)- Create LDAP entry from user objectcreateLdapGroupEntry(group, baseDn)- Create LDAP group entryextractDomainFromBaseDn(baseDn)- Extract domain from base DN
normalizeAuthError(error)- Convert auth errors to LDAP errorsnormalizeSearchError(error)- Convert search errors to LDAP errorsnormalizeServerError(error)- Convert server errors to LDAP errorscreateErrorResponse(error, context)- Create structured error response
const { LdapEngine, AuthProvider, DirectoryProvider } = require('@ldap-gateway/core');
// Create custom providers
class DatabaseAuthProvider extends AuthProvider {
async authenticate(username, password, req) {
return await db.verifyUser(username, password);
}
}
class DatabaseDirectoryProvider extends DirectoryProvider {
async findUser(username) {
return await db.getUser(username);
}
async getAllUsers() {
return await db.listUsers();
}
async getAllGroups() {
return await db.listGroups();
}
async findGroups(filter) {
return await db.searchGroups(filter);
}
}
// Create and configure engine
const engine = new LdapEngine({
baseDn: 'dc=company,dc=com',
port: 636,
certificate: fs.readFileSync('cert.pem'),
key: fs.readFileSync('key.pem'),
logger: winston.createLogger(...)
});
// Set providers
engine.setAuthProvider(new DatabaseAuthProvider());
engine.setDirectoryProvider(new DatabaseDirectoryProvider());
// Setup event handlers
engine.on('bindSuccess', ({ username }) => {
console.log(`User ${username} authenticated`);
});
engine.on('searchRequest', ({ filter, attributes }) => {
console.log(`Search: ${filter}`);
});
// Start the server
await engine.start();The core automatically normalizes internal errors to appropriate LDAP errors:
- Database connection errors →
UnavailableError - Timeout errors →
TimeLimitExceededError - Permission errors →
InsufficientAccessRightsError - Invalid credentials →
InvalidCredentialsError - Not found errors →
NoSuchObjectError - Other errors →
OperationsError
-
Provider Implementation
- Always handle errors gracefully in providers
- Return
nullfor not found cases, don't throw - Use consistent user/group object formats
- Implement proper connection pooling for database providers
-
Event Handling
- Use events for logging, monitoring, and metrics
- Don't perform heavy operations in event handlers
- Consider using async event handlers for I/O operations
-
Error Handling
- Let the core normalize errors automatically
- Provide meaningful error messages in thrown exceptions
- Include relevant context in error objects
-
Performance
- Implement caching in providers when appropriate
- Use connection pooling for database providers
- Consider pagination for large result sets
MIT