mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
feat(api-tokens): warn 7 days before a token expires
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5116a93f42
commit
548dec65b7
5 files changed
+30
-9
No files matched your search
@@ -22,17 +22,19 @@ class ApiTokenExpirationWarningJob implements ShouldBeEncrypted, ShouldQueue, Si
|
||||
|
||||
public $timeout = 120;
|
||||
|
||||
private const WARNING_DAYS = 7;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
PersonalAccessToken::query()
|
||||
->whereNotNull('expires_at')
|
||||
->where('expires_at', '>', now())
|
||||
->where('expires_at', '<=', now()->addDay())
|
||||
->where('expires_at', '<=', now()->addDays(self::WARNING_DAYS))
|
||||
->whereNull('api_token_expiration_warning_sent_at')
|
||||
->where('tokenable_type', User::class)
|
||||
->chunkById(100, function ($tokens) {
|
||||
foreach ($tokens as $token) {
|
||||
if (! $token->team_id) {
|
||||
if (! $token->team_id || ! $this->isDue($token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -53,7 +55,7 @@ class ApiTokenExpirationWarningJob implements ShouldBeEncrypted, ShouldQueue, Si
|
||||
->whereKey($token->getKey())
|
||||
->whereNotNull('expires_at')
|
||||
->where('expires_at', '>', now())
|
||||
->where('expires_at', '<=', now()->addDay())
|
||||
->where('expires_at', '<=', now()->addDays(self::WARNING_DAYS))
|
||||
->whereNull('api_token_expiration_warning_sent_at')
|
||||
->update(['api_token_expiration_warning_sent_at' => $warningSentAt]);
|
||||
|
||||
@@ -65,4 +67,17 @@ class ApiTokenExpirationWarningJob implements ShouldBeEncrypted, ShouldQueue, Si
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn 7 days before expiry. A token that lives 7 days or less gets the warning 24 hours before expiry,
|
||||
* so it is not warned right after it is created.
|
||||
*/
|
||||
private function isDue(PersonalAccessToken $token): bool
|
||||
{
|
||||
if ($token->created_at->diffInDays($token->expires_at) > self::WARNING_DAYS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $token->expires_at->lte(now()->addDay());
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class ApiTokenExpiringNotification extends CustomEmailNotification
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$mail->subject("Coolify: API token '{$this->tokenName}' expires in 24 hours");
|
||||
$mail->subject("Coolify: API token '{$this->tokenName}' expires on {$this->expiresAt}");
|
||||
$mail->view('emails.api-token-expiring', [
|
||||
'tokenName' => $this->tokenName,
|
||||
'expiresAt' => $this->expiresAt,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
'as' => 'span',
|
||||
'dynamic' => false,
|
||||
'pulse' => false, // ping the dot for a state that is still in progress
|
||||
'size' => 'md', // sm fits inline next to 12px table text without growing the row
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -15,7 +16,12 @@
|
||||
'error' => 'bg-red-500',
|
||||
];
|
||||
|
||||
$baseClasses = 'inline-flex h-6 max-w-full items-center gap-1.5 whitespace-nowrap rounded-full border border-neutral-200 bg-neutral-100 px-2 text-xs font-medium leading-none text-neutral-700 dark:border-white/[0.12] dark:bg-white/[0.07] dark:text-white';
|
||||
$sizeClasses = [
|
||||
'sm' => 'h-4 gap-1 px-1.5 text-[10px]',
|
||||
'md' => 'h-6 gap-1.5 px-2 text-xs',
|
||||
];
|
||||
|
||||
$baseClasses = 'inline-flex max-w-full items-center whitespace-nowrap rounded-full border border-neutral-200 bg-neutral-100 font-medium leading-none text-neutral-700 dark:border-white/[0.12] dark:bg-white/[0.07] dark:text-white ' . ($sizeClasses[$size] ?? $sizeClasses['md']);
|
||||
@endphp
|
||||
|
||||
@if ($as === 'button')
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
<span class="truncate text-[12px] font-medium text-black dark:text-fg">{{ $token->name }}</span>
|
||||
@if ($token->isPaused())
|
||||
<x-status-badge label="Paused" type="warning" />
|
||||
<x-status-badge label="Paused" type="warning" size="sm" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="truncate text-[11px] text-neutral-500 dark:text-fg-dim">
|
||||
@@ -266,7 +266,7 @@
|
||||
@if (!$token->expires_at)
|
||||
Never
|
||||
@elseif ($token->expires_at->isPast())
|
||||
<x-status-badge label="Expired" type="error" />
|
||||
<x-status-badge label="Expired" type="error" size="sm" />
|
||||
@else
|
||||
{{ $token->expires_at->format('Y-m-d') }}
|
||||
@endif
|
||||
|
||||
@@ -106,8 +106,8 @@ describe('ApiTokenExpirationWarningJob', function () {
|
||||
Notification::assertSentToTimes($this->team, ApiTokenExpiringNotification::class, 2);
|
||||
});
|
||||
|
||||
test('skips tokens expiring more than 24h out', function () {
|
||||
createTokenExpiring($this->user, $this->team, Carbon::now()->addDays(3));
|
||||
test('skips tokens expiring more than 7 days out', function () {
|
||||
createTokenExpiring($this->user, $this->team, Carbon::now()->addDays(8));
|
||||
|
||||
(new ApiTokenExpirationWarningJob)->handle();
|
||||
|
||||
|
||||
Reference in new issue
Block a user