420 lines
24 KiB
PHP
420 lines
24 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Exceptions\DuplicateGameAccountException;
|
|
use App\Models\User;
|
|
use App\Services\AzerothCoreSrpService;
|
|
use App\Services\VMangosAccountRegistrar;
|
|
use App\Services\VMangosAccountService;
|
|
use App\Services\VMangosSrpService;
|
|
use App\Services\WebAccountPasswordService;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Tests\TestCase;
|
|
|
|
class VMangosAccountIntegrationTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private array $dumpFiles = [];
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->dumpFiles as $path) {
|
|
unlink($path);
|
|
}
|
|
parent::tearDown();
|
|
}
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'moonwell.auth_connection' => 'vmangos_auth',
|
|
'moonwell.characters_connection' => 'vmangos_characters',
|
|
'moonwell.registration.realm_id' => -1,
|
|
'moonwell.registration.gmlevel' => 0,
|
|
'database.connections.vmangos_auth' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
|
|
'database.connections.vmangos_characters' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
|
|
]);
|
|
DB::purge('vmangos_auth');
|
|
DB::purge('vmangos_characters');
|
|
|
|
Schema::connection('vmangos_auth')->create('account', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('username', 32)->unique();
|
|
$table->text('s');
|
|
$table->text('v');
|
|
$table->unsignedTinyInteger('expansion')->default(0);
|
|
$table->string('email')->nullable();
|
|
$table->dateTime('joindate');
|
|
$table->dateTime('last_login')->nullable();
|
|
$table->boolean('locked')->default(false);
|
|
$table->string('last_ip')->default('0.0.0.0');
|
|
$table->boolean('online')->default(false);
|
|
$table->string('sessionkey')->nullable();
|
|
});
|
|
Schema::connection('vmangos_auth')->create('realmlist', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
});
|
|
Schema::connection('vmangos_auth')->create('realmcharacters', function (Blueprint $table): void {
|
|
$table->unsignedInteger('realmid');
|
|
$table->unsignedInteger('acctid');
|
|
$table->unsignedTinyInteger('numchars');
|
|
$table->primary(['realmid', 'acctid']);
|
|
});
|
|
Schema::connection('vmangos_auth')->create('account_access', function (Blueprint $table): void {
|
|
$table->unsignedInteger('id');
|
|
$table->integer('RealmID');
|
|
$table->unsignedTinyInteger('gmlevel');
|
|
$table->primary(['id', 'RealmID']);
|
|
});
|
|
Schema::connection('vmangos_auth')->create('site_credentials', function (Blueprint $table): void {
|
|
$table->unsignedInteger('account_id')->primary();
|
|
$table->unsignedInteger('legacy_account_id')->nullable()->unique();
|
|
$table->unsignedBigInteger('web_user_id')->nullable()->unique();
|
|
$table->char('salt_hex', 64)->nullable();
|
|
$table->char('verifier_hex', 64)->nullable();
|
|
$table->timestamps();
|
|
});
|
|
Schema::connection('vmangos_auth')->create('launcher_ticket', function (Blueprint $table): void {
|
|
$table->unsignedInteger('account_id')->primary();
|
|
});
|
|
Schema::connection('vmangos_characters')->create('characters', function (Blueprint $table): void {
|
|
$table->unsignedInteger('guid')->primary();
|
|
$table->unsignedInteger('account');
|
|
$table->string('name');
|
|
$table->unsignedTinyInteger('race');
|
|
$table->unsignedTinyInteger('class');
|
|
$table->unsignedTinyInteger('gender');
|
|
$table->unsignedTinyInteger('level');
|
|
$table->boolean('online');
|
|
$table->unsignedInteger('money');
|
|
$table->unsignedInteger('played_time_total');
|
|
});
|
|
}
|
|
|
|
public function test_srp_matches_vmangos_core_control_vector(): void
|
|
{
|
|
$srp = app(VMangosSrpService::class);
|
|
$salt = 'D04E6342FE6CB5FAE54C6182F885778D0AEFE4BFCDDC6B5C7DF7DC25FF6E3C2D';
|
|
$verifier = '46FD48476D925C4422DD1761C299C79FD6C92B5243E4F3C6C62576C0DABD8260';
|
|
|
|
$this->assertSame($verifier, $srp->makeVerifierHex('VMANGOS', '4EVER', $salt));
|
|
$this->assertTrue($srp->credentialsMatch('vmangos', '4ever', $salt, $verifier));
|
|
$this->assertFalse($srp->credentialsMatch('vmangos', 'wrong', $salt, $verifier));
|
|
}
|
|
|
|
public function test_registration_login_password_access_and_characters_use_vmangos_schema(): void
|
|
{
|
|
DB::connection('vmangos_auth')->table('realmlist')->insert([['id' => 1], ['id' => 2]]);
|
|
$registered = app(VMangosAccountRegistrar::class)->register('PlayerOne', 'player@example.com', 'Pass1234');
|
|
$id = $registered['id'];
|
|
$this->assertSame('PLAYERONE', $registered['username']);
|
|
$webUser = User::where('name', 'PLAYERONE')->firstOrFail();
|
|
$this->assertTrue(Hash::check('Pass1234', $webUser->password));
|
|
$this->assertDatabaseHas('site_credentials', ['account_id' => $id, 'web_user_id' => $webUser->id, 'salt_hex' => null, 'verifier_hex' => null], 'vmangos_auth');
|
|
$this->assertDatabaseHas('account', ['id' => $id, 'username' => 'PLAYERONE', 'expansion' => 0], 'vmangos_auth');
|
|
$this->assertSame(2, DB::connection('vmangos_auth')->table('realmcharacters')->where('acctid', $id)->count());
|
|
$this->assertDatabaseHas('account_access', ['id' => $id, 'RealmID' => -1, 'gmlevel' => 0], 'vmangos_auth');
|
|
$gameCredential = DB::connection('vmangos_auth')->table('account')->where('id', $id)->first();
|
|
$this->assertFalse(app(VMangosSrpService::class)->credentialsMatch('PLAYERONE', 'Pass1234', $gameCredential->s, $gameCredential->v));
|
|
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserByUsername('playerone');
|
|
$this->assertNotNull($user);
|
|
$this->assertTrue($accounts->validatePassword($user, 'Pass1234'));
|
|
$this->assertFalse($accounts->validatePassword($user, 'Wrong1234'));
|
|
$this->assertFalse($accounts->validatePassword($user, 'pass1234'));
|
|
$this->assertSame($webUser->id, $user->webUserId);
|
|
|
|
DB::connection('vmangos_auth')->table('account')->where('id', $id)->update(['last_login' => '0000-00-00 00:00:00']);
|
|
$this->assertSame('—', $accounts->findUserById($id)->lastLoginLabel());
|
|
|
|
$accounts->updateAccessLevel($id, 3);
|
|
$this->assertSame(3, $accounts->findUserById($id)->gmLevel);
|
|
|
|
DB::connection('vmangos_characters')->table('characters')->insert([
|
|
'guid' => 11, 'account' => $id, 'name' => 'Hero', 'race' => 1, 'class' => 1,
|
|
'gender' => 0, 'level' => 60, 'online' => 1, 'money' => 12345, 'played_time_total' => 7200,
|
|
]);
|
|
$character = $accounts->charactersForAccount($id)->first();
|
|
$this->assertSame('Hero', $character['name']);
|
|
$this->assertSame('2ч 0м', $character['played']);
|
|
|
|
$accounts->updatePassword($user, 'NewPass123');
|
|
$fresh = $accounts->findUserById($id);
|
|
$this->assertFalse($accounts->validatePassword($fresh, 'Pass1234'));
|
|
$this->assertTrue($accounts->validatePassword($fresh, 'NewPass123'));
|
|
$this->assertTrue(Hash::check('NewPass123', $webUser->fresh()->password));
|
|
$this->assertSame($gameCredential->v, DB::connection('vmangos_auth')->table('account')->where('id', $id)->value('v'));
|
|
}
|
|
|
|
public function test_import_reads_web_users_and_preserves_web_hashes_and_repeated_game_credentials(): void
|
|
{
|
|
DB::connection('vmangos_auth')->table('realmlist')->insert(['id' => 1]);
|
|
$webUser = User::create(['name' => 'Existing', 'email' => 'existing@example.com', 'password' => 'ExistingWebPassword123']);
|
|
$hash = $webUser->password;
|
|
$this->artisan('vmangos:import-web-accounts', ['--dry-run' => true])
|
|
->expectsOutput('Created: 1; linked: 0; already imported: 0.')->assertSuccessful();
|
|
$this->assertSame(0, DB::connection('vmangos_auth')->table('account')->count());
|
|
$this->artisan('vmangos:import-web-accounts')->assertSuccessful();
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserByUsername('existing');
|
|
$this->assertSame($webUser->id, $user->webUserId);
|
|
$this->assertSame($hash, $webUser->fresh()->password);
|
|
$this->assertTrue($accounts->validatePassword($user, 'ExistingWebPassword123'));
|
|
$game = DB::connection('vmangos_auth')->table('account')->where('id', $user->id)->first();
|
|
$this->assertFalse(app(VMangosSrpService::class)->credentialsMatch('EXISTING', 'ExistingWebPassword123', $game->s, $game->v));
|
|
$this->assertDatabaseHas('account_access', ['id' => $user->id, 'RealmID' => -1, 'gmlevel' => 0], 'vmangos_auth');
|
|
$this->assertDatabaseHas('realmcharacters', ['acctid' => $user->id, 'realmid' => 1], 'vmangos_auth');
|
|
$this->artisan('vmangos:import-web-accounts')
|
|
->expectsOutput('Created: 0; linked: 0; already imported: 1.')->assertSuccessful();
|
|
$this->assertSame($game->v, DB::connection('vmangos_auth')->table('account')->where('id', $user->id)->value('v'));
|
|
$this->post('/login', ['username' => 'existing', 'password' => 'ExistingWebPassword123'])->assertRedirect();
|
|
$this->assertAuthenticated();
|
|
}
|
|
|
|
public function test_linking_existing_account_keeps_privileges_and_revokes_sessions(): void
|
|
{
|
|
$target = DB::connection('vmangos_auth');
|
|
$id = $target->table('account')->insertGetId([
|
|
'username' => 'ADMIN', 'email' => 'admin@example.com', 's' => 'old', 'v' => 'old',
|
|
'sessionkey' => 'old-session', 'joindate' => '2025-01-01 00:00:00',
|
|
]);
|
|
$target->table('account_access')->insert(['id' => $id, 'RealmID' => -1, 'gmlevel' => 3]);
|
|
$target->table('launcher_ticket')->insert(['account_id' => $id]);
|
|
[$salt, $verifier] = app(AzerothCoreSrpService::class)->makeRegistrationData('ADMIN', 'OldPass123');
|
|
$target->table('site_credentials')->insert([
|
|
'account_id' => $id, 'legacy_account_id' => 8, 'salt_hex' => $salt, 'verifier_hex' => $verifier,
|
|
]);
|
|
$webUser = User::create(['name' => 'Admin', 'email' => 'admin@example.com', 'password' => 'WebPass123']);
|
|
DB::table('oauth_access_tokens')->insert([
|
|
'id' => 'old-token', 'user_id' => 99, 'client_id' => 'old-client', 'scopes' => '[]', 'revoked' => false,
|
|
]);
|
|
DB::table('oauth_refresh_tokens')->insert(['id' => 'old-refresh', 'access_token_id' => 'old-token', 'revoked' => false]);
|
|
$this->artisan('vmangos:import-web-accounts', ['--dry-run' => true])->assertSuccessful();
|
|
$this->assertDatabaseHas('oauth_access_tokens', ['id' => 'old-token', 'revoked' => false]);
|
|
$this->artisan('vmangos:import-web-accounts')
|
|
->expectsOutput('Created: 0; linked: 1; already imported: 0.')->assertSuccessful();
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserByUsername('admin');
|
|
$this->assertSame($id, $user->id);
|
|
$this->assertSame($webUser->id, $user->webUserId);
|
|
$this->assertSame(3, $user->gmLevel);
|
|
$this->assertTrue($accounts->validatePassword($user, 'WebPass123'));
|
|
$this->assertFalse($accounts->validatePassword($user, 'OldPass123'));
|
|
$this->assertDatabaseHas('site_credentials', ['account_id' => $id, 'salt_hex' => null, 'verifier_hex' => null], 'vmangos_auth');
|
|
$this->assertNull($target->table('account')->where('id', $id)->value('sessionkey'));
|
|
$this->assertSame(0, $target->table('launcher_ticket')->count());
|
|
$this->assertDatabaseHas('oauth_access_tokens', ['id' => 'old-token', 'revoked' => true]);
|
|
$this->assertDatabaseHas('oauth_refresh_tokens', ['id' => 'old-refresh', 'revoked' => true]);
|
|
}
|
|
|
|
public function test_import_conflict_does_not_create_other_users(): void
|
|
{
|
|
User::create(['name' => 'FirstUser', 'email' => 'first@example.com', 'password' => 'WebPass123']);
|
|
User::create(['name' => 'SecondUser', 'email' => 'second@example.com', 'password' => 'WebPass123']);
|
|
DB::connection('vmangos_auth')->table('account')->insert([
|
|
'username' => 'SECONDUSER', 'email' => 'someone-else@example.com',
|
|
's' => 'old', 'v' => 'old', 'joindate' => '2025-01-01 00:00:00',
|
|
]);
|
|
$this->artisan('vmangos:import-web-accounts')->assertFailed();
|
|
$this->assertSame(1, DB::connection('vmangos_auth')->table('account')->count());
|
|
$this->assertSame(0, DB::connection('vmangos_auth')->table('site_credentials')->count());
|
|
}
|
|
|
|
public function test_import_rejects_duplicate_normalized_web_names(): void
|
|
{
|
|
User::create(['name' => 'Duplicate', 'email' => 'first@example.com', 'password' => 'WebPass123']);
|
|
User::create(['name' => 'duplicate', 'email' => 'second@example.com', 'password' => 'WebPass123']);
|
|
$this->artisan('vmangos:import-web-accounts')->assertFailed();
|
|
$this->assertSame(0, DB::connection('vmangos_auth')->table('account')->count());
|
|
}
|
|
|
|
public function test_missing_web_user_cannot_use_legacy_password_fallback(): void
|
|
{
|
|
$result = app(VMangosAccountRegistrar::class)->register('PlayerOne', 'player@example.com', 'Pass1234');
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserById($result['id']);
|
|
User::findOrFail($user->webUserId)->delete();
|
|
$this->assertFalse($accounts->validatePassword($user, 'Pass1234'));
|
|
}
|
|
|
|
public function test_registration_duplicate_web_email_does_not_leave_game_account(): void
|
|
{
|
|
User::create(['name' => 'Existing', 'email' => 'player@example.com', 'password' => 'Pass1234']);
|
|
try {
|
|
app(VMangosAccountRegistrar::class)->register('PlayerOne', 'player@example.com', 'Pass1234');
|
|
$this->fail('Expected duplicate email to be rejected.');
|
|
} catch (DuplicateGameAccountException $exception) {
|
|
$this->assertSame(0, DB::connection('vmangos_auth')->table('account')->count());
|
|
$this->assertSame(1, User::count());
|
|
}
|
|
}
|
|
|
|
public function test_auth_dump_dry_run_then_import_and_first_login_upgrade_password(): void
|
|
{
|
|
config(['moonwell.portal.bot_account_prefix' => 'RNDBOT']);
|
|
$path = $this->authDump([
|
|
['id' => 42, 'username' => 'SINDORING', 'gmlevel' => 3],
|
|
['id' => 99, 'username' => 'RNDBOT99'],
|
|
]);
|
|
$auth = DB::connection('vmangos_auth');
|
|
$auth->table('realmlist')->insert(['id' => 1]);
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $path, '--dry-run' => true])
|
|
->expectsOutput('Created: 1; linked: 0; already imported: 0; bots skipped: 1.')->assertSuccessful();
|
|
$this->assertSame(0, User::count());
|
|
$this->assertSame(0, $auth->table('account')->count());
|
|
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $path])->assertSuccessful();
|
|
$webUser = User::where('name', 'SINDORING')->firstOrFail();
|
|
$encoded = $webUser->password;
|
|
$this->assertStringStartsWith('acsrp1:SINDORING:', $encoded);
|
|
$game = $auth->table('account')->where('username', 'SINDORING')->first();
|
|
$this->assertNotSame(42, $game->id);
|
|
$this->assertDatabaseHas('site_credentials', [
|
|
'account_id' => $game->id, 'legacy_account_id' => 42, 'web_user_id' => $webUser->id,
|
|
'salt_hex' => null, 'verifier_hex' => null,
|
|
], 'vmangos_auth');
|
|
$this->assertDatabaseHas('account_access', ['id' => $game->id, 'RealmID' => -1, 'gmlevel' => 3], 'vmangos_auth');
|
|
$this->assertDatabaseHas('realmcharacters', ['acctid' => $game->id, 'realmid' => 1], 'vmangos_auth');
|
|
$this->assertSame('2025-01-02 03:04:05', $webUser->created_at->format('Y-m-d H:i:s'));
|
|
$this->assertFalse(app(VMangosSrpService::class)->credentialsMatch('SINDORING', 'OldPass123', $game->s, $game->v));
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserByUsername('sindoring');
|
|
$this->assertFalse($accounts->validatePassword($user, 'WrongPass'));
|
|
$this->assertSame($encoded, $webUser->fresh()->password);
|
|
|
|
$this->post('/login', ['username' => 'sindoring', 'password' => 'OldPass123'])->assertRedirect();
|
|
$this->assertAuthenticated();
|
|
$this->assertTrue(Hash::check('OldPass123', $webUser->fresh()->password));
|
|
$this->assertSame($game->v, $auth->table('account')->where('id', $game->id)->value('v'));
|
|
$accounts->updatePassword($user, 'NewPass123');
|
|
$hash = $webUser->fresh()->password;
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $path])
|
|
->expectsOutput('Created: 0; linked: 0; already imported: 1; bots skipped: 1.')->assertSuccessful();
|
|
$this->assertSame($hash, $webUser->fresh()->password);
|
|
$this->assertSame($game->v, $auth->table('account')->where('id', $game->id)->value('v'));
|
|
$this->assertTrue($accounts->validatePassword($user, 'NewPass123'));
|
|
$this->assertFalse($accounts->validatePassword($user, 'OldPass123'));
|
|
}
|
|
|
|
public function test_auth_dump_links_legacy_game_account_and_revokes_old_tokens(): void
|
|
{
|
|
$auth = DB::connection('vmangos_auth');
|
|
$gameId = $auth->table('account')->insertGetId([
|
|
'username' => 'SINDORING', 'email' => 'sindoring@example.com',
|
|
's' => 'previous-salt', 'v' => 'previous-verifier', 'sessionkey' => 'old-session',
|
|
'joindate' => '2025-01-01 00:00:00',
|
|
]);
|
|
$auth->table('site_credentials')->insert([
|
|
'account_id' => $gameId, 'legacy_account_id' => 42, 'salt_hex' => str_repeat('A', 64),
|
|
'verifier_hex' => str_repeat('B', 64),
|
|
]);
|
|
$auth->table('account_access')->insert(['id' => $gameId, 'RealmID' => -1, 'gmlevel' => 2]);
|
|
$auth->table('launcher_ticket')->insert(['account_id' => $gameId]);
|
|
DB::table('oauth_access_tokens')->insert([
|
|
'id' => 'legacy-token', 'user_id' => 42, 'client_id' => 'legacy-client', 'scopes' => '[]', 'revoked' => false,
|
|
]);
|
|
DB::table('oauth_refresh_tokens')->insert(['id' => 'legacy-refresh', 'access_token_id' => 'legacy-token', 'revoked' => false]);
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $this->authDump([['id' => 42, 'username' => 'SINDORING', 'gmlevel' => 3]])])
|
|
->expectsOutput('Created: 0; linked: 1; already imported: 0; bots skipped: 0.')->assertSuccessful();
|
|
$this->assertSame(1, $auth->table('account')->count());
|
|
$this->assertSame(0, $auth->table('launcher_ticket')->count());
|
|
$this->assertNull($auth->table('account')->value('sessionkey'));
|
|
$this->assertNotSame('previous-verifier', $auth->table('account')->value('v'));
|
|
$this->assertDatabaseHas('oauth_access_tokens', ['id' => 'legacy-token', 'revoked' => true]);
|
|
$this->assertDatabaseHas('oauth_refresh_tokens', ['id' => 'legacy-refresh', 'revoked' => true]);
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserByUsername('sindoring');
|
|
$this->assertSame(2, $user->gmLevel);
|
|
$this->assertTrue($accounts->validatePassword($user, 'oldpass123'));
|
|
$this->assertTrue(Hash::check('oldpass123', User::findOrFail($user->webUserId)->password));
|
|
}
|
|
|
|
public function test_auth_dump_restores_users_with_shared_or_missing_email(): void
|
|
{
|
|
$path = $this->authDump([
|
|
['id' => 1, 'username' => 'FIRST', 'email' => 'shared@example.com', 'locked' => 1],
|
|
['id' => 2, 'username' => 'SECOND', 'email' => 'shared@example.com'],
|
|
['id' => 3, 'username' => 'THIRD', 'email' => ''],
|
|
]);
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $path])->assertSuccessful();
|
|
$this->assertSame(3, User::whereNull('email')->count());
|
|
$this->assertDatabaseHas('account', ['username' => 'FIRST', 'email' => 'shared@example.com', 'locked' => true, 'last_ip' => '127.0.0.1'], 'vmangos_auth');
|
|
$this->assertDatabaseHas('account', ['username' => 'SECOND', 'email' => 'shared@example.com'], 'vmangos_auth');
|
|
$accounts = app(VMangosAccountService::class);
|
|
$this->assertTrue($accounts->validatePassword($accounts->findUserByUsername('third'), 'OldPass123'));
|
|
}
|
|
|
|
public function test_auth_dump_conflict_preflight_does_not_partially_import(): void
|
|
{
|
|
User::create(['name' => 'Second', 'email' => 'second@example.com', 'password' => 'CurrentPass123']);
|
|
$path = $this->authDump([['id' => 1, 'username' => 'FIRST'], ['id' => 2, 'username' => 'SECOND']]);
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $path])
|
|
->expectsOutput('An unlinked web user already has this name for legacy ID 2; resolve the mapping first.')->assertFailed();
|
|
$this->assertSame(1, User::count());
|
|
$this->assertSame(0, DB::connection('vmangos_auth')->table('account')->count());
|
|
}
|
|
|
|
public function test_auth_dump_is_fully_parsed_before_any_writes(): void
|
|
{
|
|
$path = $this->authDump([['id' => 42, 'username' => 'SINDORING']]);
|
|
file_put_contents($path, gzencode(gzdecode(file_get_contents($path)).'INSERT INTO `account` VALUES ('));
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $path])->assertFailed();
|
|
$this->assertSame(0, User::count());
|
|
$this->assertSame(0, DB::connection('vmangos_auth')->table('account')->count());
|
|
}
|
|
|
|
public function test_stale_legacy_password_cannot_overwrite_password_reset(): void
|
|
{
|
|
$this->artisan('vmangos:import-azerothcore-accounts', ['dump' => $this->authDump([['id' => 42, 'username' => 'SINDORING']])])->assertSuccessful();
|
|
$stale = User::firstOrFail();
|
|
User::findOrFail($stale->id)->update(['password' => 'ResetPass123']);
|
|
$this->assertFalse(app(WebAccountPasswordService::class)->matches($stale, 'OldPass123'));
|
|
$this->assertTrue(Hash::check('ResetPass123', $stale->fresh()->password));
|
|
}
|
|
|
|
private function authDump(array $accounts): string
|
|
{
|
|
$sql = "USE `acore_auth`;\nDROP TABLE IF EXISTS `users`;\n";
|
|
foreach ($accounts as $account) {
|
|
$id = $account['id'];
|
|
$username = $account['username'];
|
|
$email = $account['email'] ?? strtolower($username).'@example.com';
|
|
[$salt, $verifier] = app(AzerothCoreSrpService::class)->makeRegistrationData($username, 'OldPass123');
|
|
$locked = $account['locked'] ?? 0;
|
|
$level = $account['gmlevel'] ?? 0;
|
|
$sql .= "INSERT INTO `account` (`id`,`username`,`email`,`salt`,`verifier`,`joindate`,`locked`,`last_ip`) VALUES ($id,'$username','$email',0x$salt,0x$verifier,'2025-01-02 03:04:05',$locked,'127.0.0.1');\n";
|
|
$sql .= "INSERT INTO `account_access` (`id`,`gmlevel`,`RealmID`) VALUES ($id,$level,-1);\n";
|
|
}
|
|
$path = tempnam(sys_get_temp_dir(), 'auth-dump-');
|
|
file_put_contents($path, gzencode($sql));
|
|
$this->dumpFiles[] = $path;
|
|
|
|
return $path;
|
|
}
|
|
|
|
public function test_vmangos_dates_use_configured_timezone_in_profile_and_admin(): void
|
|
{
|
|
config(['moonwell.timezone' => 'Europe/Samara']);
|
|
$result = app(VMangosAccountRegistrar::class)->register('PlayerOne', 'player@example.com', 'Pass1234');
|
|
DB::connection('vmangos_auth')->table('account')->where('id', $result['id'])->update([
|
|
'joindate' => '2026-10-03 22:30:00', 'last_login' => '2026-10-04 01:15:00',
|
|
]);
|
|
$accounts = app(VMangosAccountService::class);
|
|
$user = $accounts->findUserById($result['id']);
|
|
$this->assertSame('04.10.2026 02:30', $user->joinedAtLabel());
|
|
$this->assertSame('04.10.2026 05:15', $user->lastLoginLabel());
|
|
$row = $accounts->listAccounts()->first();
|
|
$this->assertSame('04.10.2026 02:30', $row['joined_at']);
|
|
$this->assertSame('04.10.2026 05:15', $row['last_login_at']);
|
|
}
|
|
}
|