Commit Graph

9 Commits

Author SHA1 Message Date
Harwinder Sekhon eb93303c09 Let the login throttle see real client IPs behind Cloudflare
Behind a proxy REMOTE_ADDR is the proxy, so the per-IP limit would lump
every visitor together. New optional CLIENT_IP_HEADER in config.php
(e.g. 'CF-Connecting-IP') tells the throttle which header carries the
real address; invalid or absent values fall back to REMOTE_ADDR, and
configs that predate the setting behave as before. Only for origins
unreachable except through the proxy — documented in the README, which
also covers the mod_remoteip/real_ip alternative.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SePf61iazhCLJBefdRLBDm
2026-07-30 21:17:46 -04:00
Harwinder Sekhon 17a401c313 Harden auth, add subtitles, vendor all assets locally
- Session cookies are HttpOnly + SameSite=Lax (Secure on HTTPS), with
  strict session-ID mode; the CSRF token from GET api/auth.php must now
  accompany every state-changing request (X-CSRF-Token header, or a csrf
  body field for the sendBeacon progress save), rotated at login.
- Failed sign-ins are throttled per username and IP via login_attempts
  (created lazily, so existing installs need no migration): 5 failures a
  username / 20 an IP inside 15 minutes answer 429 with Retry-After.
- A video's sibling .srt now appears as a CC button in the player;
  captions.php converts it to WebVTT (comma timestamps, legacy encodings,
  and the already-VTT files with out-of-spec 6-digit fractions).
- Plyr and Inter ship in assets/vendor/ instead of coming from CDNs, and
  anime.js is gone along with all scripted animations and the motion
  setting — the app now runs with no internet access at all.
- .htaccess denies direct access to videos/, includes/, config.php and
  schema.sql, so video URLs cannot bypass login on Apache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SePf61iazhCLJBefdRLBDm
2026-07-30 21:14:07 -04:00
Harwinder Sekhon a3f67bf017 Fix blank page from stale cached assets; surface errors
A browser or CDN holding an assets/app.js from before the shell's element
IDs were renamed runs it against the newer index.php, where it grabs
btn-logout/btn-rescan/nav-user/nav-username/app — all now null — and throws
before it can unhide #app-shell. The result is a blank page whose only clue
is a TypeError in the console.

- index.php: asset() stamps local css/js with the file mtime, so a deploy is
  a new URL and a stale copy can't be paired with a newer shell.
- index.php: install an error handler ahead of every other script (capture
  phase, so failed resource loads are caught too) that paints uncaught
  errors, rejections and load failures into a #boot-error panel. It works
  even when app.js itself is what failed, which is this case.
- includes/db.php: add a register_shutdown_function fatal handler, since
  set_exception_handler never sees parse errors, exhausted memory or a
  failed require — those were ending requests with an empty body. Both
  handlers flush partial output and answer in JSON; display_errors stays off
  so a warning can't corrupt a JSON body.
- New optional DEBUG config flag adds exception type, file:line and trace to
  those responses. config.php is gitignored, so installs without the setting
  default to false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oyi9DMdhALrGz989MNNZJ
2026-07-27 14:22:23 -04:00
Harwinder Sekhon ff9a351091 Remove course thumbnails and the ffmpeg dependency
Course cards go back to the colour-block art that previously sat under the
captured frame, so the UI is unchanged apart from losing the photo.

- Drop thumb.php, generateThumbnails(), captureThumbnail() and
  ensureThumbnailColumns(), plus the first-video tracking the scanner only
  kept to pick a frame source.
- Drop the ffprobe fallback in probeDuration(); the pure-PHP mvhd parse is
  now the only scan-time probe, with the player's report on first play as
  the remaining fallback.
- Drop THUMBNAILS_DIR/THUMBNAIL_SEEK_PERCENT from config, the
  thumbnail_* columns from schema.sql, the thumbnail counters from scan
  output, .thumb img from the stylesheet, and the matching README parts.

Existing databases keep two now-unused folders.thumbnail_* columns; they
are inert and can be dropped by hand if wanted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oyi9DMdhALrGz989MNNZJ
2026-07-27 14:06:06 -04:00
Harwinder Sekhon 31b01a1a18 UI improved 2026-07-27 13:58:47 -04:00
Harwinder Sekhon e78f2a81e0 Probe video durations at scan time
Parse the mp4 mvhd box in pure PHP (ffprobe fallback) for videos with no
stored duration, so folder listings show durations before first play.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:38:41 -04:00
Harwinder Sekhon deda7c3c18 Add quiz support: parse *_quiz_solved.txt files, take and grade quizzes
Scanner indexes quiz files found next to videos ([&] questions, "- "
options, "-- Correct" markers; _notsolved twins ignored). Quizzes appear
in the folder content list interleaved with videos by filename order.
New api/quiz.php serves questions without answers and grades attempts
server-side; per-user best/last score and attempts stored in
quiz_progress. Folder/course progress now aggregates videos + quizzes
with partial credit for partial scores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:24:37 -04:00
Harwinder Sekhon aba6ef4734 Fix HTTP 500 on PHP < 8.2: drop standalone true union type
registerUser() declared `: string|true`; the bare `true` type requires
PHP 8.2, so including auth.php was a fatal error on PHP 8.0/8.1 — every
login/API call returned 500 while install.php (which doesn't include
auth.php) worked. Use string|bool instead. Also install a JSON exception
handler so future server errors surface a message to the SPA instead of
a blank 500.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:11:30 -04:00
Harwinder Sekhon b9c92540d4 CourseHub: AJAX video course player (PHP + MySQL + Bootstrap + Plyr)
Scans nested course folders for mp4s, tracks per-user resume position
and per-folder progress. SPA frontend with hash routing over a JSON API;
range-request streaming via stream.php.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:25:35 -04:00