From 4e0aaedc868e6acf4869f7baeb8499dbe11af488 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Mon, 8 Apr 2024 00:59:55 -0400 Subject: [PATCH] Add api keys --- app/Filament/Resources/ApiKeyResource.php | 190 ++++++++++++++++++ .../ApiKeyResource/Pages/CreateApiKey.php | 12 ++ .../ApiKeyResource/Pages/ListApiKeys.php | 42 ++++ app/Models/ApiKey.php | 11 + 4 files changed, 255 insertions(+) create mode 100644 app/Filament/Resources/ApiKeyResource.php create mode 100644 app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php create mode 100644 app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php diff --git a/app/Filament/Resources/ApiKeyResource.php b/app/Filament/Resources/ApiKeyResource.php new file mode 100644 index 000000000..284195a9e --- /dev/null +++ b/app/Filament/Resources/ApiKeyResource.php @@ -0,0 +1,190 @@ + Tab::make('All Keys'), + 'application' => Tab::make('Application Keys') + ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)), + ]; + } + + public function getDefaultActiveTab(): string | int | null + { + return 'application'; + } + + public static function form(Form $form): Form + { + return $form + ->schema([ + Forms\Components\Hidden::make('identifier')->default(ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION)), + Forms\Components\Hidden::make('token')->default(encrypt(str_random(ApiKey::KEY_LENGTH))), + + Forms\Components\Select::make('user_id') + ->searchable() + ->preload() + ->relationship('user', 'username') + ->default(auth()->user()->id) + ->required(), + + Forms\Components\Select::make('key_type') + ->options(function (ApiKey $apiKey) { + $originalOptions = [ + ApiKey::TYPE_NONE => 'None', + ApiKey::TYPE_ACCOUNT => 'Account', + ApiKey::TYPE_APPLICATION => 'Application', + ApiKey::TYPE_DAEMON_USER => 'Daemon User', + ApiKey::TYPE_DAEMON_APPLICATION => 'Daemon Application', + ]; + + return collect($originalOptions) + ->filter(fn ($value, $key) => $key <= ApiKey::TYPE_APPLICATION || $apiKey->key_type === $key) + ->all(); + }) + ->selectablePlaceholder(false) + ->required() + ->default(ApiKey::TYPE_APPLICATION), + + Forms\Components\Fieldset::make('Permissions')->schema( + collect(ApiKey::RESOURCES)->map(fn ($resource) => + Forms\Components\ToggleButtons::make("r_$resource") + ->label(str($resource)->replace('_', ' ')->title()) + ->options([ + 0 => 'None', + 1 => 'Read', + // 2 => 'Write', + 3 => 'Read & Write', + ]) + ->icons([ + 0 => 'tabler-book-off', + 1 => 'tabler-book', + 2 => 'tabler-writing', + 3 => 'tabler-writing', + ]) + ->colors([ + 0 => 'primary', + 1 => 'warning', + 2 => 'danger', + 3 => 'danger', + ]) + ->inline() + ->required() + ->disabledOn('edit') + ->default(0), + )->all(), + ), + + Forms\Components\TagsInput::make('allowed_ips') + ->placeholder('Example: 127.0.0.1 or 192.168.1.1') + ->label('Whitelisted IPv4 Addresses') + ->helperText('Press enter to add a new IP address or leave blank to allow any IP address') + ->columnSpanFull() + ->hidden() + ->default(null), + + Forms\Components\Textarea::make('memo') + ->required() + ->label('Description') + ->helperText(' + Once you have assigned permissions and created this set of credentials you will be unable to come back and edit it. + If you need to make changes down the road you will need to create a new set of credentials. + ') + ->columnSpanFull(), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('user.username') + ->searchable() + ->hidden() + ->sortable(), + + Tables\Columns\TextColumn::make('key_type') + ->label('Type') + ->state(fn (ApiKey $key) => $key->type()) + ->hidden() + ->sortable(), + + Tables\Columns\TextColumn::make('key') + ->copyable() + ->state(fn (ApiKey $key) => $key->identifier . decrypt($key->token)), + + Tables\Columns\TextColumn::make('memo') + ->wrap() + ->limit(50), + + Tables\Columns\TextColumn::make('identifier') + ->hidden() + ->searchable(), + + Tables\Columns\TextColumn::make('last_used_at') + ->dateTime() + ->sortable() + ->toggleable(), + + Tables\Columns\TextColumn::make('expires_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListApiKeys::route('/'), + 'create' => Pages\CreateApiKey::route('/create'), + ]; + } +} diff --git a/app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php b/app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php new file mode 100644 index 000000000..2aca41e5c --- /dev/null +++ b/app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php @@ -0,0 +1,12 @@ + Tab::make('All Keys'), + 'application' => Tab::make('Application Keys') + ->modifyQueryUsing(fn (Builder $query) => + $query->where('key_type', ApiKey::TYPE_APPLICATION) + ), + 'account' => Tab::make('Account Keys') + ->modifyQueryUsing(fn (Builder $query) => + $query->where('key_type', ApiKey::TYPE_ACCOUNT) + ), + ]; + } + + public function getDefaultActiveTab(): string | int | null + { + return 'application'; + } +} diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index b3a1a1bcb..42d9fe4d7 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -83,6 +83,8 @@ class ApiKey extends Model */ public const KEY_LENGTH = 32; + public const RESOURCES = ['servers', 'nodes', 'allocations', 'users', 'eggs', 'database_hosts', 'server_databases']; + /** * The table associated with the model. */ @@ -92,12 +94,21 @@ class ApiKey extends Model * Fields that are mass assignable. */ protected $fillable = [ + 'user_id', + 'key_type', 'identifier', 'token', 'allowed_ips', 'memo', 'last_used_at', 'expires_at', + 'r_' . AdminAcl::RESOURCE_USERS, + 'r_' . AdminAcl::RESOURCE_ALLOCATIONS, + 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS, + 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES, + 'r_' . AdminAcl::RESOURCE_EGGS, + 'r_' . AdminAcl::RESOURCE_NODES, + 'r_' . AdminAcl::RESOURCE_SERVERS, ]; /**