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
This commit is contained in:
Harwinder Sekhon
2026-07-30 21:14:07 -04:00
parent 0bd11f6ab3
commit 17a401c313
22 changed files with 433 additions and 102 deletions
+4 -6
View File
@@ -23,10 +23,9 @@ function asset(string $path): string
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#F2F0EA">
<title>CourseHub</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.css" rel="stylesheet" data-boot-critical>
<!-- All assets are local (assets/vendor/) — the app runs with no internet access. -->
<link href="<?= asset('assets/vendor/inter.css') ?>" rel="stylesheet">
<link href="<?= asset('assets/vendor/plyr.css') ?>" rel="stylesheet" data-boot-critical>
<link href="<?= asset('assets/style.css') ?>" rel="stylesheet" data-boot-critical>
<!-- Installed before every other script so it can report failures in them
@@ -137,8 +136,7 @@ function asset(string $path): string
<div class="toasts" id="toasts"></div>
<div id="overlay-root"></div>
<script src="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.min.js" data-boot-critical></script>
<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.2/lib/anime.min.js" data-boot-critical></script>
<script src="<?= asset('assets/vendor/plyr.min.js') ?>" data-boot-critical></script>
<script src="<?= asset('assets/app.js') ?>" data-boot-critical></script>
</body>
</html>