Harden published curriculum and student flows
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Core\RedisClient;
|
||||
|
||||
/** Short-lived, asset-scoped browser grant. Never place a session JWT in a URL. */
|
||||
final class AssetDownloadTicketService
|
||||
{
|
||||
private const TTL_SECONDS = 120;
|
||||
|
||||
public static function issue(int $studentId, string $assetUuid): array
|
||||
{
|
||||
if ($studentId <= 0 || $assetUuid === '') throw new \InvalidArgumentException('Invalid download scope');
|
||||
$ticket = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
|
||||
$key = 'asset_download:' . hash('sha256', $ticket);
|
||||
$saved = RedisClient::getInstance()->setex($key, self::TTL_SECONDS, json_encode([
|
||||
'student_id' => $studentId,
|
||||
'asset_uuid' => $assetUuid,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
if (!$saved) throw new \RuntimeException('Download grant unavailable');
|
||||
return ['ticket' => $ticket, 'expires_in_seconds' => self::TTL_SECONDS];
|
||||
}
|
||||
|
||||
public static function resolve(string $ticket, string $assetUuid): ?int
|
||||
{
|
||||
if (!preg_match('/^[A-Za-z0-9_-]{43}$/', $ticket)) return null;
|
||||
$payload = RedisClient::getInstance()->get('asset_download:' . hash('sha256', $ticket));
|
||||
if (!is_string($payload)) return null;
|
||||
$grant = json_decode($payload, true);
|
||||
if (!is_array($grant) || !hash_equals((string)($grant['asset_uuid'] ?? ''), $assetUuid)) return null;
|
||||
$studentId = (int)($grant['student_id'] ?? 0);
|
||||
return $studentId > 0 ? $studentId : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user