mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
security: add PHP session auth guard for admin panel (item #2, CRITICAL)
- 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)
This commit is contained in:
@@ -57,12 +57,15 @@ Reusable HTML components:
|
||||
|
||||
## Security
|
||||
|
||||
- All pages require HTTP Basic Auth (configured in nginx)
|
||||
- All pages require HTTP Basic Auth (configured in nginx) — primary layer
|
||||
- All pages require PHP session auth (`AdminAuth::requireLogin()`) — defence-in-depth
|
||||
- CSRF tokens protect all forms
|
||||
- File uploads validated and sanitized
|
||||
- Database queries use prepared statements
|
||||
- Upload directory outside public/ in production
|
||||
|
||||
See `nginx/PHP_AUTH_LAYER.md` for details on the dual-auth architecture.
|
||||
|
||||
## Templates
|
||||
|
||||
The `inc/` folder contains shared templates:
|
||||
@@ -96,7 +99,8 @@ Backend actions (not directly accessed):
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
session_start();
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
$pageTitle = "Your Page Title";
|
||||
?>
|
||||
<?php include "inc/head.php" ?>
|
||||
@@ -114,7 +118,8 @@ $pageTitle = "Your Page Title";
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
session_start();
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Verify CSRF token
|
||||
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php // formulaire.php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// Configure error reporting
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
// Start session for CSRF protection
|
||||
session_start();
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Verify CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
/**
|
||||
* Handle publish/unpublish actions for theses
|
||||
*/
|
||||
session_start();
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Start session and generate CSRF token
|
||||
session_start();
|
||||
if (empty($_SESSION["csrf_token"])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// Edit thesis page
|
||||
session_start();
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// CSV Import page for Post-ERG thesis database
|
||||
// This page allows importing thesis data from CSV files
|
||||
|
||||
session_start();
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
|
||||
@@ -62,5 +62,8 @@
|
||||
|
||||
echo implode(' ', $navLinks);
|
||||
?>
|
||||
<?php if (defined('ADMIN_PASSWORD_HASH')): ?>
|
||||
<a href="/admin/logout.php"><button>🔐 Déconnexion</button></a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// List all theses in the database
|
||||
session_start();
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
|
||||
60
public/admin/login.php
Normal file
60
public/admin/login.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../config/bootstrap.php';
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// If no password is configured, nothing to log into — go straight to admin.
|
||||
if (!defined('ADMIN_PASSWORD_HASH')) {
|
||||
header('Location: /admin/');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Already authenticated — redirect to admin.
|
||||
if (AdminAuth::isAuthenticated()) {
|
||||
header('Location: /admin/');
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$password = $_POST['password'] ?? '';
|
||||
if (AdminAuth::login($password)) {
|
||||
header('Location: /admin/');
|
||||
exit;
|
||||
}
|
||||
// Intentionally vague error — avoid user-enumeration.
|
||||
$error = 'Mot de passe incorrect.';
|
||||
}
|
||||
|
||||
$pageTitle = 'Connexion';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo htmlspecialchars($pageTitle); ?> — Post-ERG Admin</title>
|
||||
<link rel="stylesheet" href="/assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="/assets/admin.css">
|
||||
<link rel="shortcut icon" href="/assets/admin_favicon.svg" type="image/svg+xml">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><?php echo htmlspecialchars($pageTitle); ?></h1>
|
||||
</header>
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert-error">
|
||||
<strong>⚠️ <?php echo htmlspecialchars($error); ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="/admin/login.php">
|
||||
<fieldset>
|
||||
<legend>Authentification admin</legend>
|
||||
<label for="password">Mot de passe</label>
|
||||
<input type="password" id="password" name="password" required autofocus>
|
||||
<button type="submit">Se connecter</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
8
public/admin/logout.php
Normal file
8
public/admin/logout.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../config/bootstrap.php';
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
AdminAuth::logout();
|
||||
|
||||
header('Location: /admin/login.php');
|
||||
exit;
|
||||
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
||||
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Configure error reporting
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
Reference in New Issue
Block a user