states, and pulls data from the authenticated /api/v1/tenant/packages endpoint.
(resources/js/admin/pages/EventFormPage.tsx, resources/js/admin/api.ts)
- Harden tenant-admin auth flow: prevent PKCE state loss, scope out StrictMode double-processing, add SPA
routes for /event-admin/login and /event-admin/logout, and tighten token/session clearing semantics (resources/js/admin/auth/{context,tokens}.tsx, resources/js/admin/pages/{AuthCallbackPage,LogoutPage}.tsx,
resources/js/admin/router.tsx, routes/web.php)
78 lines
2.5 KiB
PHP
78 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Str;
|
|
|
|
class OAuthPruneKeysCommand extends Command
|
|
{
|
|
protected $signature = 'oauth:prune-keys
|
|
{--days=90 : Prune keys whose directories were last modified before this many days ago}
|
|
{--dry-run : Show which keys would be removed without deleting}
|
|
{--force : Skip confirmation prompt}';
|
|
|
|
protected $description = 'Remove legacy JWT signing keys older than the configured threshold.';
|
|
|
|
public function handle(): int
|
|
{
|
|
$storage = rtrim(config('oauth.keys.storage_path', storage_path('app/oauth-keys')), DIRECTORY_SEPARATOR);
|
|
$currentKid = config('oauth.keys.current_kid', 'fotospiel-jwt');
|
|
|
|
if (! File::exists($storage)) {
|
|
$this->error("Key store path does not exist: {$storage}");
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$days = (int) $this->option('days');
|
|
$cutoff = now()->subDays($days);
|
|
|
|
$candidates = collect(File::directories($storage))
|
|
->reject(fn ($path) => Str::lower(basename($path)) === 'archive')
|
|
->filter(function (string $path) use ($currentKid, $cutoff) {
|
|
$kid = basename($path);
|
|
if ($kid === $currentKid) {
|
|
return false;
|
|
}
|
|
|
|
$lastModified = File::lastModified($path);
|
|
|
|
return $lastModified !== false && $cutoff->greaterThan(\Carbon\Carbon::createFromTimestamp($lastModified));
|
|
})
|
|
->values();
|
|
|
|
if ($candidates->isEmpty()) {
|
|
$this->info("No legacy key directories older than {$days} days were found.");
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->table(
|
|
['KID', 'Last Modified', 'Path'],
|
|
$candidates->map(fn ($path) => [
|
|
basename($path),
|
|
date('c', File::lastModified($path)),
|
|
$path,
|
|
])
|
|
);
|
|
|
|
if ($this->option('dry-run')) {
|
|
$this->info('Dry run complete. No keys were removed.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
if (! $this->option('force') && ! $this->confirm('Remove the listed legacy key directories?', false)) {
|
|
$this->warn('Prune cancelled.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
foreach ($candidates as $path) {
|
|
File::deleteDirectory($path);
|
|
}
|
|
|
|
$this->info('Legacy key directories pruned.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|