<?php
/**
 * Ragreen - Minimal JWT (HS256) helper
 *
 * Used only for the Flutter mobile app's access tokens (short-lived, stateless -
 * see includes/auth.php). The web app still uses PHP sessions; this never
 * touches sessions or cookies.
 *
 * Deliberately hand-rolled instead of pulling in firebase/php-jwt via Composer -
 * this project stays plain PHP with no dependency manager (see README), and
 * HS256 encode/decode is genuinely small. Only HS256 is supported - that's
 * all this app needs.
 *
 * Access tokens are short-lived (see JWT_ACCESS_TTL in config.php) and are
 * NOT stored anywhere server-side - they're stateless by design, verified
 * purely by signature + expiry. Only refresh tokens are stored (in
 * auth_refresh_tokens), so they can be revoked (logout, "log out everywhere").
 */

function jwt_base64url_encode(string $data): string
{
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

function jwt_base64url_decode(string $data): string|false
{
    $remainder = strlen($data) % 4;
    if ($remainder) {
        $data .= str_repeat('=', 4 - $remainder);
    }
    return base64_decode(strtr($data, '-_', '+/'), true);
}

/**
 * Encode a JWT. $payload should NOT include 'exp'/'iat' - pass $ttlSeconds instead.
 */
function jwt_encode(array $payload, int $ttlSeconds): string
{
    $header = ['typ' => 'JWT', 'alg' => 'HS256'];
    $now = time();
    $payload['iat'] = $now;
    $payload['exp'] = $now + $ttlSeconds;

    $segments = [
        jwt_base64url_encode(json_encode($header)),
        jwt_base64url_encode(json_encode($payload)),
    ];
    $signingInput = implode('.', $segments);
    $signature = hash_hmac('sha256', $signingInput, APP_SECRET, true);
    $segments[] = jwt_base64url_encode($signature);

    return implode('.', $segments);
}

/**
 * Decode + verify a JWT. Returns the payload array, or null if invalid/expired/malformed.
 * Never throws - callers just check for null and respond 401, same as an
 * absent session would.
 */
function jwt_decode(string $token): ?array
{
    $parts = explode('.', $token);
    if (count($parts) !== 3) {
        return null;
    }
    [$headerB64, $payloadB64, $sigB64] = $parts;

    $header = json_decode((string) jwt_base64url_decode($headerB64), true);
    $payload = json_decode((string) jwt_base64url_decode($payloadB64), true);
    $signature = jwt_base64url_decode($sigB64);

    if (!is_array($header) || !is_array($payload) || $signature === false) {
        return null;
    }
    if (($header['alg'] ?? null) !== 'HS256') {
        return null; // reject alg=none and anything else outright
    }

    $signingInput = $headerB64 . '.' . $payloadB64;
    $expectedSignature = hash_hmac('sha256', $signingInput, APP_SECRET, true);
    if (!hash_equals($expectedSignature, $signature)) {
        return null;
    }

    if (!isset($payload['exp']) || time() >= (int) $payload['exp']) {
        return null; // expired
    }

    return $payload;
}
