No description
  • Go 50.1%
  • CSS 26.9%
  • HTML 22.4%
  • Dockerfile 0.6%
Find a file
Pierre-Olivier Mercier e6bca3ac8f
All checks were successful
continuous-integration/drone/push Build is passing
fix(ldap): split ambiguous error messages in SearchDN and GetEntry
Distinguish between "not found" and "multiple entries found" instead of
the generic "User does not exist or too many entries returned", making
it easier to diagnose issues in logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 17:07:41 +07:00
dextpl docs: add README and dex custom theme 2026-03-08 14:02:21 +07:00
static fix(reset): validate token on GET and surface errors on POST 2026-03-12 15:05:25 +07:00
.drone.yml Replace bindata by embed 2024-05-31 15:52:25 +02:00
.gitignore chldapasswd is now a go module 2021-02-03 15:16:19 +01:00
addy.go fix(ldap): add Close() method and defer conn.Close() at all call sites 2026-03-16 14:40:40 +07:00
altcha.go feat(security): add altcha proof-of-work CAPTCHA to all sensitive forms 2026-03-08 10:56:16 +07:00
change.go fix(ldap): add Close() method and defer conn.Close() at all call sites 2026-03-16 14:40:40 +07:00
csrf.go feat: add -dev flag for local HTTP testing 2026-03-08 10:56:37 +07:00
Dockerfile Replace bindata by embed 2024-05-31 15:52:25 +02:00
go.mod feat(security): add altcha proof-of-work CAPTCHA to all sensitive forms 2026-03-08 10:56:16 +07:00
go.sum feat(security): add altcha proof-of-work CAPTCHA to all sensitive forms 2026-03-08 10:56:16 +07:00
ldap.go fix(ldap): split ambiguous error messages in SearchDN and GetEntry 2026-03-16 17:07:41 +07:00
login.go fix(ldap): add Close() method and defer conn.Close() at all call sites 2026-03-16 14:40:40 +07:00
lost.go refactor: separate SMTP config from LDAP struct 2026-03-16 17:02:52 +07:00
main.go refactor: separate SMTP config from LDAP struct 2026-03-16 17:02:52 +07:00
ratelimit.go fix(security): add per-IP rate limiting to all authentication endpoints 2026-03-06 15:30:48 +07:00
README.md docs: add README and dex custom theme 2026-03-08 14:02:21 +07:00
renovate.json Add renovate.json 2021-08-03 09:02:00 +00:00
reset.go fix(ldap): add Close() method and defer conn.Close() at all call sites 2026-03-16 14:40:40 +07:00
static.go feat: replace Bootstrap with custom CSS and add profile page 2026-03-08 11:49:51 +07:00

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

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:

{
  "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:

# 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 to keep a consistent look across the SSO stack.

Docker

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