mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
- tag-search: add minTags/required params, counter shows red if < 3, accent if ≥ 3 - form.php: pass minTags=3 for partage mode keywords - checkbox-list: support labelHtml for raw HTML label with targetable asterisk span - language-autre-fragment: OOB swap updates #languages-required-asterisk when autre pills change - language-search: client-side update #languages-required-asterisk on pill add/remove - contenus.php: replace 3 form+submit-button fieldsets with HTMX auto-save checkboxes - settings.php: detect HX-Request header, return OOB CSRF token updates, skip redirect
7.6 KiB
7.6 KiB
LDAP Authentication Specification for XAMXAM Admin
Current state
Two-layer authentication guards the /admin/ area:
| Layer | Mechanism | Where |
|---|---|---|
| 1 (nginx) | auth_basic against /etc/nginx/.htpasswd-xamxam |
nginx/xamxam.conf |
| 2 (PHP) | AdminAuth — bcrypt hash in site_settings.admin_password_hash |
app/src/AdminAuth.php, app/public/admin/login.php, app/public/admin/actions/account.php |
Layer 1 controls the browser's Basic Auth dialog. Layer 2 provides a PHP session gate and a
fallback login form. When both layers share the same password, the user is authenticated
transparently (nginx passes PHP_AUTH_PW to PHP, AdminAuth verifies it against the DB hash).
Goal
Replace both layers with LDAP-based authentication while preserving the defence-in-depth structure and the transparent user experience (single sign-on via the browser's Basic Auth dialog, no PHP login form unless fallback).
Required information from IT
| # | Item | Example / format |
|---|---|---|
| 1 | LDAP server URL | ldaps://ldap.erg.be:636 or ldap://ldap.erg.be:389 |
| 2 | Base DN | dc=erg,dc=be |
| 3 | Bind DN (service / search account) | cn=svc-xamxam,ou=services,dc=erg,dc=be |
| 4 | Bind password | (secret — read-only account is sufficient) |
| 5 | User search filter | (&(uid=%s)(memberOf=cn=admin-xamxam,ou=groups,dc=erg,dc=be)) — %s is the username entered in the Basic Auth dialog |
| 6 | Group membership mechanism | memberOf attribute (AD-style) or member/uniqueMember on the group entry (OpenLDAP-style) |
| 7 | Username attribute | Typically uid (OpenLDAP) or sAMAccountName (AD). What attribute should the user type in the auth dialog? |
| 8 | TLS certificate | If ldaps:// is used and the certificate is self-signed, provide the CA certificate (PEM). Otherwise confirm it's a publicly-trusted cert. |
| 9 | Admin group DN/CN | The exact DN or CN that grants admin access (e.g. cn=xamxam-admins,ou=groups,dc=erg,dc=be). If there's no group yet, what should it be named? |
Architecture
Browser Nginx LDAP daemon LDAP server
│ │ │ │
│─ GET /admin/ ──────────►│ │ │
│◄── 401 WWW-Authenticate │ │ │
│─ GET /admin/ + Basic ──►│ │ │
│ │─ POST /auth-ldap ───────────►│ │
│ │ (proxy Authorization hdr) │─ ldap_bind ──────────►│
│ │ │◄── success ───────────│
│ │ │─ ldap_search ────────►│
│ │ │◄── group check OK ────│
│ │◄── 200 OK ──────────────────│ │
│ │─ forward to PHP ────────────► │
│ │ │ │
│◄── admin page ─────────│ │
Option A — nginx-ldap-auth daemon (preferred)
- Drop-in replacement for
auth_basic/.htpasswdusing nginx'sauth_requestmodule - A small Python 3 daemon (
nginx-ldap-auth) runs at127.0.0.1:8888 - Configured via
/etc/nginx-ldap-auth.conf(JSON or YAML) - Nginx proxies the
Authorizationheader to the daemon; daemon binds to LDAP, checks group membership, returns 200 or 403 - The PHP
AdminAuthlayer remains — it receivesPHP_AUTH_PWfrom nginx, can verify the username against LDAP group membership, and establish the PHP session
Nginx config (add to location ^~ /admin/):
location ^~ /admin/ {
# Replace auth_basic + auth_basic_user_file with:
auth_request /auth-ldap;
auth_request_set $saved_set_cookie $upstream_http_set_cookie;
add_header Set-Cookie $saved_set_cookie;
# Client-facing Basic Auth challenge (so the browser asks for credentials)
satisfy any;
# Fallback: if auth_request returns 401, challenge
error_page 401 = @ldap_challenge;
# Keep: rate limiting, CSP, PHP handling, security headers
limit_req zone=admin burst=20 nodelay;
# ... rest as-is ...
}
# Internal endpoint — delegates to LDAP daemon
location = /auth-ldap {
internal;
proxy_pass http://127.0.0.1:8888;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Authorization $http_authorization;
}
# Trigger browser Basic Auth dialog when LDAP returns 401
location @ldap_challenge {
add_header WWW-Authenticate 'Basic realm="Admin Access - XAMXAM"';
return 401;
}
Option B — ngx_http_auth_ldap_module (native nginx module)
- Requires recompiling nginx with this third-party module
- Simpler config:
auth_ldap "XAMXAM Admin"; auth_ldap_servers { ... } - Less flexible; harder to debug
Option C — PHP-only LDAP (no nginx layer)
- Remove nginx auth entirely
AdminAuth::requireLogin()doesldap_bind()+ group check directly in PHP- Simpler nginx config, but no nginx-level gate
- Browser auth dialog still possible via PHP sending
WWW-Authenticateheader
After LDAP is working: cleanup checklist
| Step | File(s) affected | Action |
|---|---|---|
| 1 | app/src/AdminAuth.php |
Remove getStoredHash(), setPasswordHash(), removePasswordHash(), hasPassword(), verifyHash(). Keep requireLogin(), isAuthenticated(), login(), logout() — adapt them to LDAP group check. |
| 2 | app/public/admin/login.php |
Remove entirely (no more PHP login form). |
| 3 | app/public/admin/actions/account.php |
Remove entirely (no more password CRUD). |
| 4 | app/templates/admin/login.php |
Remove template file. |
| 5 | app/templates/admin/parametres.php |
Remove the "Compte administrateur" <section> (password set/change/delete UI). |
| 6 | app/public/admin/parametres.php |
Remove AdminAuth::hasPassword() call and related variables. |
| 7 | app/templates/admin/account.php |
Remove if only used for password management. |
| 8 | nginx/xamxam.conf |
Remove auth_basic and auth_basic_user_file lines from the admin location block. |
| 9 | Database | Remove admin_password_hash row from site_settings table (manual or migration). |
| 10 | app/bootstrap.php |
Remove legacy ADMIN_PASSWORD_HASH constant reference if present. |
Dependencies to install
- Option A: Python 3,
python3-ldap(orpip install python-ldap),nginx-ldap-authdaemon - Option B: nginx recompiled with
ngx_http_auth_ldap_module - Option C: PHP
ldapextension (php8.4-ldaporapt install php-ldap)
Notes
- The
AdminAuthPHP layer should remain even after LDAP is implemented — it provides session persistence, logout, CSRF integration, and the admin audit log identity. - The LDAP daemon/nginx layer handles authentication (who are you?).
The PHP
AdminAuthlayer handles session management (are you still you?). - If IT provides a dedicated admin group, access control is centralised: adding/removing an admin is a single LDAP operation, no need to touch the server.