![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/utils/ |
<?php
/**
* Audio Token System - Generates and validates expiring signed tokens for audio streaming
* This prevents direct URL sharing and unauthorized downloads
*/
// Secret key for signing tokens - should be unique per installation
// This is combined with the track ID and expiration to create a signature
define('AUDIO_TOKEN_SECRET', 'ssp_audio_2024_' . md5(__DIR__));
// Token expiration time in seconds (5 minutes default)
define('AUDIO_TOKEN_EXPIRY', 300);
// Token usage limit - very restrictive to prevent abuse
define('AUDIO_TOKEN_MAX_USES', 1); // Allow only 1 use (no refreshes allowed)
/**
* Generate a signed token for audio streaming
* NOW INCLUDES USER/SESSION BINDING - tokens cannot be shared
*
* @param int $trackId The track ID
* @param int|null $variationIndex Optional variation index
* @param int|null $expiresIn Seconds until expiration (default: AUDIO_TOKEN_EXPIRY)
* @param int|null $userId User ID to bind token to (required for security)
* @param string|null $sessionId Session ID to bind token to (required for security)
* @return array ['token' => string, 'expires' => int]
*/
function generateAudioToken($trackId, $variationIndex = null, $expiresIn = null, $userId = null, $sessionId = null) {
$expires = time() + ($expiresIn ?? AUDIO_TOKEN_EXPIRY);
// CRITICAL: Include user and session in token signature
// This prevents token sharing - tokens only work for the user/session who generated them
// For guests (no user_id), bind to session_id to prevent sharing
// Ensure session is started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Always use current session ID (don't trust passed parameter)
// This ensures consistency - token is always bound to the current session
$currentSessionId = session_id();
// Build user context: user_id|session_id
// For logged-in users: "123|abc123"
// For guests: "|abc123" (empty user_id but session_id present)
$userContext = ($userId ?? '') . '|' . $currentSessionId;
// Create signature from track ID, variation, expiration, user context, and secret
$data = $trackId . '|' . ($variationIndex ?? '') . '|' . $expires . '|' . $userContext;
$signature = hash_hmac('sha256', $data, AUDIO_TOKEN_SECRET);
// Return short token (first 16 chars of signature) and expiration
return [
'token' => substr($signature, 0, 16),
'expires' => $expires
];
}
/**
* Check if a token has exceeded max uses
*
* @param string $token The token to check
* @param int $trackId The track ID
* @return array ['used' => bool, 'use_count' => int, 'expired' => bool]
*/
function checkTokenUsage($token, $trackId) {
// Ensure session is started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Initialize token usage tracking in session if not exists
if (!isset($_SESSION['audio_token_usage'])) {
$_SESSION['audio_token_usage'] = [];
}
// Create unique key for this token+track combination
$tokenKey = $token . '|' . $trackId;
// Check if token has been used
if (isset($_SESSION['audio_token_usage'][$tokenKey])) {
$useCount = $_SESSION['audio_token_usage'][$tokenKey]['count'];
// Check if exceeded max uses
if ($useCount >= AUDIO_TOKEN_MAX_USES) {
return [
'used' => true,
'use_count' => $useCount,
'expired' => true
];
}
// Under max uses - allow but track
return [
'used' => false,
'use_count' => $useCount,
'expired' => false
];
}
// Token not used yet
return [
'used' => false,
'use_count' => 0,
'expired' => false
];
}
/**
* Mark a token as used (increment usage counter)
*
* @param string $token The token to mark
* @param int $trackId The track ID
*/
function markTokenUsed($token, $trackId) {
// Ensure session is started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Initialize token usage tracking in session if not exists
if (!isset($_SESSION['audio_token_usage'])) {
$_SESSION['audio_token_usage'] = [];
}
// Create unique key for this token+track combination
$tokenKey = $token . '|' . $trackId;
$now = time();
if (isset($_SESSION['audio_token_usage'][$tokenKey])) {
// Increment usage count
$_SESSION['audio_token_usage'][$tokenKey]['count']++;
$_SESSION['audio_token_usage'][$tokenKey]['last_use'] = $now;
} else {
// First use - record timestamp
$_SESSION['audio_token_usage'][$tokenKey] = [
'first_use' => $now,
'last_use' => $now,
'count' => 1
];
}
// Clean up old token usage data (older than token expiry)
$cleanupTime = $now - (AUDIO_TOKEN_EXPIRY + 60);
foreach ($_SESSION['audio_token_usage'] as $key => $usage) {
if ($usage['first_use'] < $cleanupTime) {
unset($_SESSION['audio_token_usage'][$key]);
}
}
}
/**
* Validate an audio token
* NOW VALIDATES USER/SESSION BINDING AND USAGE LIMIT - tokens cannot be shared or used unlimited times
*
* @param int $trackId The track ID
* @param int|null $variationIndex Optional variation index
* @param string $token The token to validate
* @param int $expires The expiration timestamp
* @param int|null $userId User ID to validate against (required)
* @param string|null $sessionId Session ID to validate against (required)
* @param bool $checkUsage Whether to check usage limit (default: true)
* @return bool True if valid, false otherwise
*/
function validateAudioToken($trackId, $variationIndex, $token, $expires, $userId = null, $sessionId = null, $checkUsage = true) {
// Check if expired
if (time() > $expires) {
return false;
}
// Ensure session is started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Always use current session ID (must match the session used when generating token)
$currentSessionId = session_id();
// CRITICAL: Validate user context matches (user_id + session_id)
// Tokens are bound to specific user/session - cannot be shared
// Build user context the same way as generation: user_id|session_id
$userContext = ($userId ?? '') . '|' . $currentSessionId;
// Recreate the signature with user context
$data = $trackId . '|' . ($variationIndex ?? '') . '|' . $expires . '|' . $userContext;
$expectedSignature = hash_hmac('sha256', $data, AUDIO_TOKEN_SECRET);
$expectedToken = substr($expectedSignature, 0, 16);
// Constant-time comparison to prevent timing attacks
if (!hash_equals($expectedToken, $token)) {
return false;
}
// Check usage limit if enabled
if ($checkUsage) {
$usage = checkTokenUsage($token, $trackId);
if ($usage['used'] && $usage['expired']) {
// Token has exceeded max uses
return false;
}
}
return true;
}
/**
* Generate a complete signed audio URL
*
* @param int $trackId The track ID
* @param int|null $variationIndex Optional variation index
* @param int|null $expiresIn Seconds until expiration (default: AUDIO_TOKEN_EXPIRY)
* @return string The signed URL
*/
function getSignedAudioUrl($trackId, $variationIndex = null, $expiresIn = null, $userId = null, $sessionId = null) {
$tokenData = generateAudioToken($trackId, $variationIndex, $expiresIn, $userId, $sessionId);
$url = '/utils/play_audio.php?id=' . $trackId;
if ($variationIndex !== null) {
$url .= '&variation=' . $variationIndex;
}
$url .= '&token=' . $tokenData['token'];
$url .= '&expires=' . $tokenData['expires'];
return $url;
}
/**
* Generate signed audio URL for JavaScript (returns JSON-encodable data)
*
* @param int $trackId The track ID
* @param int|null $variationIndex Optional variation index
* @return array URL components for JavaScript
*/
function getSignedAudioUrlComponents($trackId, $variationIndex = null, $userId = null, $sessionId = null) {
$tokenData = generateAudioToken($trackId, $variationIndex, null, $userId, $sessionId);
return [
'baseUrl' => '/utils/play_audio.php',
'trackId' => $trackId,
'variationIndex' => $variationIndex,
'token' => $tokenData['token'],
'expires' => $tokenData['expires']
];
}