2026-02-24 21:02:41 +03:00
<? php
/**
2026-03-07 21:53:45 +03:00
* Консолидированный сервис аутентификации.
2026-02-24 21:02:41 +03:00
* Объединяет: CodeService, HMACService, HMACValidator.
2026-04-05 22:38:36 +03:00
*
* @package XC_VM_Domain_Auth
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
2026-02-24 21:02:41 +03:00
*/
2026-04-05 22:38:36 +03:00
2026-02-24 21:02:41 +03:00
class AuthService {
// ──────────────────────────────────────────────
// Из CodeService
// ──────────────────────────────────────────────
2026-06-22 11:03:32 +03:00
/**
* Create or update an access code from admin form data.
*
* Validates code length, reserved names and uniqueness, normalizes the
* group/whitelist fields, then upserts the row.
*
* @param array $rData Submitted form data (includes `edit` id when updating).
* @return array ['status' => STATUS_* constant, 'data' => payload].
*/
2026-03-07 21:53:45 +03:00
public static function processCode ( $rData ) {
2026-02-27 22:32:39 +03:00
global $db ;
2026-02-24 21:02:41 +03:00
if ( isset ( $rData [ 'edit' ])) {
2026-04-09 21:24:48 +03:00
$rArray = AdminHelpers :: overwriteData ( AuthRepository :: getCodeById ( $rData [ 'edit' ]), $rData );
2026-02-24 21:02:41 +03:00
$rOrigCode = $rArray [ 'code' ];
} else {
2026-04-09 21:24:48 +03:00
$rArray = QueryHelper :: verifyPostTable ( 'access_codes' , $rData );
2026-02-24 21:02:41 +03:00
$rOrigCode = null ;
unset ( $rArray [ 'id' ]);
}
if ( isset ( $rData [ 'enabled' ])) {
$rArray [ 'enabled' ] = 1 ;
} else {
$rArray [ 'enabled' ] = 0 ;
}
if ( isset ( $rData [ 'groups' ])) {
$rArray [ 'groups' ] = array ();
foreach ( $rData [ 'groups' ] as $rGroupID ) {
$rArray [ 'groups' ][] = intval ( $rGroupID );
}
2026-04-16 22:35:07 +03:00
} elseif ( ! is_array ( $rArray [ 'groups' ] ?? null )) {
$rArray [ 'groups' ] = is_string ( $rArray [ 'groups' ] ?? null ) ? ( json_decode ( $rArray [ 'groups' ], true ) ?: []) : [];
2026-02-24 21:02:41 +03:00
}
if ( in_array ( $rData [ 'type' ], array ( 0 , 1 , 3 , 4 ))) {
$rArray [ 'groups' ] = '[' . implode ( ',' , array_map ( 'intval' , $rArray [ 'groups' ])) . ']' ;
} else {
$rArray [ 'groups' ] = '[]' ;
}
if ( ! isset ( $rData [ 'whitelist' ])) {
$rArray [ 'whitelist' ] = '[]' ;
}
if ( $rData [ 'type' ] != 2 && strlen ( $rData [ 'code' ]) < 8 ) {
return array ( 'status' => STATUS_CODE_LENGTH , 'data' => $rData );
}
if ( $rData [ 'type' ] == 2 && empty ( $rData [ 'code' ])) {
return array ( 'status' => STATUS_INVALID_CODE , 'data' => $rData );
}
if ( in_array ( $rData [ 'code' ], array ( 'admin' , 'stream' , 'images' , 'player_api' , 'player' , 'playlist' , 'epg' , 'live' , 'movie' , 'series' , 'status' , 'nginx_status' , 'get' , 'panel_api' , 'xmltv' , 'probe' , 'thumb' , 'timeshift' , 'auth' , 'vauth' , 'tsauth' , 'hls' , 'play' , 'key' , 'api' , 'c' ))) {
return array ( 'status' => STATUS_RESERVED_CODE , 'data' => $rData );
}
if ( isset ( $rData [ 'edit' ])) {
$db -> query ( 'SELECT `id` FROM `access_codes` WHERE `code` = ? AND `id` <> ?;' , $rData [ 'code' ], $rData [ 'edit' ]);
} else {
$db -> query ( 'SELECT `id` FROM `access_codes` WHERE `code` = ?;' , $rData [ 'code' ]);
}
if ( 0 < $db -> num_rows ()) {
return array ( 'status' => STATUS_EXISTS_CODE , 'data' => $rData );
}
2026-04-09 21:24:48 +03:00
$rPrepare = QueryHelper :: prepareArray ( $rArray );
2026-02-24 21:02:41 +03:00
$rQuery = 'REPLACE INTO `access_codes`(' . $rPrepare [ 'columns' ] . ') VALUES(' . $rPrepare [ 'placeholder' ] . ');' ;
if ( $db -> query ( $rQuery , ... $rPrepare [ 'data' ])) {
$rInsertID = $db -> last_insert_id ();
2026-03-07 21:53:45 +03:00
AuthRepository :: updateCodes ();
2026-02-24 21:02:41 +03:00
return array ( 'status' => STATUS_SUCCESS , 'data' => array ( 'insert_id' => $rInsertID , 'orig_code' => $rOrigCode , 'new_code' => $rData [ 'code' ]));
}
return array ( 'status' => STATUS_FAILURE , 'data' => $rData );
}
// ──────────────────────────────────────────────
// Из HMACService
// ──────────────────────────────────────────────
2026-06-22 11:03:32 +03:00
/**
* Create or update an HMAC key from admin form data.
*
* Validates the 32-char key and description, enforces uniqueness, and stores
* the key encrypted with the live-streaming password.
*
* @param array $rData Submitted form data (includes `edit` id when updating).
* @return array ['status' => STATUS_* constant, 'data' => payload or insert_id].
*/
2026-03-07 21:53:45 +03:00
public static function processHMAC ( $rData ) {
2026-03-08 16:19:30 +03:00
global $db , $rSettings ;
2026-02-24 21:02:41 +03:00
if ( isset ( $rData [ 'edit' ])) {
2026-04-09 21:24:48 +03:00
$rArray = AdminHelpers :: overwriteData ( AuthRepository :: getHMACById ( $rData [ 'edit' ]), $rData );
2026-02-24 21:02:41 +03:00
} else {
2026-04-09 21:24:48 +03:00
$rArray = QueryHelper :: verifyPostTable ( 'hmac_keys' , $rData );
2026-02-24 21:02:41 +03:00
unset ( $rArray [ 'id' ]);
}
if ( isset ( $rData [ 'enabled' ])) {
$rArray [ 'enabled' ] = 1 ;
} else {
$rArray [ 'enabled' ] = 0 ;
}
if ( $rData [ 'keygen' ] != 'HMAC KEY HIDDEN' && strlen ( $rData [ 'keygen' ]) != 32 ) {
return array ( 'status' => STATUS_NO_KEY , 'data' => $rData );
}
if ( strlen ( $rData [ 'notes' ]) == 0 ) {
return array ( 'status' => STATUS_NO_DESCRIPTION , 'data' => $rData );
}
if ( isset ( $rData [ 'edit' ])) {
if ( $rData [ 'keygen' ] != 'HMAC KEY HIDDEN' ) {
2026-03-07 21:53:45 +03:00
$db -> query ( 'SELECT `id` FROM `hmac_keys` WHERE `key` = ? AND `id` <> ?;' , Encryption :: encrypt ( $rData [ 'keygen' ], $rSettings [ 'live_streaming_pass' ], OPENSSL_EXTRA ), $rData [ 'edit' ]);
2026-02-24 21:02:41 +03:00
if ( 0 < $db -> num_rows ()) {
return array ( 'status' => STATUS_EXISTS_HMAC , 'data' => $rData );
}
}
} else {
2026-03-07 21:53:45 +03:00
$db -> query ( 'SELECT `id` FROM `hmac_keys` WHERE `key` = ?;' , Encryption :: encrypt ( $rData [ 'keygen' ], $rSettings [ 'live_streaming_pass' ], OPENSSL_EXTRA ));
2026-02-24 21:02:41 +03:00
if ( 0 < $db -> num_rows ()) {
return array ( 'status' => STATUS_EXISTS_HMAC , 'data' => $rData );
}
}
if ( $rData [ 'keygen' ] != 'HMAC KEY HIDDEN' ) {
2026-03-07 21:53:45 +03:00
$rArray [ 'key' ] = Encryption :: encrypt ( $rData [ 'keygen' ], $rSettings [ 'live_streaming_pass' ], OPENSSL_EXTRA );
2026-02-24 21:02:41 +03:00
}
2026-04-09 21:24:48 +03:00
$rPrepare = QueryHelper :: prepareArray ( $rArray );
2026-02-24 21:02:41 +03:00
$rQuery = 'REPLACE INTO `hmac_keys`(' . $rPrepare [ 'columns' ] . ') VALUES(' . $rPrepare [ 'placeholder' ] . ');' ;
if ( $db -> query ( $rQuery , ... $rPrepare [ 'data' ])) {
$rInsertID = $db -> last_insert_id ();
return array ( 'status' => STATUS_SUCCESS , 'data' => array ( 'insert_id' => $rInsertID ));
}
return array ( 'status' => STATUS_FAILURE , 'data' => $rData );
}
// ──────────────────────────────────────────────
// Из HMACValidator
// ──────────────────────────────────────────────
2026-06-22 11:03:32 +03:00
/**
* Validate a streaming HMAC token against all enabled keys.
*
* Recomputes the SHA-256 HMAC over the stream parameters for each enabled key
* (from cache or DB) and returns the id of the first matching key.
*
* @param string $rHMAC Token supplied by the client.
* @param int|string $rExpiry Token expiry component.
* @param int|string $rStreamID Stream id component.
* @param string $rExtension Stream extension component.
* @param string $rIP Request IP (must match $rMACIP when both set).
* @param string $rMACIP Bound MAC/IP component.
* @param string $rIdentifier Optional identifier component.
* @param int $rMaxConnections Max-connections component.
* @return int|null Matching HMAC key id, or null if no key matches.
*/
2026-03-07 21:53:45 +03:00
public static function validateHMAC ( $rHMAC , $rExpiry , $rStreamID , $rExtension , $rIP = '' , $rMACIP = '' , $rIdentifier = '' , $rMaxConnections = 0 ) {
2026-03-08 16:19:30 +03:00
global $db , $rSettings ;
$rCached = $rSettings [ 'enable_cache' ];
2026-02-24 21:02:41 +03:00
if ( 0 < strlen ( $rIP ) && 0 < strlen ( $rMACIP ) && $rIP != $rMACIP ) {
return null ;
}
$rKeyID = null ;
if ( $rCached ) {
$rKeys = igbinary_unserialize ( file_get_contents ( CACHE_TMP_PATH . 'hmac_keys' ));
} else {
$rKeys = array ();
$db -> query ( 'SELECT `id`, `key` FROM `hmac_keys` WHERE `enabled` = 1;' );
foreach ( $db -> get_rows () as $rKey ) {
$rKeys [] = $rKey ;
}
}
foreach ( $rKeys as $rKey ) {
2026-03-03 20:00:34 +03:00
$rSecret = Encryption :: decrypt ( $rKey [ 'key' ], $rSettings [ 'live_streaming_pass' ], OPENSSL_EXTRA );
2026-02-24 21:02:41 +03:00
$rResult = hash_hmac ( 'sha256' , ( string ) $rStreamID . '##' . $rExtension . '##' . $rExpiry . '##' . $rMACIP . '##' . $rIdentifier . '##' . $rMaxConnections , $rSecret );
if ( md5 ( $rResult ) == md5 ( $rHMAC )) {
$rKeyID = $rKey [ 'id' ];
break ;
}
}
return $rKeyID ;
}
}