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:
Pontoporeia
2026-06-11 13:05:37 +02:00
parent 00fed5f0e3
commit d588ae004d
81 changed files with 1061 additions and 840 deletions

View File

@@ -1,275 +0,0 @@
# Managing Admin Users - Post-ERG
Quick guide to manage admin users for the Post-ERG admin panel.
---
## 🎯 Quick Commands
### Interactive Menu (Recommended)
```bash
# From your local machine
just manage-admin-users
# Then on the server
ssh xamxam
sudo bash /tmp/manage-admin-users.sh
```
This gives you an interactive menu to:
1. List all users
2. Add new user
3. Change user password
4. Delete user
5. Reset all (start fresh)
---
## 📝 Manual Commands
### List Current Users
```bash
ssh xamxam
sudo cut -d: -f1 /etc/nginx/.htpasswd-xamxam
```
### Change Password for Existing User
```bash
ssh xamxam
sudo htpasswd /etc/nginx/.htpasswd-xamxam username_here
```
You'll be prompted to enter the new password twice.
### Add New User
```bash
ssh xamxam
sudo htpasswd /etc/nginx/.htpasswd-xamxam new_username
```
### Delete User
```bash
ssh xamxam
sudo htpasswd -D /etc/nginx/.htpasswd-xamxam username_to_delete
```
### Reset Everything (Start Fresh)
```bash
ssh xamxam
sudo htpasswd -c /etc/nginx/.htpasswd-xamxam new_username
```
⚠️ **Warning:** The `-c` flag creates a new file, deleting all existing users!
---
## 🚀 Deploy Management Script
To upload the interactive management script to the server:
```bash
# From your local machine
just manage-admin-users
# Or manually:
rsync -v scripts/manage-admin-users.sh xamxam:/tmp/manage-admin-users.sh
```
---
## 🔑 Current Setup
After deployment, your admin panel has:
- **URL:** https://xamxam.erg.be/admin/
- **Current user:** `test_posterg_22@`
- **Password:** Set during initial deployment
---
## 💡 Common Scenarios
### Scenario 1: Change Current Password
```bash
ssh xamxam
sudo htpasswd /etc/nginx/.htpasswd-xamxam test_posterg_22@
# Enter new password when prompted
```
### Scenario 2: Change Username
Since you can't rename users, you need to:
```bash
ssh xamxam
# Add new user
sudo htpasswd /etc/nginx/.htpasswd-xamxam new_username
# Delete old user
sudo htpasswd -D /etc/nginx/.htpasswd-xamxam test_posterg_22@
```
### Scenario 3: Forgot Username
```bash
ssh xamxam
sudo cut -d: -f1 /etc/nginx/.htpasswd-xamxam
```
### Scenario 4: Multiple Admins
```bash
ssh xamxam
# Add second admin
sudo htpasswd /etc/nginx/.htpasswd-xamxam admin2
# Add third admin
sudo htpasswd /etc/nginx/.htpasswd-xamxam admin3
```
All users can log into `/admin/` with their own credentials.
### Scenario 5: Start Over with New Username
```bash
ssh xamxam
# This will DELETE ALL existing users and create a new one
sudo htpasswd -c /etc/nginx/.htpasswd-xamxam new_admin
```
---
## 🧪 Testing
After changing users/passwords:
```bash
# Test that password is required
curl -I https://xamxam.erg.be/admin/
# Should return: 401 Unauthorized
# Test with credentials
curl -u username:password https://xamxam.erg.be/admin/
# Should return: 200 OK
```
No nginx reload needed - changes take effect immediately!
---
## 📊 Password File Details
**Location:** `/etc/nginx/.htpasswd-xamxam`
**Format:** Standard Apache htpasswd format
```
username:$apr1$encrypted_password_hash
```
**Permissions:**
```bash
-rw-r--r-- root root /etc/nginx/.htpasswd-xamxam
```
---
## 🔒 Security Tips
1. **Use Strong Passwords**
```bash
# Generate a strong password
openssl rand -base64 32
```
2. **Avoid Common Usernames**
- ❌ Bad: `admin`, `administrator`, `root`
- ✅ Good: `xamxam_admin`, `erg_webmaster`
3. **Regular Password Changes**
- Change passwords every 3-6 months
- Change immediately if compromised
4. **Monitor Access**
```bash
# Check who's accessing the admin panel
ssh xamxam
sudo grep "admin" /var/log/nginx/xamxam_access.log
```
5. **Backup Password File**
```bash
ssh xamxam
sudo cp /etc/nginx/.htpasswd-xamxam /etc/nginx/.htpasswd-xamxam.backup
```
---
## 🆘 Troubleshooting
### "401 Unauthorized" even with correct password
**Check file exists:**
```bash
ssh xamxam
ls -la /etc/nginx/.htpasswd-xamxam
```
**Verify user exists:**
```bash
sudo cat /etc/nginx/.htpasswd-xamxam
```
**Check nginx config:**
```bash
sudo grep -A 5 "auth_basic" /etc/nginx/sites-available/xamxam
```
### Can't change password - "command not found"
**Install apache2-utils:**
```bash
ssh xamxam
sudo apt update
sudo apt install apache2-utils
```
### Password file got deleted
**Recreate it:**
```bash
ssh xamxam
sudo htpasswd -c /etc/nginx/.htpasswd-xamxam new_admin
```
---
## 📞 Quick Reference
| Task | Command |
|------|---------|
| **Interactive menu** | `sudo bash /tmp/manage-admin-users.sh` |
| **List users** | `sudo cut -d: -f1 /etc/nginx/.htpasswd-xamxam` |
| **Change password** | `sudo htpasswd /etc/nginx/.htpasswd-xamxam username` |
| **Add user** | `sudo htpasswd /etc/nginx/.htpasswd-xamxam newuser` |
| **Delete user** | `sudo htpasswd -D /etc/nginx/.htpasswd-xamxam username` |
| **Reset all** | `sudo htpasswd -c /etc/nginx/.htpasswd-xamxam newuser` |
| **Generate password** | `openssl rand -base64 32` |
---
## ✅ After Making Changes
No action needed! Changes to the password file take effect immediately.
You can verify with:
```bash
curl -u username:password https://xamxam.erg.be/admin/
```
---
**Remember:** Store passwords securely using a password manager! 🔐

View File

@@ -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 |