table('account')->selectRaw('id, username, email, locked, joindate, HEX(salt) AS salt_hex, HEX(verifier) AS verifier_hex')->orderBy('id'); if (! $this->option('include-bots')) { $query->where('username', 'not like', config('moonwell.portal.bot_account_prefix').'%'); } $created = 0; $linked = 0; $skipped = 0; foreach ($query->cursor() as $legacy) { $username = $srp->normalizeUsername($legacy->username); if (preg_match('/^[A-Z0-9]{3,16}$/D', $username) !== 1) { throw new RuntimeException("Unsupported legacy account name for ID {$legacy->id}."); } $existing = $target->table('account')->where('username', $username)->first(['id']); $credential = $existing ? $target->table('site_credentials')->where('account_id', $existing->id)->first() : null; if ($credential) { if ((int) $credential->legacy_account_id !== (int) $legacy->id) { throw new RuntimeException("Account name conflict for legacy ID {$legacy->id}."); } $skipped++; continue; } if ($this->option('dry-run')) { $existing ? $linked++ : $created++; continue; } $target->transaction(function () use ($target, $source, $srp, $legacy, $username, $existing): void { // Only the SRP verifier is stored; the random plaintext is discarded. [$gameSalt, $gameVerifier] = $srp->makeRegistrationData($username, bin2hex(random_bytes(16))); if ($existing) { $accountId = (int) $existing->id; $target->table('account')->where('id', $accountId)->update([ 's' => $gameSalt, 'v' => $gameVerifier, 'sessionkey' => null, 'email' => $legacy->email, 'locked' => (int) $legacy->locked, ]); } else { $accountId = (int) $target->table('account')->insertGetId([ 'username' => $username, 's' => $gameSalt, 'v' => $gameVerifier, 'email' => $legacy->email, 'locked' => (int) $legacy->locked, 'expansion' => 0, 'joindate' => $legacy->joindate, ]); } $target->table('site_credentials')->insert([ 'account_id' => $accountId, 'legacy_account_id' => (int) $legacy->id, 'salt_hex' => $legacy->salt_hex, 'verifier_hex' => $legacy->verifier_hex, 'created_at' => now(), 'updated_at' => now(), ]); foreach ($target->table('realmlist')->pluck('id') as $realmId) { $target->table('realmcharacters')->insertOrIgnore(['realmid' => (int) $realmId, 'acctid' => $accountId, 'numchars' => 0]); } $gmLevel = (int) $source->table('account_access')->where('id', $legacy->id)->max('gmlevel'); if ($gmLevel > 0) { $target->table('account_access')->updateOrInsert( ['id' => $accountId, 'RealmID' => -1], ['gmlevel' => $gmLevel], ); } }, 3); $existing ? $linked++ : $created++; } if (! $this->option('dry-run') && ($created + $linked) > 0) { // Old bearer tokens and sessions contain AzerothCore IDs, which may // now identify a different vMaNGOS account. Force fresh login. DB::table('oauth_access_tokens')->where('revoked', false)->update(['revoked' => true]); DB::table('oauth_refresh_tokens')->where('revoked', false)->update(['revoked' => true]); if (config('session.driver') === 'database') { DB::table(config('session.table', 'sessions'))->delete(); } elseif (config('session.driver') === 'file') { foreach (File::files(config('session.files')) as $sessionFile) { File::delete($sessionFile->getPathname()); } } } $this->info("Created: {$created}; linked: {$linked}; already imported: {$skipped}."); return self::SUCCESS; } }