first commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\DuplicateGameAccountException;
|
||||
use Brick\Math\BigInteger;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AzerothCoreAccountRegistrar
|
||||
{
|
||||
private const string MODULUS_HEX = '894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7';
|
||||
|
||||
private const int GENERATOR = 7;
|
||||
|
||||
private const int BYTE_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* @return array{id: int, username: string}
|
||||
*/
|
||||
public function register(string $username, string $email, string $password): array
|
||||
{
|
||||
$normalizedUsername = $this->normalizeUpperOnlyLatin(trim($username));
|
||||
$normalizedPassword = $this->normalizeUpperOnlyLatin($password);
|
||||
$normalizedEmail = $this->normalizeUpperOnlyLatin(trim($email));
|
||||
[$saltHex, $verifierHex] = $this->makeRegistrationData($normalizedUsername, $normalizedPassword);
|
||||
|
||||
$connection = DB::connection(config('moonwell.auth_connection'));
|
||||
|
||||
return $connection->transaction(function () use (
|
||||
$connection,
|
||||
$normalizedEmail,
|
||||
$normalizedUsername,
|
||||
$saltHex,
|
||||
$verifierHex,
|
||||
): 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();
|
||||
}
|
||||
|
||||
try {
|
||||
$connection->insert(
|
||||
'INSERT INTO account (username, salt, verifier, expansion, reg_mail, email, joindate) VALUES (?, UNHEX(?), UNHEX(?), ?, ?, ?, ?)',
|
||||
[
|
||||
$normalizedUsername,
|
||||
$saltHex,
|
||||
$verifierHex,
|
||||
config('moonwell.registration.expansion'),
|
||||
$normalizedEmail,
|
||||
$normalizedEmail,
|
||||
now()->toDateTimeString(),
|
||||
],
|
||||
);
|
||||
} catch (QueryException $exception) {
|
||||
if ($this->isDuplicateKeyException($exception)) {
|
||||
throw DuplicateGameAccountException::forUsername();
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$accountId = (int) $connection->getPdo()->lastInsertId();
|
||||
|
||||
$connection->insert(
|
||||
'INSERT IGNORE INTO realmcharacters (realmid, acctid, numchars) SELECT id, ?, 0 FROM realmlist',
|
||||
[$accountId],
|
||||
);
|
||||
|
||||
$connection->statement(
|
||||
'INSERT INTO account_access (id, gmlevel, RealmID, comment) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE gmlevel = VALUES(gmlevel), comment = VALUES(comment)',
|
||||
[
|
||||
$accountId,
|
||||
config('moonwell.registration.gmlevel'),
|
||||
config('moonwell.registration.realm_id'),
|
||||
config('moonwell.registration.access_comment'),
|
||||
],
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => $accountId,
|
||||
'username' => $normalizedUsername,
|
||||
];
|
||||
}, 3);
|
||||
}
|
||||
|
||||
private function accountExists(ConnectionInterface $connection, string $column, string $value): bool
|
||||
{
|
||||
return $connection->table('account')->where($column, $value)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
*/
|
||||
private function makeRegistrationData(string $username, string $password): array
|
||||
{
|
||||
$salt = random_bytes(self::BYTE_LENGTH);
|
||||
$innerDigest = hash('sha1', sprintf('%s:%s', $username, $password), true);
|
||||
$xDigest = hash('sha1', $salt.$innerDigest, true);
|
||||
$modulus = BigInteger::fromBase(self::MODULUS_HEX, 16);
|
||||
$exponent = BigInteger::fromBytes(strrev($xDigest), false);
|
||||
$verifier = BigInteger::of(self::GENERATOR)->modPow($exponent, $modulus);
|
||||
$verifierBytes = strrev(str_pad($verifier->toBytes(false), self::BYTE_LENGTH, "\0", STR_PAD_LEFT));
|
||||
|
||||
return [
|
||||
strtoupper(bin2hex($salt)),
|
||||
strtoupper(bin2hex($verifierBytes)),
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeUpperOnlyLatin(string $value): string
|
||||
{
|
||||
return strtoupper($value);
|
||||
}
|
||||
|
||||
private function isDuplicateKeyException(QueryException $exception): bool
|
||||
{
|
||||
return (string) $exception->getCode() === '23000';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user