mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
feat(api-tokens): roll a token to replace its secret
Keep the description, permissions, scopes, and expiry. Move the row actions into a dropdown menu and measure the dropdown panel after it is shown, so menus at the right edge stay on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
edee5ccfd6
commit
263eab5d27
@@ -264,6 +264,26 @@ class ApiTokens extends Component
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
public function roll(int $id)
|
||||
{
|
||||
try {
|
||||
$token = auth()->user()->tokens()
|
||||
->where('team_id', currentTeam()->id)
|
||||
->where('id', $id)
|
||||
->firstOrFail();
|
||||
$this->authorize('update', $token);
|
||||
$plainTextToken = $token->roll();
|
||||
auditLog('ui.api_token.rolled', [
|
||||
'team_id' => $token->team_id,
|
||||
'api_token_name' => $token->name,
|
||||
]);
|
||||
$this->getTokens();
|
||||
session()->flash('token', $plainTextToken);
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function revoke(int $id)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
|
||||
|
||||
class PersonalAccessToken extends SanctumPersonalAccessToken
|
||||
@@ -23,4 +24,27 @@ class PersonalAccessToken extends SanctumPersonalAccessToken
|
||||
'scopes' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public static function generateSecret(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%s%s%s',
|
||||
config('sanctum.token_prefix', ''),
|
||||
$tokenEntropy = Str::random(40),
|
||||
hash('crc32b', $tokenEntropy)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the secret and keep the abilities, scopes, and expiry. The old secret stops working at once.
|
||||
*
|
||||
* @return string the new plain-text token
|
||||
*/
|
||||
public function roll(): string
|
||||
{
|
||||
$secret = self::generateSecret();
|
||||
$this->forceFill(['token' => hash('sha256', $secret), 'last_used_at' => null])->save();
|
||||
|
||||
return $this->getKey().'|'.$secret;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-7
@@ -23,7 +23,6 @@ use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\Contracts\PasskeyUser;
|
||||
use Laravel\Fortify\PasskeyAuthenticatable;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
@@ -269,12 +268,7 @@ class User extends Authenticatable implements PasskeyUser, SendsEmail
|
||||
|
||||
public function createToken(string $name, array $abilities = ['read'], ?DateTimeInterface $expiresAt = null, ?int $teamId = null)
|
||||
{
|
||||
$plainTextToken = sprintf(
|
||||
'%s%s%s',
|
||||
config('sanctum.token_prefix', ''),
|
||||
$tokenEntropy = Str::random(40),
|
||||
hash('crc32b', $tokenEntropy)
|
||||
);
|
||||
$plainTextToken = PersonalAccessToken::generateSecret();
|
||||
|
||||
$token = $this->tokens()->create([
|
||||
'name' => $name,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
this.panelStyle = 'position: fixed; min-width: 0; visibility: hidden;';
|
||||
this.open = true;
|
||||
this.$nextTick(() => this.updatePosition());
|
||||
this.$nextTick(() => requestAnimationFrame(() => this.updatePosition()));
|
||||
},
|
||||
close() {
|
||||
this.open = false;
|
||||
|
||||
@@ -247,21 +247,45 @@
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
@if (auth()->id() === $token->tokenable_id)
|
||||
<x-modal-confirmation title="Confirm API Token Revocation?"
|
||||
submitAction="revoke({{ $token->id }})" :actions="[
|
||||
'This API token will be permanently revoked.',
|
||||
]"
|
||||
confirmationText="{{ $token->name }}"
|
||||
confirmationLabel="Enter the token description to confirm"
|
||||
shortConfirmationLabel="Token description"
|
||||
:confirmWithPassword="false" step2ButtonText="Revoke token">
|
||||
<x-table.dropdown panel-class="w-36!" role="menu">
|
||||
<x-slot:trigger>
|
||||
<button type="button"
|
||||
class="inline-flex h-7 items-center rounded-md px-2 text-[11px] font-medium text-error transition-colors hover:bg-error/10">
|
||||
Revoke
|
||||
<button type="button" class="icon-button" title="Token actions"
|
||||
aria-label="Token actions" aria-haspopup="menu" :aria-expanded="open">
|
||||
<x-reicon name="dots-horizontal" class="size-4" />
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Roll API Token?" buttonFullWidth
|
||||
submitAction="roll({{ $token->id }})" :actions="[
|
||||
'A new secret replaces this token. The current secret stops working at once.',
|
||||
'The description, permissions, scopes, and expiry do not change.',
|
||||
]"
|
||||
confirmationText="{{ $token->name }}"
|
||||
confirmationLabel="Enter the token description to confirm"
|
||||
shortConfirmationLabel="Token description"
|
||||
:confirmWithPassword="false" step2ButtonText="Roll token">
|
||||
<x-slot:trigger>
|
||||
<button type="button" role="menuitem" x-on:click="close()"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
|
||||
Roll
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Confirm API Token Revocation?" buttonFullWidth
|
||||
submitAction="revoke({{ $token->id }})" :actions="[
|
||||
'This API token will be permanently revoked.',
|
||||
]"
|
||||
confirmationText="{{ $token->name }}"
|
||||
confirmationLabel="Enter the token description to confirm"
|
||||
shortConfirmationLabel="Token description"
|
||||
:confirmWithPassword="false" step2ButtonText="Revoke token">
|
||||
<x-slot:trigger>
|
||||
<button type="button" role="menuitem" x-on:click="close()"
|
||||
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-error hover:bg-error/10">
|
||||
Revoke
|
||||
</button>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
</x-table.dropdown>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user