create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/dashboard') ->assertRedirect(config('app.url')); }); it('blocks non-admin from admin users page', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/users/show/1') ->assertRedirect(config('app.url')); }); it('blocks non-admin from admin reports', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/reports') ->assertRedirect(config('app.url')); }); it('blocks non-admin from admin settings', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/settings') ->assertRedirect(config('app.url')); }); it('blocks non-admin from admin instances', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/instances') ->assertRedirect(config('app.url')); }); it('blocks non-admin from curated onboarding', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/curated-onboarding/home') ->assertRedirect(config('app.url')); }); }); describe('API admin routes deny non-admin users', function () { it('blocks non-admin from admin stats API', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:read']); $this->getJson('/api/admin/stats') ->assertNotFound(); }); it('blocks non-admin from admin users list API', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:read']); $this->getJson('/api/admin/users/list') ->assertNotFound(); }); it('blocks non-admin from admin config API', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:read']); $this->getJson('/api/admin/config') ->assertNotFound(); }); it('blocks non-admin from admin instances API', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:read']); $this->getJson('/api/admin/instances/list') ->assertNotFound(); }); it('blocks non-admin from admin reports API', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:read']); $this->getJson('/api/admin/mod-reports/list') ->assertNotFound(); }); it('blocks non-admin from v1 admin domain blocks', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:read']); $this->getJson('/api/v1/admin/domain_blocks') ->assertForbidden(); }); it('blocks non-admin from internal admin API', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->getJson('/i/admin/api/stats') ->assertRedirect(config('app.url')); }); it('blocks non-admin from admin settings fetch', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); $this->actingAs($user) ->getJson('/i/admin/api/settings/fetch') ->assertRedirect(config('app.url')); }); }); describe('admin write operations deny non-admin users', function () { it('blocks non-admin from admin user actions', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:write']); $this->postJson('/api/v1/admin/domain_blocks', [ 'domain' => 'evil.example.com', ])->assertForbidden(); }); it('blocks non-admin from deleting domain blocks', function () { $user = User::factory()->create(['is_admin' => false]); $user->refresh(); Passport::actingAs($user, ['admin:write']); $this->deleteJson('/api/v1/admin/domain_blocks/1') ->assertForbidden(); }); }); describe('unauthenticated admin access', function () { it('redirects unauthenticated users from admin dashboard', function () { $response = $this->get('/i/admin/dashboard'); // Should redirect to login (may go via password confirm) $response->assertRedirect(); }); it('returns 401 for unauthenticated admin API requests', function () { $this->getJson('/api/admin/stats') ->assertUnauthorized(); }); it('returns 401 for unauthenticated v1 admin API', function () { $this->getJson('/api/v1/admin/domain_blocks') ->assertUnauthorized(); }); }); describe('admin access granted to admin users', function () { it('allows admin to access dashboard', function () { $admin = User::factory()->admin()->create(); $admin->refresh(); $this->actingAs($admin) ->withSession(['auth.password_confirmed_at' => time()]) ->get('/i/admin/dashboard') ->assertOk(); }); it('allows admin to access admin stats API', function () { $admin = User::factory()->admin()->create(); $admin->refresh(); Passport::actingAs($admin, ['admin:read']); $this->getJson('/api/admin/stats') ->assertOk(); }); it('allows admin to access instance stats API when is_admin is a boolean', function () { // Postgres hydrates the boolean `is_admin` column to a PHP bool, so // the guard must accept `true` and not just the integer 1. Force the // boolean here so the test is driver-independent (SQLite hydrates 1). $admin = User::factory()->admin()->create(); $admin->refresh(); $admin->is_admin = true; Passport::actingAs($admin, ['admin:read']); $this->getJson('/api/admin/instance/stats') ->assertOk(); }); it('allows admin to access admin users API', function () { $admin = User::factory()->admin()->create(); $admin->refresh(); Passport::actingAs($admin, ['admin:read']); $this->getJson('/api/admin/users/list') ->assertOk(); }); it('allows admin to access v1 admin domain blocks', function () { $admin = User::factory()->admin()->create(); $admin->refresh(); Passport::actingAs($admin, ['admin:read']); $this->getJson('/api/v1/admin/domain_blocks') ->assertOk(); }); });