chldapasswd/README.md
Pierre-Olivier Mercier 69c307e7d6 docs: add README and dex custom theme
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 14:02:21 +07:00

148 lines
5.8 KiB
Markdown

# chldapasswd
A self-hosted web portal for LDAP account management. Users can log in, view their profile, change their password, recover a lost password, and manage mail aliases — all without requiring direct LDAP access.
## Features
- **Password change** — authenticated users can update their LDAP password (SHA-512 crypt, minimum 12 chars, requires upper/lower/digit)
- **Lost password recovery** — sends a one-time reset link via email (token expires in 1 hour)
- **Profile view** — displays LDAP attributes after login
- **Mail alias management** — create/delete auto-generated email aliases stored as `mailAlias` in LDAP, exposed via an addy.io-compatible API
- **HTTP Basic Auth endpoint** (`/auth`) — validates credentials against LDAP, forwards `X-Remote-User` header; suitable for use with nginx `auth_request`
- **Docker registry anonymous read** — optionally allows unauthenticated `GET`/`HEAD` on registry image paths via `X-Special-Auth` header
- **Altcha PoW CAPTCHA** — proof-of-work challenge on sensitive forms, no third-party service required
- **CSRF protection** — token-based on state-changing forms
- **Rate limiting** — per-IP on login, password change, lost password, and alias API endpoints
- **Security headers** — CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
## Building
```sh
go build -ldflags="-s -w" -o chldapasswd
```
Requires Go 1.23+. A Drone CI pipeline builds and pushes `nemunaire/chldapasswd:latest` on each push to `master`.
## Usage
```
chldapasswd [flags] [serve]
chldapasswd [flags] generate-lost-password-link <uid>
```
### Flags
| Flag | Default | Description |
|------|---------|-------------|
| `-bind` | `127.0.0.1:8080` | Listen address |
| `-baseurl` | `/` | URL prefix (for reverse-proxy subpath deployment) |
| `-config` | _(none)_ | Path to a JSON config file for LDAP settings |
| `-public-url` | _(none)_ | Base URL used in password reset emails |
| `-brand-name` | `chldapasswd` | Brand name shown in the UI |
| `-brand-logo` | _(none)_ | URL of a logo image shown in the UI |
| `-addy-api-secret` | _(none)_ | HMAC secret for the alias API |
| `-dev` | `false` | Development mode: disables HSTS and cookie `Secure` flag |
### Environment variables
All LDAP and SMTP settings can be provided via environment variables (they override CLI flags and config file values):
| Variable | Description |
|----------|-------------|
| `LDAP_HOST` | LDAP server hostname |
| `LDAP_PORT` | LDAP server port |
| `LDAP_STARTTLS` | Enable STARTTLS (`1`/`on`/`true`) |
| `LDAP_SSL` | Use LDAPS (`1`/`on`/`true`) |
| `LDAP_BASEDN` | Base DN for searches |
| `LDAP_SERVICEDN` | DN of the service account |
| `LDAP_SERVICE_PASSWORD` | Password of the service account |
| `LDAP_SERVICE_PASSWORD_FILE` | Path to a file containing the service password |
| `SMTP_HOST` | SMTP server (leave empty to use local `sendmail`) |
| `SMTP_PORT` | SMTP port |
| `SMTP_USER` | SMTP username |
| `SMTP_PASSWORD` | SMTP password |
| `SMTP_PASSWORD_FILE` | Path to a file containing the SMTP password |
| `SMTP_FROM` | Sender address for recovery emails |
| `PUBLIC_URL` | Public base URL (overrides `-public-url`) |
| `BRAND_NAME` | Brand name (overrides `-brand-name`) |
| `BRAND_LOGO` | Brand logo URL (overrides `-brand-logo`) |
| `ADDY_API_SECRET` | HMAC secret for the alias API |
| `ALIAS_ALLOWED_DOMAINS` | Comma-separated list of domains users may create aliases under |
| `DOCKER_REGISTRY_SECRET` | Shared secret for anonymous Docker registry read access |
### JSON config file
The `-config` flag accepts a JSON file whose fields map directly to the `LDAP` struct:
```json
{
"Host": "auth.example.com",
"Port": 636,
"Ssl": true,
"BaseDN": "dc=example,dc=com",
"ServiceDN": "cn=svc,ou=services,dc=example,dc=com",
"ServicePassword": "secret",
"MailHost": "smtp.example.com",
"MailPort": 587,
"MailUser": "mailer",
"MailPassword": "secret",
"MailFrom": "noreply@example.com"
}
```
## HTTP endpoints
| Method | Path | Description |
|--------|------|-------------|
| `GET/POST` | `/` or `/change` | Password change form |
| `GET/POST` | `/login` | Login form — shows user profile on success |
| `GET/POST` | `/lost` | Lost password form |
| `GET/POST` | `/reset` | Password reset via token (from email link) |
| `GET/POST` | `/auth` | HTTP Basic Auth validation for reverse-proxy use |
| `POST` | `/api/v1/aliases` | Create a mail alias (addy.io-compatible) |
| `DELETE` | `/api/v1/aliases/{alias}` | Delete a mail alias |
| `GET` | `/altcha-challenge` | Fetch a PoW challenge for the Altcha widget |
## Mail alias API
The alias API is compatible with the addy.io API format. Tokens are HMAC-SHA224 signed and encoded in Base32:
```sh
# Create alias
curl -X POST https://auth.example.com/api/v1/aliases \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
# Delete alias
curl -X DELETE https://auth.example.com/api/v1/aliases/abc123%40example.com \
-H "Authorization: Bearer <token>"
```
The token for a given `uid` is: `base32(uid + ":" + HMAC-SHA224(apiSecret, uid))`.
Alias creation is disabled unless `ALIAS_ALLOWED_DOMAINS` is set.
## Templates
HTML templates are embedded from the `static/` directory at build time. To customise the UI, edit the files in `static/` before building.
The `dextpl/` directory contains a matching theme for [Dex](https://dexidp.io/) to keep a consistent look across the SSO stack.
## Docker
```sh
docker run -d \
-e LDAP_HOST=auth.example.com \
-e LDAP_PORT=636 \
-e LDAP_SSL=true \
-e LDAP_BASEDN=dc=example,dc=com \
-e LDAP_SERVICEDN=cn=svc,ou=services,dc=example,dc=com \
-e LDAP_SERVICE_PASSWORD=secret \
-e SMTP_HOST=smtp.example.com \
-e SMTP_PORT=587 \
-e SMTP_FROM=noreply@example.com \
-e PUBLIC_URL=https://auth.example.com \
-p 8080:8080 \
nemunaire/chldapasswd
```