12-05-2026 Frontend dev
This commit is contained in:
parent
405df0a122
commit
5b8bdf4182
779 changed files with 480564 additions and 6241 deletions
78
tests/Feature/ApiAccessSecurityAndLoggingTest.php
Normal file
78
tests/Feature/ApiAccessSecurityAndLoggingTest.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
use App\Models\ApiUsageLog;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('api requests are logged without storing bearer token contents', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = User::factory()->create(['is_active' => true]);
|
||||
$plainTextToken = $user->createToken('API Client', ['press-releases:read'])->plainTextToken;
|
||||
$tokenId = $user->tokens()->value('id');
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$plainTextToken}")
|
||||
->getJson('/api/v1/categories')
|
||||
->assertOk();
|
||||
|
||||
$log = ApiUsageLog::query()->firstOrFail();
|
||||
|
||||
expect($log->user_id)->toBe($user->id)
|
||||
->and($log->personal_access_token_id)->toBe($tokenId)
|
||||
->and($log->method)->toBe('GET')
|
||||
->and($log->path)->toBe('/api/v1/categories')
|
||||
->and($log->status_code)->toBe(200)
|
||||
->and(json_encode($log->toArray()))->not->toContain($plainTextToken);
|
||||
});
|
||||
|
||||
test('legacy api key rejections are logged', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->getJson('/api/v1/categories?api_key=legacy-secret')
|
||||
->assertStatus(410);
|
||||
|
||||
$log = ApiUsageLog::query()->firstOrFail();
|
||||
|
||||
expect($log->user_id)->toBeNull()
|
||||
->and($log->personal_access_token_id)->toBeNull()
|
||||
->and($log->path)->toBe('/api/v1/categories')
|
||||
->and($log->status_code)->toBe(410)
|
||||
->and(json_encode($log->toArray()))->not->toContain('legacy-secret');
|
||||
});
|
||||
|
||||
test('inactive users cannot use existing api tokens', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = User::factory()->create(['is_active' => false]);
|
||||
$plainTextToken = $user->createToken('Old API Client', ['press-releases:read'])->plainTextToken;
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$plainTextToken}")
|
||||
->getJson('/api/v1/categories')
|
||||
->assertForbidden()
|
||||
->assertJsonPath('message', 'API access is disabled for inactive users.');
|
||||
|
||||
expect(ApiUsageLog::query()->value('status_code'))->toBe(403);
|
||||
});
|
||||
|
||||
test('api rate limit is scoped to each personal access token', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = User::factory()->create(['is_active' => true]);
|
||||
$firstToken = $user->createToken('Busy Client', ['press-releases:read'])->plainTextToken;
|
||||
$secondToken = $user->createToken('Other Client', ['press-releases:read'])->plainTextToken;
|
||||
|
||||
expect(strtok($firstToken, '|'))->not->toBe(strtok($secondToken, '|'));
|
||||
|
||||
for ($request = 1; $request <= 60; $request++) {
|
||||
$this->withHeader('Authorization', "Bearer {$firstToken}")
|
||||
->getJson('/api/v1/categories')
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$firstToken}")
|
||||
->getJson('/api/v1/categories')
|
||||
->assertStatus(429)
|
||||
->assertJsonPath('message', 'API rate limit exceeded.');
|
||||
|
||||
$this->flushHeaders();
|
||||
|
||||
$this->withHeader('Authorization', "Bearer {$secondToken}")
|
||||
->getJson('/api/v1/categories')
|
||||
->assertOk();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue