Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ This is a **modular LDAP gateway** built with Node.js and ldapjs that bridges LD
- **Backend separation**: Always implement both auth and directory providers (see `npm/src/interfaces/`)
- **Dynamic backend loading**: Backends can be loaded from `server/backends/*.js` at runtime without rebuilding (see `server/utils/backendLoader.js`)
- **Provider factory**: Use `ProviderFactory` to instantiate backends; supports both compiled and dynamic providers
- **Environment config**: All settings via `.env` (e.g., `AUTH_BACKEND=db`, `DIRECTORY_BACKEND=mysql`, `BACKEND_DIR=/custom/path`)
- **Environment config**: All settings via `.env` (e.g., `AUTH_BACKENDS=sql`, `DIRECTORY_BACKEND=sql`, `BACKEND_DIR=/custom/path`, `SQL_URL=mysql://user:pass@host:port/db`)
- **LDAP entry mapping**: Use `@ldap-gateway/core` utilities to create entries with standard attributes (posixAccount, inetOrgPerson)
- **Database queries**: Use connection pooling in drivers (`server/src/db/drivers/`); groups store `member_uids` as JSON arrays
- **UID/GID mapping**: For WebChart, `uidNumber` from "LDAP UID Number" observation or `user_id + 10000`; `gidNumber` from `realms.id`
Expand Down
18 changes: 13 additions & 5 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,21 @@ npm run dev
| `src/.env` | `/etc/ldap-gateway/.env` | Production (binary install) |

### Environment Variables
**No changes required** - all existing environment variables work as before:
**Configuration syntax has changed** - backend names and SQL configuration updated:

```ini
# These remain the same
DIRECTORY_BACKEND=mysql
AUTH_BACKEND=ldap
MYSQL_HOST=localhost
# Backend names changed from 'mysql' to 'sql'
DIRECTORY_BACKEND=sql # Changed from 'mysql'
AUTH_BACKENDS=sql,ldap # Changed from 'mysql,ldap'

# SQL configuration now uses connection URL and custom queries
SQL_URL=mysql://user:password@localhost:3306/database # Replaces MYSQL_HOST, MYSQL_PORT, etc.
SQL_QUERY_ONE_USER='SELECT * FROM users WHERE username = ?'
SQL_QUERY_ALL_USERS='SELECT * FROM users'
SQL_QUERY_GROUPS_BY_MEMBER='SELECT * FROM groups WHERE member = ?'
SQL_QUERY_ALL_GROUPS='SELECT * FROM groups'

# Other settings remain the same
LDAP_BIND_DN=...
# etc.
```
Expand Down
121 changes: 87 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,26 +118,35 @@ Create or edit `/etc/ldap-gateway/.env`:

```ini
# Directory backend: where to find user/group information
DIRECTORY_BACKEND=mysql # mysql | mongodb | proxmox
DIRECTORY_BACKEND=sql # sql | mongodb | proxmox

# Authentication backends: how to validate passwords (comma-separated for multiple)
AUTH_BACKENDS=ldap # mysql | mongodb | ldap | proxmox | notification | mysql,ldap | ldap,notification
AUTH_BACKENDS=ldap # sql | mongodb | ldap | proxmox | notification | sql,ldap | ldap,notification

# LDAP Server Configuration
LDAP_BASE_DN=dc=company,dc=com

# SQL configuration (for any SQL-based system)
SQL_URL=mysql://ldap_user:secure_password@localhost:3306/your_database
SQL_QUERY_ONE_USER='SELECT * FROM users WHERE username = ?'
SQL_QUERY_GROUPS_BY_MEMBER='SELECT * FROM groups g WHERE JSON_CONTAINS(g.member_uids, JSON_QUOTE(?))'
SQL_QUERY_ALL_USERS='SELECT * FROM users'
SQL_QUERY_ALL_GROUPS='SELECT
g.gid_number,
g.name,
g.gid_number AS id,
GROUP_CONCAT(u.username) AS member_uids
FROM groups g
LEFT JOIN user_groups ug ON g.gid_number = ug.group_id
LEFT JOIN users u ON ug.user_id = u.id
GROUP BY g.gid_number, g.name
ORDER BY g.name'

# Security: Require authentication for search operations
# Default: true (authentication required for security)
# Set to false only for development/testing if you need anonymous access
REQUIRE_AUTH_FOR_SEARCH=true

# MySQL configuration (for any MySQL-based system)
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=ldap_user
MYSQL_PASSWORD=secure_password
MYSQL_DATABASE=your_database

# MongoDB configuration (for mongodb backends)
MONGO_URI=mongodb://localhost:27017/ldap_user_db
MONGO_DATABASE=ldap_user_db
Expand Down Expand Up @@ -203,42 +212,64 @@ The LDAP gateway separates **directory lookups** from **authentication**, allowi

| Backend | Description | Use Case |
|---------|-------------|----------|
| `mysql` | MySQL/MariaDB databases | Any MySQL-based system (WebChart, custom schemas) |
| `sql` | MySQL/MariaDB/SQLite3/PostgreSQL databases | Any SQL-based system (WebChart, custom schemas) |
| `mongodb` | MongoDB collections | Modern web applications |
| `proxmox` | Proxmox user.cfg/shadow.cfg files | Virtualization environments |

### Authentication Backends (`AUTH_BACKENDS`)

**Multiple backends supported** - Use comma-separated values (e.g., `AUTH_BACKENDS=mysql,ldap`) to try authentication providers in order.
**Multiple backends supported** - Use comma-separated values (e.g., `AUTH_BACKENDS=sql,ldap`) to try authentication providers in order.

| Backend | Description | Use Case |
|---------|-------------|----------|
| `mysql` | MySQL/MariaDB password hashes | Self-contained auth with MySQL databases |
| `sql` | MySQL/MariaDB/SQLite3/PostgreSQL password hashes | Self-contained auth with SQL databases |
| `mongodb` | MongoDB password hashes | Self-contained auth with MongoDB collections |
| `ldap` | External LDAP/Active Directory | Enterprise SSO integration |
| `proxmox` | Proxmox shadow file | Proxmox container authentication |
| `notification` | MFA push notifications via mobile app | Two-factor authentication, enhanced security |

### Example Configurations

#### MySQL + Active Directory
#### SQL + Active Directory
```ini
DIRECTORY_BACKEND=mysql # User info from MySQL
DIRECTORY_BACKEND=sql # User info from MySQL
AUTH_BACKENDS=ldap # Passwords via AD
MYSQL_HOST=your-mysql-host
MYSQL_DATABASE=your_database
AD_DOMAIN=your-domain.com
LDAP_BIND_DN=CN=service,DC=your-domain,DC=com
SQL_URL=mysql://ldap_user:secure_password@localhost:3306/your_database
SQL_QUERY_ONE_USER='SELECT * FROM users WHERE username = ?'
SQL_QUERY_GROUPS_BY_MEMBER='SELECT * FROM groups g WHERE JSON_CONTAINS(g.member_uids, JSON_QUOTE(?))'
SQL_QUERY_ALL_USERS='SELECT * FROM users'
SQL_QUERY_ALL_GROUPS='SELECT
g.gid_number,
g.name,
g.gid_number AS id,
GROUP_CONCAT(u.username) AS member_uids
FROM groups g
LEFT JOIN user_groups ug ON g.gid_number = ug.group_id
LEFT JOIN users u ON ug.user_id = u.id
GROUP BY g.gid_number, g.name
ORDER BY g.name'
```

#### MySQL Self-Contained
```ini
DIRECTORY_BACKEND=mysql # User info from MySQL
AUTH_BACKENDS=mysql # Passwords in MySQL
MYSQL_HOST=localhost
MYSQL_DATABASE=users
MYSQL_USER=ldap_user
MYSQL_PASSWORD=secure_password
SQL_URL=mysql://ldap_user:secure_password@localhost:3306/your_database
SQL_QUERY_ONE_USER='SELECT * FROM users WHERE username = ?'
SQL_QUERY_GROUPS_BY_MEMBER='SELECT * FROM groups g WHERE JSON_CONTAINS(g.member_uids, JSON_QUOTE(?))'
SQL_QUERY_ALL_USERS='SELECT * FROM users'
SQL_QUERY_ALL_GROUPS='SELECT
g.gid_number,
g.name,
g.gid_number AS id,
GROUP_CONCAT(u.username) AS member_uids
FROM groups g
LEFT JOIN user_groups ug ON g.gid_number = ug.group_id
LEFT JOIN users u ON ug.user_id = u.id
GROUP BY g.gid_number, g.name
ORDER BY g.name'
```

#### MongoDB Self-Contained
Expand All @@ -262,22 +293,46 @@ PROXMOX_SHADOW_CFG=/etc/pve/shadow.cfg
🎥 **[Multiple Backends Demo](https://youtube.com/shorts/4N-aov0wxZ4?si=AA9SN_s_EfpkM-MK)** - See how to configure multiple authentication backends

```ini
DIRECTORY_BACKEND=mysql # User info from MySQL
AUTH_BACKENDS=mysql,ldap # Try MySQL auth first, fallback to LDAP
MYSQL_HOST=your-mysql-host
MYSQL_DATABASE=your_database
DIRECTORY_BACKEND=sql # User info from SQL
AUTH_BACKENDS=sql,ldap # Try SQL auth first, fallback to LDAP
SQL_URL=mysql://ldap_user:secure_password@localhost:3306/your_database
SQL_QUERY_ONE_USER='SELECT * FROM users WHERE username = ?'
SQL_QUERY_GROUPS_BY_MEMBER='SELECT * FROM groups g WHERE JSON_CONTAINS(g.member_uids, JSON_QUOTE(?))'
SQL_QUERY_ALL_USERS='SELECT * FROM users'
SQL_QUERY_ALL_GROUPS='SELECT
g.gid_number,
g.name,
g.gid_number AS id,
GROUP_CONCAT(u.username) AS member_uids
FROM groups g
LEFT JOIN user_groups ug ON g.gid_number = ug.group_id
LEFT JOIN users u ON ug.user_id = u.id
GROUP BY g.gid_number, g.name
ORDER BY g.name'
AD_DOMAIN=your-domain.com
LDAP_BIND_DN=CN=service,DC=your-domain,DC=com
```

#### MFA with Push Notifications
```ini
DIRECTORY_BACKEND=mysql # User info from MySQL
DIRECTORY_BACKEND=sql # User info from MySQL
AUTH_BACKENDS=ldap,notification # LDAP auth + MFA push notifications
MYSQL_HOST=your-mysql-host
MYSQL_DATABASE=your_database
AD_DOMAIN=your-domain.com
LDAP_BIND_DN=CN=service,DC=your-domain,DC=com
SQL_URL=mysql://ldap_user:secure_password@localhost:3306/your_database
SQL_QUERY_ONE_USER='SELECT * FROM users WHERE username = ?'
SQL_QUERY_GROUPS_BY_MEMBER='SELECT * FROM groups g WHERE JSON_CONTAINS(g.member_uids, JSON_QUOTE(?))'
SQL_QUERY_ALL_USERS='SELECT * FROM users'
SQL_QUERY_ALL_GROUPS='SELECT
g.gid_number,
g.name,
g.gid_number AS id,
GROUP_CONCAT(u.username) AS member_uids
FROM groups g
LEFT JOIN user_groups ug ON g.gid_number = ug.group_id
LEFT JOIN users u ON ug.user_id = u.id
GROUP BY g.gid_number, g.name
ORDER BY g.name'

