feat(settings): API token management UI for users

Adds a "API Tokens" settings page so an authenticated user can create and
revoke Sanctum personal access tokens for the new authenticated write
endpoints — using the official Sanctum API ($user->createToken() / tokens()).

- New Volt component settings/api-tokens (create token, one-time plain-text
  reveal with copy-to-clipboard, list + revoke own tokens).
- Registered route settings.api-tokens (country-prefixed, auth group) and
  added a nav entry in the settings layout.
- SEO definition for the new page.
- Pest feature tests (create/reveal-once, validation, revoke, ownership
  scoping) and a Pest browser screenshot test.
This commit is contained in:
HolgerHatGarKeineNode
2026-06-07 22:56:28 +02:00
parent a3062f6c4e
commit 4c81e20529
6 changed files with 260 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
<?php
use App\Models\User;
use Livewire\Livewire;
it('mounts the api tokens page when authenticated', function () {
actingAsUser();
Livewire::test('settings.api-tokens')->assertStatus(200);
});
it('creates a personal access token and reveals it once', function () {
$user = actingAsUser();
Livewire::test('settings.api-tokens')
->set('name', 'Externer Kurs-Sync')
->call('createToken')
->assertHasNoErrors()
->assertDispatched('token-created')
->assertSet('name', '')
->assertSet('plainTextToken', fn ($token) => is_string($token) && str_contains($token, '|'));
expect($user->tokens()->where('name', 'Externer Kurs-Sync')->exists())->toBeTrue();
});
it('requires a token name', function () {
actingAsUser();
Livewire::test('settings.api-tokens')
->set('name', '')
->call('createToken')
->assertHasErrors(['name' => 'required']);
});
it('revokes a token', function () {
$user = actingAsUser();
$token = $user->createToken('to-be-revoked')->accessToken;
Livewire::test('settings.api-tokens')
->call('deleteToken', $token->id)
->assertDispatched('token-deleted');
expect($user->tokens()->whereKey($token->id)->exists())->toBeFalse();
});
it('only lists the authenticated user\'s own tokens', function () {
$user = actingAsUser();
$user->createToken('mine');
$other = User::factory()->create();
$other->createToken('theirs');
Livewire::test('settings.api-tokens')
->assertViewHas('tokens', fn ($tokens) => $tokens->count() === 1 && $tokens->first()->name === 'mine');
});
it('cannot revoke a token belonging to another user', function () {
actingAsUser();
$other = User::factory()->create();
$foreignToken = $other->createToken('theirs')->accessToken;
Livewire::test('settings.api-tokens')
->call('deleteToken', $foreignToken->id);
expect($other->tokens()->whereKey($foreignToken->id)->exists())->toBeTrue();
});