Files
moonwell-web/app/Services/VMangosAccountRegistrar.php
T

122 lines
4.2 KiB
PHP

<?php
namespace App\Services;
use App\Exceptions\DuplicateGameAccountException;
use App\Models\User;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
class VMangosAccountRegistrar
{
public function __construct(private readonly VMangosSrpService $srp) {}
/**
* @return array{id: int, username: string}
*/
public function register(string $username, string $email, string $password): array
{
$normalizedUsername = $this->srp->normalizeUsername($username);
$normalizedEmail = strtolower(trim($email));
[$saltHex, $verifierHex] = $this->srp->makeRegistrationData($normalizedUsername, bin2hex(random_bytes(16)));
$connection = DB::connection(config('moonwell.auth_connection'));
return DB::transaction(fn (): array => $connection->transaction(function () use (
$connection,
$normalizedEmail,
$normalizedUsername,
$saltHex,
$verifierHex,
$password,
): array {
if ($this->accountExists($connection, 'username', $normalizedUsername)) {
throw DuplicateGameAccountException::forUsername();
}
if (
config('moonwell.registration.enforce_unique_email')
&& $normalizedEmail !== ''
&& $this->accountExists($connection, 'email', $normalizedEmail)
) {
throw DuplicateGameAccountException::forEmail();
}
if (User::whereRaw('UPPER(name) = ?', [$normalizedUsername])->exists()) {
throw DuplicateGameAccountException::forUsername();
}
if (User::whereRaw('LOWER(email) = ?', [$normalizedEmail])->exists()) {
throw DuplicateGameAccountException::forEmail();
}
try {
$webUser = User::create([
'name' => $normalizedUsername,
'email' => $normalizedEmail,
'password' => $password,
]);
} catch (QueryException $exception) {
if ($this->isDuplicateKeyException($exception)) {
throw DuplicateGameAccountException::forEmail();
}
throw $exception;
}
try {
$accountId = (int) $connection->table('account')->insertGetId([
'username' => $normalizedUsername,
's' => $saltHex,
'v' => $verifierHex,
'expansion' => 0,
'email' => $normalizedEmail,
'joindate' => now('UTC')->toDateTimeString(),
]);
} catch (QueryException $exception) {
if ($this->isDuplicateKeyException($exception)) {
throw DuplicateGameAccountException::forUsername();
}
throw $exception;
}
$connection->table('site_credentials')->insert([
'account_id' => $accountId,
'web_user_id' => $webUser->id,
'created_at' => now(),
'updated_at' => now(),
]);
foreach ($connection->table('realmlist')->pluck('id') as $realmId) {
$connection->table('realmcharacters')->insertOrIgnore([
'realmid' => (int) $realmId,
'acctid' => $accountId,
'numchars' => 0,
]);
}
$connection->table('account_access')->updateOrInsert(
['id' => $accountId, 'RealmID' => (int) config('moonwell.registration.realm_id')],
['gmlevel' => (int) config('moonwell.registration.gmlevel')],
);
return [
'id' => $accountId,
'username' => $normalizedUsername,
];
}));
}
private function accountExists(ConnectionInterface $connection, string $column, string $value): bool
{
return $connection->table('account')->where($column, $value)->exists();
}
private function isDuplicateKeyException(QueryException $exception): bool
{
return (string) $exception->getCode() === '23000';
}
}