# 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 ``` ### 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 " \ -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 " ``` 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 ```