mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
- lib/AdminAuth.php: new class with requireLogin(), login(), logout(), isAuthenticated(); starts session with hardened cookie params (HttpOnly, SameSite=Strict, Secure, Path=/admin) — also resolves item #8 (session cookie hardening) - requireLogin() auto-authenticates from nginx Basic Auth credentials ($_SERVER['PHP_AUTH_PW']) so the user only sees one browser prompt; falls back to /admin/login.php if the proxy is absent/misconfigured - config/admin_credentials.php: gitignored credential store; define ADMIN_PASSWORD_HASH with a bcrypt hash to enable PHP auth - config/admin_credentials.example.php: template for the above - config/bootstrap.php: auto-loads admin_credentials.php if present - .gitignore: exclude config/admin_credentials.php - public/admin/login.php: fallback login form (shown only when nginx Basic Auth is bypassed / proxy absent) - public/admin/logout.php: session destruction + redirect to login - All 7 admin PHP files: replace session_start() with AdminAuth::requireLogin() (defence-in-depth behind nginx Basic Auth) - public/admin/inc/head.php: Déconnexion button when ADMIN_PASSWORD_HASH is defined - nginx/PHP_AUTH_LAYER.md: documents dual-auth architecture, UX flow, and setup instructions - docs/TODO.SECURITY.md: items #2 and #8 moved to Resolved; priority order updated (all CRITICAL done)
115 lines
3.8 KiB
Markdown
115 lines
3.8 KiB
Markdown
# PHP Session Auth Layer — Admin Panel
|
|
|
|
> Addresses: **TODO item #2** (No PHP-level authentication in admin panel — 🔴 CRITICAL)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The admin panel uses **two independent authentication layers** with a single UX prompt:
|
|
|
|
| Layer | Mechanism | Configured by |
|
|
|-------|-----------|---------------|
|
|
| **1st** | nginx HTTP Basic Auth | `/etc/nginx/.htpasswd-posterg` (see `ADMIN_USERS.md`) |
|
|
| **2nd** | PHP session guard (`lib/AdminAuth.php`) | `config/admin_credentials.php` |
|
|
|
|
The user only sees **one prompt** (the browser Basic Auth dialog). PHP reads the
|
|
same password from `$_SERVER['PHP_AUTH_PW']` and validates it independently with
|
|
`password_verify`. On success it creates a session so subsequent requests skip
|
|
the `password_verify` call.
|
|
|
|
---
|
|
|
|
## Why two layers?
|
|
|
|
nginx Basic Auth alone is a **single point of failure**:
|
|
|
|
- Reverse-proxy misconfiguration could expose admin routes directly.
|
|
- Local development without the proxy leaves admin unprotected.
|
|
- A misconfigured `auth_basic off` block (e.g., in a nested location) could bypass it.
|
|
|
|
The PHP session guard (`AdminAuth::requireLogin()`) is ~100 lines of PHP stdlib
|
|
(`password_verify` + `session_regenerate_id`) with negligible attack surface.
|
|
|
|
## Authentication flow
|
|
|
|
```
|
|
Browser → nginx Basic Auth dialog (username + password)
|
|
│
|
|
▼
|
|
nginx validates against .htpasswd ──✗──▶ 401
|
|
│ ✓
|
|
▼
|
|
PHP: AdminAuth::requireLogin()
|
|
├─ session already live? ──✓──▶ proceed
|
|
├─ $_SERVER['PHP_AUTH_PW'] set?
|
|
│ └─ password_verify(PHP_AUTH_PW, ADMIN_PASSWORD_HASH)
|
|
│ ├─ ✓ → create session → proceed (normal path)
|
|
│ └─ ✗ → redirect to login form
|
|
└─ neither → redirect to login form (proxy bypass)
|
|
```
|
|
|
|
The login form (`/admin/login.php`) is a **fallback** for when the reverse proxy
|
|
is absent. In normal production use the user never sees it.
|
|
|
|
---
|
|
|
|
## PHP auth setup (production)
|
|
|
|
1. Generate a bcrypt hash for the admin password:
|
|
```bash
|
|
php -r "echo password_hash('your-secret-password', PASSWORD_DEFAULT);"
|
|
```
|
|
|
|
2. Create `config/admin_credentials.php` (outside the webroot, never committed):
|
|
```php
|
|
<?php
|
|
define('ADMIN_PASSWORD_HASH', '$2y$12$<paste-hash-here>');
|
|
```
|
|
|
|
3. The `bootstrap.php` auto-loads this file if it exists.
|
|
|
|
If `ADMIN_PASSWORD_HASH` is not defined (development / cli-server), the PHP
|
|
auth layer is a **no-op** — nginx Basic Auth remains the sole guard.
|
|
|
|
---
|
|
|
|
## Session cookie hardening (TODO item #8)
|
|
|
|
`AdminAuth::startSession()` calls `session_set_cookie_params()` before
|
|
`session_start()`, applying:
|
|
|
|
| Attribute | Value |
|
|
|-----------|-------|
|
|
| `HttpOnly` | `true` |
|
|
| `SameSite` | `Strict` |
|
|
| `Secure` | `true` (disabled on cli-server for dev) |
|
|
| `Path` | `/admin` |
|
|
| `Lifetime` | `0` (session cookie, expires on browser close) |
|
|
|
|
This replaces all direct `session_start()` calls in admin PHP files.
|
|
|
|
---
|
|
|
|
## Logout
|
|
|
|
A **Déconnexion** button is shown in the admin nav when `ADMIN_PASSWORD_HASH`
|
|
is defined. It hits `/admin/logout.php` which destroys the PHP session.
|
|
nginx Basic Auth invalidation requires closing the browser tab / window.
|
|
|
|
---
|
|
|
|
## Files changed
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `lib/AdminAuth.php` | New — auth guard class |
|
|
| `config/admin_credentials.php` | New — credential store (gitignored) |
|
|
| `config/admin_credentials.example.php` | New — example / template |
|
|
| `config/bootstrap.php` | Load credentials on startup |
|
|
| `public/admin/*.php` | Replace `session_start()` with `AdminAuth::requireLogin()` |
|
|
| `public/admin/actions/*.php` | Same |
|
|
| `public/admin/login.php` | New — login form |
|
|
| `public/admin/logout.php` | New — logout handler |
|
|
| `public/admin/inc/head.php` | Logout button in nav |
|