No description
  • Go 49.2%
  • CSS 27.4%
  • HTML 22.8%
  • Dockerfile 0.6%
Find a file
Pierre-Olivier Mercier 71805cf65c
All checks were successful
continuous-integration/drone/push Build is passing
fix(reset): validate token on GET and surface errors on POST
- Verify reset token before showing the form (GET), redirecting with
  an error immediately if the token is invalid or expired
- Add peekResetToken to check token validity non-destructively
- Fix POST form action to include query params so the URL check doesn't
  silently redirect to /lost before processing errors
- Update page title and subtitle to reflect the reset step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 15:05:25 +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: remove base32 padding for Addy API token encoding/decoding 2026-03-08 14:02:21 +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(security): prevent username enumeration via timing attack 2026-03-08 12:42:41 +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(security): redesign password reset tokens using crypto/rand with server-side storage 2026-03-06 15:30:48 +07:00
login.go feat: add alias creation UI with domain selector on profile page 2026-03-08 14:02:21 +07:00
lost.go fix(reset): validate token on GET and surface errors on POST 2026-03-12 15:05:25 +07:00
main.go docs: add README and dex custom theme 2026-03-08 14:02:21 +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(reset): validate token on GET and surface errors on POST 2026-03-12 15:05:25 +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