Add admin login

- admins and login_attempts tables
- cli/create_admin.php: create an admin or reset a password over SSH
  (no email involved)
- includes/auth.php: hardened admin sessions, login throttling (5
  failures per IP per 15 minutes) and CSRF helpers
- admin/: login, logout and a dashboard showing broadcast status
- includes/admin_layout.php: shared admin page layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel MesserandClaude Opus 5.5 committed 2026-10-29 07:13:51 -05:00
1 parent 74dfb3c146
commit babb71660c
8 files changed
+389

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
<?php
require __DIR__ . '/../includes/auth.php';
require __DIR__ . '/../includes/admin_layout.php';
require __DIR__ . '/../includes/broadcasts.php';
$admin = requireAdmin();
$db = getDB();
$live = liveBroadcast($db);
$show = currentShow($db);
adminHeader('Dashboard', $admin);
?>
<p>Logged in as <strong><?= e($admin['username']) ?></strong>.</p>
<h3>Broadcast status</h3>
<p>
<?php if ($live): ?>
Live now<?= $show ? ': <strong>' . e($show['name']) . '</strong>' : ' (no show set)' ?>, since <?= e($live['started_at']) ?> UTC.
<?php elseif ($show): ?>
Offline. <strong><?= e($show['name']) ?></strong> is queued for the next broadcast.
<?php else: ?>
Offline. No show is queued.
<?php endif; ?>
</p>
<?php
adminFooter();
+49
View File
@@ -0,0 +1,49 @@
<?php
require __DIR__ . '/../includes/auth.php';
require __DIR__ . '/../includes/admin_layout.php';
if (currentAdmin() !== null) {
header('Location: index.php');
exit;
}
$error = null;
$username = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verifyCsrf();
$username = trim((string)($_POST['username'] ?? ''));
$password = (string)($_POST['password'] ?? '');
$error = attemptLogin($username, $password);
if ($error === null) {
header('Location: index.php');
exit;
}
}
adminHeader('Log In');
?>
<?php if ($error): ?>
<p class="admin-message error"><?= e($error) ?></p>
<?php endif; ?>
<form method="post" action="login.php">
<?= csrfField() ?>
<div class="row gtr-uniform gtr-50">
<div class="col-12">
<label for="username">Username</label>
<input type="text" name="username" id="username" value="<?= e($username) ?>" autocomplete="username" required autofocus />
</div>
<div class="col-12">
<label for="password">Password</label>
<input type="password" name="password" id="password" autocomplete="current-password" required />
</div>
<div class="col-12">
<ul class="actions">
<li><input type="submit" class="primary" value="Log In" /></li>
</ul>
</div>
</div>
</form>
<?php
adminFooter();
+11
View File
@@ -0,0 +1,11 @@
<?php
require __DIR__ . '/../includes/auth.php';
// Logout is POST-only with a CSRF token, so other sites can't log you out with a link or image
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verifyCsrf();
startAdminSession();
logoutAdmin();
}
header('Location: login.php');