mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration
Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
This commit is contained in:
@@ -6,53 +6,32 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The admin panel uses **two independent authentication layers** with a single UX prompt:
|
||||
The admin panel uses a single **PHP session-based authentication** layer.
|
||||
Authentication is password-only (no username required).
|
||||
|
||||
| Layer | Mechanism | Configured by |
|
||||
|-------|-----------|---------------|
|
||||
| **1st** | nginx HTTP Basic Auth | `/etc/nginx/.htpasswd-xamxam` (see `ADMIN_USERS.md`) |
|
||||
| **2nd** | PHP session guard (`src/AdminAuth.php`) | `config/admin_credentials.php` |
|
||||
| **PHP** | Session guard (`src/AdminAuth.php`) | `site_settings.admin_password_hash` in DB |
|
||||
|
||||
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.
|
||||
The user sees an HTML login form at `/admin/login.php` that asks only for a
|
||||
password. On successful login, a PHP session is created and all admin pages
|
||||
use `AdminAuth::requireLogin()` to enforce the guard.
|
||||
|
||||
## Authentication flow
|
||||
|
||||
```
|
||||
Browser → nginx Basic Auth dialog (username + password)
|
||||
Browser → /admin/login.php (HTML password-only form)
|
||||
│
|
||||
▼
|
||||
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)
|
||||
POST password → AdminAuth::login()
|
||||
├─ password_verify(password, stored_hash)
|
||||
│ ├─ ✓ → create session → redirect to /admin/
|
||||
│ └─ ✗ → show error, stay on login form
|
||||
└─
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
If no password hash is stored in the DB (dev / cli-server), `AdminAuth`
|
||||
is a no-op — all admin pages are open.
|
||||
|
||||
## PHP auth setup (production)
|
||||
|
||||
@@ -61,19 +40,13 @@ is absent. In normal production use the user never sees it.
|
||||
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>');
|
||||
2. Store it in the DB via the admin panel at `/admin/parametres` (Account tab)
|
||||
or by inserting directly:
|
||||
```sql
|
||||
INSERT INTO site_settings (key, value) VALUES ('admin_password_hash', '$2y$12$...')
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value;
|
||||
```
|
||||
|
||||
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
|
||||
@@ -87,28 +60,15 @@ auth layer is a **no-op** — nginx Basic Auth remains the sole guard.
|
||||
| `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.
|
||||
A **Déconnexion** button is shown in the admin nav when a password hash is
|
||||
configured. It hits `/admin/logout.php` which destroys the PHP session.
|
||||
|
||||
---
|
||||
## Files
|
||||
|
||||
## Files changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/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 |
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/AdminAuth.php` | Auth guard class |
|
||||
| `public/admin/login.php` | Login form (password-only) |
|
||||
| `public/admin/logout.php` | Logout handler |
|
||||
|
||||
Reference in New Issue
Block a user