fix(auth): enforce password and agent role boundaries
This commit is contained in:
@@ -150,15 +150,51 @@ test('agent creation keeps its agent role and cleanup removes only unbound platf
|
||||
'granted_at' => now(),
|
||||
]);
|
||||
|
||||
expect(InvalidPlatformAgentRoleCleanup::run())->toBe(1);
|
||||
$otherSiteId = (int) DB::table('admin_sites')->insertGetId([
|
||||
'code' => 'boundary-other-site',
|
||||
'name' => 'Boundary Other Site',
|
||||
'is_default' => false,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('admin_user_site_roles')->insert([
|
||||
'admin_user_id' => $agentUser->id,
|
||||
'site_id' => $otherSiteId,
|
||||
'role_id' => $agentRole->id,
|
||||
'granted_at' => now(),
|
||||
]);
|
||||
$ordinaryRole = AdminRole::query()->create([
|
||||
'slug' => 'cross_site_ordinary_role',
|
||||
'name' => 'Cross Site Ordinary Role',
|
||||
'scope_type' => AdminRole::SCOPE_SYSTEM,
|
||||
]);
|
||||
DB::table('admin_user_site_roles')->insert([
|
||||
'admin_user_id' => $agentUser->id,
|
||||
'site_id' => $otherSiteId,
|
||||
'role_id' => $ordinaryRole->id,
|
||||
'granted_at' => now(),
|
||||
]);
|
||||
|
||||
expect(InvalidPlatformAgentRoleCleanup::run())->toBe(2);
|
||||
expect(DB::table('admin_user_site_roles')
|
||||
->where('admin_user_id', $illegalUser->id)
|
||||
->where('role_id', $agentRole->id)
|
||||
->exists())->toBeFalse();
|
||||
expect(DB::table('admin_user_site_roles')
|
||||
->where('admin_user_id', $agentUser->id)
|
||||
->where('site_id', $siteId)
|
||||
->where('role_id', $agentRole->id)
|
||||
->exists())->toBeTrue();
|
||||
expect(DB::table('admin_user_site_roles')
|
||||
->where('admin_user_id', $agentUser->id)
|
||||
->where('site_id', $otherSiteId)
|
||||
->where('role_id', $agentRole->id)
|
||||
->exists())->toBeFalse();
|
||||
expect(DB::table('admin_user_site_roles')
|
||||
->where('admin_user_id', $agentUser->id)
|
||||
->where('site_id', $otherSiteId)
|
||||
->where('role_id', $ordinaryRole->id)
|
||||
->exists())->toBeTrue();
|
||||
expect(AdminUser::query()->whereKey($illegalUser->id)->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use Database\Seeders\CurrencySeeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Database\Seeders\LotterySettingsSeeder;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use App\Services\Player\PlayerNativeAuthService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
@@ -397,6 +398,50 @@ test('native player password change validates current password', function (): vo
|
||||
->and(Hash::check('old-secret', (string) $player->fresh()->password_hash))->toBeTrue();
|
||||
});
|
||||
|
||||
test('native player password change is rate limited by player and ip', function (): void {
|
||||
$site = DB::table('admin_sites')->where('is_default', true)->first();
|
||||
$rootId = (int) DB::table('agent_nodes')->where('depth', 0)->value('id');
|
||||
|
||||
$player = Player::query()->create([
|
||||
'site_code' => (string) $site->code,
|
||||
'agent_node_id' => $rootId,
|
||||
'site_player_id' => 'native:password-rate-limit',
|
||||
'auth_source' => PlayerAuthSource::LOTTERY_NATIVE,
|
||||
'funding_mode' => PlayerFundingMode::CREDIT,
|
||||
'username' => 'password_rate_limit_user',
|
||||
'password_hash' => Hash::make('old-secret'),
|
||||
'default_currency' => 'NPR',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$ip = '198.51.100.25';
|
||||
$token = app(PlayerNativeAuthService::class)->issueToken($player);
|
||||
RateLimiter::clear($player->id.'|'.$ip);
|
||||
|
||||
for ($attempt = 0; $attempt < 5; $attempt++) {
|
||||
$this->withServerVariables(['REMOTE_ADDR' => $ip])
|
||||
->withHeader('Authorization', 'Bearer '.$token)
|
||||
->putJson('/api/v1/player/password', [
|
||||
'current_password' => 'wrong-secret',
|
||||
'password' => 'new-secret',
|
||||
'password_confirmation' => 'new-secret',
|
||||
])
|
||||
->assertStatus(422);
|
||||
}
|
||||
|
||||
$this->withServerVariables(['REMOTE_ADDR' => $ip])
|
||||
->withHeader('Authorization', 'Bearer '.$token)
|
||||
->putJson('/api/v1/player/password', [
|
||||
'current_password' => 'wrong-secret',
|
||||
'password' => 'new-secret',
|
||||
'password_confirmation' => 'new-secret',
|
||||
])
|
||||
->assertStatus(429)
|
||||
->assertJsonPath('code', ErrorCode::TooManyRequests->value);
|
||||
|
||||
expect(Hash::check('old-secret', (string) $player->fresh()->password_hash))->toBeTrue();
|
||||
});
|
||||
|
||||
test('sso player cannot use native password management', function (): void {
|
||||
$site = DB::table('admin_sites')->where('is_default', true)->first();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user