Files
lotteryLaravel/app/Services/Player/NativeJwtSecretGuard.php

72 lines
2.1 KiB
PHP

<?php
namespace App\Services\Player;
use Throwable;
use App\Models\AdminSite;
use App\Lottery\ErrorCode;
use Illuminate\Support\Facades\Schema;
use App\Exceptions\PlayerAuthenticationException;
final class NativeJwtSecretGuard
{
public function validatedSecret(): string
{
$secret = config('lottery.player_auth.native.secret');
if (! is_string($secret) || $secret === '') {
$this->rejectConfiguration('原生登录未配置');
}
$legacySsoSecret = config('lottery.main_site.sso_jwt_secret');
if ($this->matches($secret, $legacySsoSecret)) {
$this->rejectConfiguration('原生登录密钥不得与 legacy SSO 密钥相同');
}
if ($this->matchesStoredSiteSsoSecret($secret)) {
$this->rejectConfiguration('原生登录密钥不得与任何站点保存的 SSO 密钥相同');
}
return $secret;
}
private function matchesStoredSiteSsoSecret(string $nativeSecret): bool
{
try {
if (! Schema::hasTable('admin_sites')
|| ! Schema::hasColumn('admin_sites', 'sso_jwt_secret_encrypted')) {
return false;
}
$sites = AdminSite::query()
->whereNotNull('sso_jwt_secret_encrypted')
->get(['sso_jwt_secret_encrypted']);
} catch (Throwable) {
$this->rejectConfiguration('无法检查原生登录密钥是否与站点 SSO 密钥冲突');
}
foreach ($sites as $site) {
if ($this->matches($nativeSecret, $site->decryptedSsoJwtSecret())) {
return true;
}
}
return false;
}
private function matches(string $nativeSecret, mixed $candidate): bool
{
return is_string($candidate)
&& $candidate !== ''
&& hash_equals($nativeSecret, $candidate);
}
private function rejectConfiguration(string $message): never
{
throw new PlayerAuthenticationException(
$message,
ErrorCode::PlayerSsoSecretNotConfigured->value,
503,
);
}
}