# MFA Configuration (requires MIE Authenticator app)
ENABLE_NOTIFICATION=true
Expand Down Expand Up @@ -367,15 +422,13 @@ ldap-gateway --config-test

## 🏥 WebChart Integration

The LDAP Gateway integrates with [WebChart EHR](https://www.mieweb.com/) systems using the MySQL backend:
The LDAP Gateway integrates with [WebChart EHR](https://www.mieweb.com/) systems using the SQL backend:

```ini
DIRECTORY_BACKEND=mysql # WebChart uses MySQL
AUTH_BACKENDS=mysql
MYSQL_HOST=webchart-host
MYSQL_DATABASE=webchart
MYSQL_USER=readonly_user
MYSQL_PASSWORD=secure_password
DIRECTORY_BACKEND=sql # WebChart uses MySQL
AUTH_BACKENDS=sql
SQL_URL=mysql://ldap_user:secure_password@localhost:3306/your_database
# TODO: implement queries matching the webchart schema
```

WebChart users are mapped to standard LDAP objects with healthcare-specific attributes and group memberships based on WebChart realms.
Expand Down
3 changes: 3 additions & 0 deletions nfpm/systemd/ldap-gateway.service
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes

# Allow creating self-signed certs on startup
ReadWritePaths=/opt/ldap-gateway/server/cert

# Capabilities
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
Expand Down
4 changes: 1 addition & 3 deletions npm/src/utils/ldapUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function createLdapEntry(user, baseDn) {
const entry = {
dn: `uid=${user.username},${baseDn}`,
attributes: {
objectClass: ["top", "posixAccount", "inetOrgPerson", "shadowAccount"],
objectClass: ["top", "posixAccount", "inetOrgPerson"],
uid: user.username,
uidNumber,
gidNumber,
Expand All @@ -26,8 +26,6 @@ function createLdapEntry(user, baseDn) {
mail: user.mail || `${user.username}@${extractDomainFromBaseDn(baseDn)}`,
homeDirectory: user.home_directory || `/home/${user.username}`,
loginShell: "/bin/bash",
shadowLastChange: "0",
userpassword: user?.password,
},
};

Expand Down
Loading