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

+6
View File
@@ -26,6 +26,8 @@ This repo covers the **frontend only** — the public-facing website. Icecast co
- [includes/broadcasts.php](includes/broadcasts.php) — Broadcast lifecycle and track history logic.
- [cli/poll_icecast.php](cli/poll_icecast.php) — Cron-run poller that records broadcasts and tracks.
- [cli/set_show.php](cli/set_show.php) — Sets which show is airing next (or now).
- [cli/create_admin.php](cli/create_admin.php) — Creates an admin account or resets its password.
- [admin/](admin/) — Admin backend. Every page calls `requireAdmin()` from [includes/auth.php](includes/auth.php) and uses `adminHeader()`/`adminFooter()` from [includes/admin_layout.php](includes/admin_layout.php).
## Architecture
@@ -43,6 +45,10 @@ MariaDB on localhost. Credentials live in `config/db_credentials.php`, which is
`cli/poll_icecast.php` runs from cron every minute and polls Icecast four times, 15 seconds apart. A broadcast starts when the stream comes online and ends after five minutes unseen (so brief drop-outs don't split a show). Before going live, run `php cli/set_show.php <slug>` to choose the show.
## Admin Authentication
Username and password only; the site sends no email. Accounts are created and passwords reset over SSH with `php cli/create_admin.php <username>`. Passwords are hashed with `password_hash()` (PASSWORD_DEFAULT). Sessions use a `rfe_admin` cookie scoped to the admin directory (HttpOnly, SameSite=Strict, Secure over HTTPS) and expire after four hours idle. Five failed logins from one IP within 15 minutes locks that IP out for the rest of the window. Every POST form must include `csrfField()` and its handler must call `verifyCsrf()` first.
## Icecast Integration
- **Stream URL:** `https://radio.cyberpunklibrarian.nohost.me/rfe`