<?php
/**
 * CSRF token helpers. Session-bound, regenerated each login.
 *
 *   csrf_field()         – emits a hidden <input> for forms.
 *   csrf_token()         – returns the raw token (for AJAX).
 *   csrf_check()         – returns true|false; checks $_POST['_csrf'] or X-CSRF-Token.
 *   csrf_require()       – 403s on mismatch. Call at the top of POST handlers.
 *   csrf_rotate()        – issue a fresh token (call after login + privileged actions).
 */

function csrf_token(): string
{
    if (empty($_SESSION['_csrf_token'])) {
        $_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['_csrf_token'];
}

function csrf_rotate(): string
{
    $_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
    return $_SESSION['_csrf_token'];
}

function csrf_field(): string
{
    $t = csrf_token();
    return '<input type="hidden" name="_csrf" value="' . htmlspecialchars($t, ENT_QUOTES) . '">';
}

function csrf_check(): bool
{
    $sent = $_POST['_csrf'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!is_string($sent) || $sent === '' || empty($_SESSION['_csrf_token'])) return false;
    return hash_equals($_SESSION['_csrf_token'], $sent);
}

function csrf_require(): void
{
    if (!csrf_check()) {
        http_response_code(403);
        if (!headers_sent()) header('Content-Type: text/plain; charset=utf-8');
        echo "Forbidden: CSRF token invalid or expired. Reload the page and try again.";
        exit;
    }
}
