auth.php 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. <?php
  2. // auth.php – shared admin authentication helper
  3. // Include at the top of every admin page.
  4. require_once __DIR__ . '/../db.php';
  5. session_name('packit_admin');
  6. // Scope the cookie to the admin directory, keep it off JavaScript, and never
  7. // let it travel cross-site (which is what makes the CSRF tokens below the
  8. // second line of defence rather than the only one).
  9. $adminPath = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/admin/x.php')), '/') . '/';
  10. session_set_cookie_params([
  11. 'lifetime' => 0,
  12. 'path' => $adminPath,
  13. 'httponly' => true,
  14. 'secure' => request_is_https(),
  15. 'samesite' => 'Strict',
  16. ]);
  17. session_start();
  18. define('ADMIN_LOGGED_IN', isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true);
  19. function require_admin(): void {
  20. if (!ADMIN_LOGGED_IN) {
  21. header('Location: ' . admin_url('login.php'));
  22. exit;
  23. }
  24. }
  25. function admin_url(string $page): string {
  26. return $page;
  27. }
  28. function admin_logout(): void {
  29. $_SESSION = [];
  30. if (ini_get('session.use_cookies')) {
  31. $p = session_get_cookie_params();
  32. setcookie(session_name(), '', time() - 42000,
  33. $p['path'], $p['domain'], $p['secure'], $p['httponly']);
  34. }
  35. session_destroy();
  36. header('Location: login.php');
  37. exit;
  38. }
  39. // ---------------------------------------------------------------------------
  40. // CSRF
  41. // ---------------------------------------------------------------------------
  42. /** Per-session token, minted on first use and reused for the session's life. */
  43. function csrf_token(): string {
  44. if (empty($_SESSION['csrf_token'])) {
  45. $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
  46. }
  47. return $_SESSION['csrf_token'];
  48. }
  49. /** Hidden field to drop into every POST form. */
  50. function csrf_field(): string {
  51. return '<input type="hidden" name="csrf_token" value="'
  52. . htmlspecialchars(csrf_token(), ENT_QUOTES) . '">';
  53. }
  54. /**
  55. * Guard for every state-changing POST handler. Without this, any page an
  56. * admin visits while logged in could post to us on their behalf.
  57. */
  58. function require_csrf(): void {
  59. $sent = $_POST['csrf_token'] ?? '';
  60. if (!is_string($sent) || $sent === '' || !hash_equals(csrf_token(), $sent)) {
  61. http_response_code(403);
  62. exit('<p style="font-family:system-ui,sans-serif">Invalid or expired form token. '
  63. . 'Go back, reload the page and try again.</p>');
  64. }
  65. }