b882c304b1
Admin login & user management (novaconium/ISSUES.md): session-based
login against a SQLite users table replaces the single-user HTTP Basic
Auth stopgap (admin_username/admin_password_hash and /admin/password-hash
are gone; one admin_auth_enabled flag, off by default with zero DB
footprint). New /admin/login, /admin/logout (POST-only, real page), and
/admin/users pages plus bin/create-admin-user.php.
First user created is the admin; everyone after is registered with a
unique normalized email and an optional group. /admin/* and drafts are
admin-only; Lib\Access gates page content from sidecars
(Access::require('group:members')) with login-redirect/404 responses —
public by default, static pages always public by construction. User
management covers disable/enable, delete, promote/demote, group, email,
and password, with last-active-admin lockout guards.
Also: Session::regenerate() against fixation, friendly missing-PDO-driver
errors in Lib\Db, docs at /admin/docs/access-control and updates across
admin-auth/drafts/sidecars/config/libraries and README/AGENTS.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
50 lines
1.7 KiB
PHP
50 lines
1.7 KiB
PHP
<?php
|
|
|
|
use App\AdminAuth;
|
|
use App\Response;
|
|
use Lib\Csrf;
|
|
use Lib\Input;
|
|
use Lib\Session;
|
|
|
|
// Same two-step config load as /admin/login — see that sidecar. 404 when
|
|
// admin auth is off so this route has zero footprint (no session cookie,
|
|
// no Lib\Db touch) on a site that never opted in.
|
|
$config = require __DIR__ . '/../../../config.php';
|
|
$appConfigFile = __DIR__ . '/../../../../App/config.php';
|
|
if (is_file($appConfigFile)) {
|
|
$config = array_merge($config, require $appConfigFile);
|
|
}
|
|
|
|
if (!$config['admin_auth_enabled']) {
|
|
return Response::html('404 Not Found', 404);
|
|
}
|
|
|
|
// A real page now, replacing the hardcoded pre-router special case
|
|
// bootstrap.php needed back when logout meant tricking the browser into
|
|
// dropping cached Basic Auth credentials. Unlike then, this is a real
|
|
// server-side logout: the session's user id is gone afterwards, whatever
|
|
// the browser resends.
|
|
//
|
|
// POST-only, with a GET confirm form, same shape as /admin/clear-cache —
|
|
// NOT a logout-on-GET link. Sidecars must be side-effect-free on GET
|
|
// (ordinary HTTP hygiene, and the content-index crawl relies on it: it
|
|
// invokes every page's sidecar the way a real GET would, so a
|
|
// logout-on-GET here would silently end the crawling admin's own session
|
|
// the first time a lazy reindex rendered this page).
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if (!Csrf::verify(Input::post('csrf_token'))) {
|
|
return Response::redirect('/admin/logout?error=security', 303);
|
|
}
|
|
|
|
AdminAuth::logout();
|
|
Session::flash('admin_notice', 'You have been logged out.');
|
|
|
|
return Response::redirect('/admin/login', 303);
|
|
}
|
|
|
|
return [
|
|
'securityError' => Input::get('error') === 'security',
|
|
'csrfField' => Csrf::fieldName(),
|
|
'csrfToken' => Csrf::token(),
|
|
];
|