mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(api-tokens): keep token search in sync after create, edit, and revoke
Closes #12465 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9e64998d68
commit
59481c7bb6
@@ -10,7 +10,9 @@ use App\Models\Project;
|
||||
use App\Services\Access\AccessResolver;
|
||||
use App\Services\Access\Grant;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Livewire\Attributes\Computed;
|
||||
@@ -30,6 +32,14 @@ class ApiTokens extends Component
|
||||
|
||||
public $tokens = [];
|
||||
|
||||
/**
|
||||
* Search text of each listed token. Alpine filters and pages the list with it through $wire, so it must change with $tokens.
|
||||
*
|
||||
* @var list<array{id: string, search: string}>
|
||||
*/
|
||||
#[Locked]
|
||||
public array $tokenSearchIndex = [];
|
||||
|
||||
public array $expirationOptions = [
|
||||
7 => '7 days',
|
||||
30 => '30 days',
|
||||
@@ -59,10 +69,18 @@ class ApiTokens extends Component
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.security.api-tokens', [
|
||||
'scopeLabels' => collect($this->scopeOptions)->reject(fn (array $option) => $option['header'] ?? false)->pluck('label', 'value'),
|
||||
'scopeLabels' => $this->scopeLabels(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<string, string>
|
||||
*/
|
||||
private function scopeLabels(): Collection
|
||||
{
|
||||
return collect($this->scopeOptions)->reject(fn (array $option) => $option['header'] ?? false)->pluck('label', 'value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $selected
|
||||
* @return list<string>
|
||||
@@ -149,6 +167,16 @@ class ApiTokens extends Component
|
||||
->where('team_id', currentTeam()->id)
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
$scopeLabels = $this->scopeLabels();
|
||||
$this->tokenSearchIndex = $this->tokens->map(fn (PersonalAccessToken $token) => [
|
||||
'id' => (string) $token->id,
|
||||
'search' => Str::lower(collect([$token->name, ...($token->abilities ?? [])])
|
||||
->merge($token->scopes === null
|
||||
? ['All resources in the team']
|
||||
: collect($token->scopes)->map(fn ($scope) => $scopeLabels[($scope['scope'] ?? '').':'.($scope['uuid'] ?? '')] ?? null)->filter())
|
||||
->join(' ')),
|
||||
])->values()->all();
|
||||
}
|
||||
|
||||
public function addNewToken()
|
||||
|
||||
@@ -41,14 +41,9 @@
|
||||
search: '',
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
tokens: @js($tokens->map(fn ($token) => [
|
||||
'id' => (string) $token->id,
|
||||
'search' => Str::lower(collect([$token->name, ...($token->abilities ?? [])])
|
||||
->merge($token->scopes === null
|
||||
? ['All resources in the team']
|
||||
: collect($token->scopes)->map(fn ($scope) => $scopeLabels[($scope['scope'] ?? '').':'.($scope['uuid'] ?? '')] ?? null)->filter())
|
||||
->join(' ')),
|
||||
])->values()),
|
||||
get tokens() {
|
||||
return this.$wire.tokenSearchIndex;
|
||||
},
|
||||
get filteredTokens() {
|
||||
const query = this.search.trim().toLowerCase();
|
||||
|
||||
|
||||
@@ -27,8 +27,32 @@ test('api token permission flags are locked', function (string $property) {
|
||||
})->with([
|
||||
'root permission flag' => 'canUseRootPermissions',
|
||||
'write permission flag' => 'canUseWritePermissions',
|
||||
'token search index' => 'tokenSearchIndex',
|
||||
]);
|
||||
|
||||
test('token search index follows created and revoked tokens', function () {
|
||||
$owner = User::factory()->create();
|
||||
$this->team->members()->attach($owner->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$component = Livewire::test(ApiTokens::class)
|
||||
->set('form.description', 'deploy-token')
|
||||
->set('form.expiresInDays', 30)
|
||||
->set('form.permissions', ['read'])
|
||||
->call('addNewToken');
|
||||
|
||||
$token = $owner->tokens()->sole();
|
||||
expect($component->get('tokenSearchIndex'))->toBe([
|
||||
['id' => (string) $token->id, 'search' => 'deploy-token read all resources in the team'],
|
||||
]);
|
||||
|
||||
$component->call('revoke', $token->id);
|
||||
|
||||
expect($component->get('tokenSearchIndex'))->toBe([]);
|
||||
});
|
||||
|
||||
test('member cannot tamper with root permission flag', function () {
|
||||
$member = User::factory()->create();
|
||||
$this->team->members()->attach($member->id, ['role' => 'member']);
|
||||
|
||||
Reference in New Issue
Block a user