mirror of
https://github.com/pelican-dev/panel.git
synced 2025-09-08 18:38:37 +02:00
Add SSH Keys to Profile (#1478)
This commit is contained in:
parent
23ddded61e
commit
556551b4f3
@ -8,7 +8,9 @@ use App\Facades\Activity;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\ApiKey;
|
||||
use App\Models\User;
|
||||
use App\Models\UserSSHKey;
|
||||
use App\Services\Helpers\LanguageService;
|
||||
use App\Services\Ssh\KeyCreationService;
|
||||
use App\Services\Users\ToggleTwoFactorService;
|
||||
use App\Services\Users\TwoFactorSetupService;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
@ -19,6 +21,7 @@ use chillerlan\QRCode\Common\Version;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use Filament\Actions\Action as HeaderAction;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Actions;
|
||||
@ -276,7 +279,7 @@ class EditProfile extends BaseEditProfile
|
||||
->icon('tabler-key')
|
||||
->schema([
|
||||
Grid::make('name')->columns(5)->schema([
|
||||
Section::make(trans('profile.create_key'))->columnSpan(3)->schema([
|
||||
Section::make(trans('profile.create_api_key'))->columnSpan(3)->schema([
|
||||
TextInput::make('description')
|
||||
->label(trans('profile.description'))
|
||||
->live(),
|
||||
@ -288,9 +291,9 @@ class EditProfile extends BaseEditProfile
|
||||
->helperText(trans('profile.allowed_ips_help'))
|
||||
->columnSpanFull(),
|
||||
])->headerActions([
|
||||
Action::make('Create')
|
||||
Action::make('create')
|
||||
->label(trans('filament-actions::create.single.modal.actions.create.label'))
|
||||
->disabled(fn (Get $get) => $get('description') === null)
|
||||
->disabled(fn (Get $get) => empty($get('description')))
|
||||
->successRedirectUrl(self::getUrl(['tab' => '-api-keys-tab'], panel: 'app'))
|
||||
->action(function (Get $get, Action $action, User $user) {
|
||||
$token = $user->createToken(
|
||||
@ -306,7 +309,7 @@ class EditProfile extends BaseEditProfile
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title(trans('profile.key_created'))
|
||||
->title(trans('profile.api_key_created'))
|
||||
->body($token->accessToken->identifier . $token->plainTextToken)
|
||||
->persistent()
|
||||
->success()
|
||||
@ -315,17 +318,28 @@ class EditProfile extends BaseEditProfile
|
||||
$action->success();
|
||||
}),
|
||||
]),
|
||||
Section::make(trans('profile.keys'))->label(trans('profile.keys'))->columnSpan(2)->schema([
|
||||
Repeater::make('keys')
|
||||
->label('')
|
||||
Section::make(trans('profile.api_keys'))->columnSpan(2)->schema([
|
||||
Repeater::make('api_keys')
|
||||
->hiddenLabel()
|
||||
->relationship('apiKeys')
|
||||
->addable(false)
|
||||
->itemLabel(fn ($state) => $state['identifier'])
|
||||
->deleteAction(function (Action $action) {
|
||||
$action->requiresConfirmation()->action(function (array $arguments, Repeater $component) {
|
||||
$action->requiresConfirmation()->action(function (array $arguments, Repeater $component, User $user) {
|
||||
$items = $component->getState();
|
||||
$key = $items[$arguments['item']];
|
||||
ApiKey::find($key['id'] ?? null)?->delete();
|
||||
|
||||
$apiKey = ApiKey::find($key['id'] ?? null);
|
||||
if ($apiKey->exists()) {
|
||||
$apiKey->delete();
|
||||
|
||||
Activity::event('user:api-key.delete')
|
||||
->actor($user)
|
||||
->subject($user)
|
||||
->subject($apiKey)
|
||||
->property('identifier', $apiKey->identifier)
|
||||
->log();
|
||||
}
|
||||
|
||||
unset($items[$arguments['item']]);
|
||||
|
||||
@ -335,7 +349,8 @@ class EditProfile extends BaseEditProfile
|
||||
});
|
||||
})
|
||||
->schema(fn () => [
|
||||
Placeholder::make('adf')->label(fn (ApiKey $key) => $key->memo),
|
||||
Placeholder::make('memo')
|
||||
->label(fn (ApiKey $key) => $key->memo),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
@ -343,7 +358,87 @@ class EditProfile extends BaseEditProfile
|
||||
|
||||
Tab::make(trans('profile.tabs.ssh_keys'))
|
||||
->icon('tabler-lock-code')
|
||||
->hidden(),
|
||||
->schema([
|
||||
Grid::make('name')->columns(5)->schema([
|
||||
Section::make(trans('profile.create_ssh_key'))->columnSpan(3)->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('profile.name'))
|
||||
->live(),
|
||||
Textarea::make('public_key')
|
||||
->label(trans('profile.public_key'))
|
||||
->autosize()
|
||||
->live(),
|
||||
])->headerActions([
|
||||
Action::make('create')
|
||||
->label(trans('filament-actions::create.single.modal.actions.create.label'))
|
||||
->disabled(fn (Get $get) => empty($get('name')) || empty($get('public_key')))
|
||||
->successRedirectUrl(self::getUrl(['tab' => '-ssh-keys-tab'], panel: 'app'))
|
||||
->action(function (Get $get, Action $action, User $user, KeyCreationService $service) {
|
||||
try {
|
||||
$sshKey = $service->handle($user, $get('name'), $get('public_key'));
|
||||
|
||||
Activity::event('user:ssh-key.create')
|
||||
->actor($user)
|
||||
->subject($user)
|
||||
->subject($sshKey)
|
||||
->property('fingerprint', $sshKey->fingerprint)
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title(trans('profile.ssh_key_created'))
|
||||
->body("SHA256:{$sshKey->fingerprint}")
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$action->success();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('profile.could_not_create_ssh_key'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
$action->failure();
|
||||
}
|
||||
}),
|
||||
]),
|
||||
Section::make(trans('profile.ssh_keys'))->columnSpan(2)->schema([
|
||||
Repeater::make('ssh_keys')
|
||||
->hiddenLabel()
|
||||
->relationship('sshKeys')
|
||||
->addable(false)
|
||||
->itemLabel(fn ($state) => $state['name'])
|
||||
->deleteAction(function (Action $action) {
|
||||
$action->requiresConfirmation()->action(function (array $arguments, Repeater $component, User $user) {
|
||||
$items = $component->getState();
|
||||
$key = $items[$arguments['item']];
|
||||
|
||||
$sshKey = UserSSHKey::find($key['id'] ?? null);
|
||||
if ($sshKey->exists()) {
|
||||
$sshKey->delete();
|
||||
|
||||
Activity::event('user:ssh-key.delete')
|
||||
->actor($user)
|
||||
->subject($user)
|
||||
->subject($sshKey)
|
||||
->property('fingerprint', $sshKey->fingerprint)
|
||||
->log();
|
||||
}
|
||||
|
||||
unset($items[$arguments['item']]);
|
||||
|
||||
$component->state($items);
|
||||
|
||||
$component->callAfterStateUpdated();
|
||||
});
|
||||
})
|
||||
->schema(fn () => [
|
||||
Placeholder::make('fingerprint')
|
||||
->label(fn (UserSSHKey $key) => "SHA256:{$key->fingerprint}"),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
Tab::make(trans('profile.tabs.activity'))
|
||||
->icon('tabler-history')
|
||||
|
48
app/Services/Ssh/KeyCreationService.php
Normal file
48
app/Services/Ssh/KeyCreationService.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Ssh;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserSSHKey;
|
||||
use Exception;
|
||||
use phpseclib3\Crypt\DSA;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use phpseclib3\Crypt\RSA;
|
||||
use phpseclib3\Exception\NoKeyLoadedException;
|
||||
|
||||
class KeyCreationService
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle(User $user, string $name, string $publicKey): UserSSHKey
|
||||
{
|
||||
try {
|
||||
$key = PublicKeyLoader::loadPublicKey($publicKey);
|
||||
} catch (NoKeyLoadedException) {
|
||||
throw new Exception('The public key provided is not valid');
|
||||
}
|
||||
|
||||
if ($key instanceof DSA) {
|
||||
throw new Exception('DSA keys are not supported');
|
||||
}
|
||||
|
||||
if ($key instanceof RSA && $key->getLength() < 2048) {
|
||||
throw new Exception('RSA keys must be at least 2048 bytes in length');
|
||||
}
|
||||
|
||||
$fingerprint = $key->getFingerprint('sha256');
|
||||
if ($user->sshKeys()->where('fingerprint', $fingerprint)->exists()) {
|
||||
throw new Exception('The public key provided already exists on your account');
|
||||
}
|
||||
|
||||
/** @var UserSSHKey $sshKey */
|
||||
$sshKey = $user->sshKeys()->create([
|
||||
'name' => $name,
|
||||
'public_key' => $key->toString('PKCS8'),
|
||||
'fingerprint' => $fingerprint,
|
||||
]);
|
||||
|
||||
return $sshKey;
|
||||
}
|
||||
}
|
@ -33,12 +33,18 @@ return [
|
||||
'backup_codes' => 'Backup Codes',
|
||||
'disable_2fa' => 'Disable 2FA',
|
||||
'disable_2fa_help' => 'Enter your current 2FA code to disable Two Factor Authentication',
|
||||
'keys' => 'Keys',
|
||||
'create_key' => 'Create API Key',
|
||||
'key_created' => 'Key Created',
|
||||
'api_keys' => 'API Keys',
|
||||
'create_api_key' => 'Create API Key',
|
||||
'api_key_created' => 'API Key Created',
|
||||
'description' => 'Description',
|
||||
'allowed_ips' => 'Allowed IPs',
|
||||
'allowed_ips_help' => 'Press enter to add a new IP address or leave blank to allow any IP address',
|
||||
'ssh_keys' => 'SSH Keys',
|
||||
'create_ssh_key' => 'Create SSH Key',
|
||||
'ssh_key_created' => 'SSH Key Created',
|
||||
'name' => 'Name',
|
||||
'public_key' => 'Public Key',
|
||||
'could_not_create_ssh_key' => 'Could not create ssh key',
|
||||
'dashboard' => 'Dashboard',
|
||||
'dashboard_layout' => 'Dashboard Layout',
|
||||
'console' => 'Console',
|
||||
|
Loading…
x
Reference in New Issue
Block a user