Files
fotospiel-app/app/Filament/Resources/RefreshTokenResource.php
Codex Agent 6290a3a448 Fix tenant event form package selector so it no longer renders empty-value options, handles loading/empty
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)
2025-10-19 23:00:47 +02:00

201 lines
7.9 KiB
PHP

<?php
namespace App\Filament\Resources;
use App\Filament\Resources\RefreshTokenResource\Pages;
use App\Filament\Resources\RefreshTokenResource\RelationManagers\AuditsRelationManager;
use App\Models\RefreshToken;
use BackedEnum;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
use Filament\Tables\Filters\SelectFilter;
use Illuminate\Database\Eloquent\Builder;
class RefreshTokenResource extends Resource
{
protected static ?string $model = RefreshToken::class;
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-shield-check';
protected static ?int $navigationSort = 32;
public static function getNavigationGroup(): string
{
return __('admin.nav.security');
}
public static function getNavigationLabel(): string
{
return __('admin.refresh_tokens.menu');
}
public static function getPluralLabel(): string
{
return __('admin.refresh_tokens.menu');
}
public static function getModelLabel(): string
{
return __('admin.refresh_tokens.single');
}
public static function form(Schema $form): Schema
{
return $form->schema([]);
}
public static function table(Table $table): Table
{
return $table
->defaultSort('created_at', 'desc')
->columns([
Tables\Columns\TextColumn::make('tenant.name')
->label(__('admin.refresh_tokens.fields.tenant'))
->searchable(),
Tables\Columns\TextColumn::make('client_id')
->label(__('admin.refresh_tokens.fields.client'))
->copyable()
->toggleable(),
Tables\Columns\TextColumn::make('ip_address')
->label(__('admin.refresh_tokens.fields.ip_address'))
->toggleable(),
Tables\Columns\TextColumn::make('user_agent')
->label(__('admin.refresh_tokens.fields.user_agent'))
->limit(40)
->tooltip(fn (RefreshToken $record) => $record->user_agent)
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('created_at')
->label(__('admin.refresh_tokens.fields.created_at'))
->since()
->sortable(),
Tables\Columns\TextColumn::make('last_used_at')
->label(__('admin.refresh_tokens.fields.last_used_at'))
->since()
->sortable()
->toggleable(),
Tables\Columns\TextColumn::make('expires_at')
->label(__('admin.refresh_tokens.fields.expires_at'))
->dateTime()
->sortable()
->toggleable(),
Tables\Columns\TextColumn::make('status')
->label(__('admin.refresh_tokens.fields.status'))
->badge()
->formatStateUsing(function (RefreshToken $record): string {
if ($record->revoked_at) {
return __('admin.refresh_tokens.status.revoked');
}
if ($record->expires_at && $record->expires_at->isPast()) {
return __('admin.refresh_tokens.status.expired');
}
return __('admin.refresh_tokens.status.active');
})
->color(function (RefreshToken $record): string {
if ($record->revoked_at) {
return 'danger';
}
if ($record->expires_at && $record->expires_at->isPast()) {
return 'warning';
}
return 'success';
}),
Tables\Columns\TextColumn::make('revoked_reason')
->label(__('admin.refresh_tokens.fields.revoked_reason'))
->formatStateUsing(function (?string $state): ?string {
if (! $state) {
return null;
}
$key = "admin.refresh_tokens.reasons.{$state}";
$translated = __($key);
return $translated === $key ? $state : $translated;
})
->badge()
->color('gray')
->toggleable(),
])
->filters([
SelectFilter::make('status')
->label(__('admin.refresh_tokens.filters.status'))
->options([
'active' => __('admin.refresh_tokens.status.active'),
'revoked' => __('admin.refresh_tokens.status.revoked'),
'expired' => __('admin.refresh_tokens.status.expired'),
])
->query(function (Builder $query, array $data): Builder {
return match ($data['value'] ?? null) {
'revoked' => $query->whereNotNull('revoked_at'),
'expired' => $query->whereNull('revoked_at')->whereNotNull('expires_at')->where('expires_at', '<=', now()),
'active' => $query->whereNull('revoked_at')->where(function ($inner) {
$inner->whereNull('expires_at')
->orWhere('expires_at', '>', now());
}),
default => $query,
};
}),
SelectFilter::make('tenant_id')
->label(__('admin.refresh_tokens.filters.tenant'))
->relationship('tenant', 'name')
->searchable(),
])
->actions([
Tables\Actions\ViewAction::make(),
Action::make('revoke')
->label(__('admin.refresh_tokens.actions.revoke'))
->icon('heroicon-o-no-symbol')
->color('danger')
->visible(fn (RefreshToken $record): bool => $record->isActive())
->form([
Forms\Components\Select::make('reason')
->label(__('admin.refresh_tokens.fields.revoked_reason'))
->options([
'manual' => __('admin.refresh_tokens.reasons.manual'),
'operator' => __('admin.refresh_tokens.reasons.operator'),
])
->default('manual')
->required(),
Forms\Components\Textarea::make('note')
->label(__('admin.refresh_tokens.fields.note'))
->rows(2)
->maxLength(255),
])
->requiresConfirmation()
->action(function (RefreshToken $record, array $data): void {
$note = $data['note'] ?? null;
$record->revoke(
$data['reason'] ?? 'manual',
auth()->id(),
request(),
$note ? ['note' => $note] : []
);
}),
])
->bulkActions([]);
}
public static function getRelations(): array
{
return [
AuditsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRefreshTokens::route('/'),
'view' => Pages\ViewRefreshToken::route('/{record}'),
];
}
}