wip
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\GameAccount;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
class LauncherGameTicketService
|
||||
{
|
||||
private const string ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
private const int TICKET_LENGTH = 16;
|
||||
|
||||
public function __construct(private readonly AzerothCoreSrpService $srp) {}
|
||||
|
||||
/**
|
||||
* @return array{account: string, ticket: string, expires_at: string}
|
||||
*/
|
||||
public function issue(GameAccount $account, string $launcherSessionToken, int $clientBuild): array
|
||||
{
|
||||
$username = $this->srp->normalizeUsername((string) $account->username);
|
||||
$this->assertCompatibleAccountName($username);
|
||||
|
||||
$ticket = $this->randomTicket();
|
||||
[$saltHex, $verifierHex] = $this->srp->makeRegistrationData($username, $ticket);
|
||||
$generationId = random_bytes(16);
|
||||
$sessionHash = hash('sha256', $launcherSessionToken, true);
|
||||
$issuedAt = CarbonImmutable::now('UTC');
|
||||
$ttl = max(5, min(60, (int) config('moonwell.launcher.game_ticket_ttl', 60)));
|
||||
$expiresAt = $issuedAt->addSeconds($ttl);
|
||||
$connection = DB::connection((string) config('moonwell.auth_connection'));
|
||||
|
||||
$connection->transaction(function () use (
|
||||
$connection,
|
||||
$account,
|
||||
$generationId,
|
||||
$saltHex,
|
||||
$verifierHex,
|
||||
$clientBuild,
|
||||
$sessionHash,
|
||||
$issuedAt,
|
||||
$expiresAt,
|
||||
): void {
|
||||
$connection->table('launcher_ticket')->updateOrInsert(
|
||||
['account_id' => (int) $account->id],
|
||||
[
|
||||
'generation_id' => $generationId,
|
||||
'srp_salt' => hex2bin($saltHex),
|
||||
'srp_verifier' => hex2bin($verifierHex),
|
||||
'client_build' => $clientBuild,
|
||||
'launcher_session_hash' => $sessionHash,
|
||||
'issued_at' => $issuedAt,
|
||||
'expires_at' => $expiresAt,
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
return [
|
||||
'account' => $username,
|
||||
'ticket' => $ticket,
|
||||
'expires_at' => $expiresAt->format('Y-m-d\\TH:i:s\\Z'),
|
||||
];
|
||||
}
|
||||
|
||||
private function randomTicket(): string
|
||||
{
|
||||
$ticket = '';
|
||||
$lastIndex = strlen(self::ALPHABET) - 1;
|
||||
|
||||
for ($i = 0; $i < self::TICKET_LENGTH; $i++) {
|
||||
$ticket .= self::ALPHABET[random_int(0, $lastIndex)];
|
||||
}
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
private function assertCompatibleAccountName(string $username): void
|
||||
{
|
||||
if (
|
||||
$username === ''
|
||||
|| strlen($username) > 17
|
||||
|| preg_match('/^[!-<>-~]+$/D', $username) !== 1
|
||||
) {
|
||||
throw new RuntimeException('Game account is not compatible with the 3.3.5a authentication protocol.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user