Files
panel-pelican-dev/app/Filament/Resources/UserResource/Pages/EditProfile.php

307 lines
18 KiB
PHP
Raw Normal View History

2024-04-08 00:33:00 -04:00
<?php
namespace App\Filament\Resources\UserResource\Pages;
2024-05-31 01:26:28 -04:00
use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
2024-04-20 21:01:41 -04:00
use App\Facades\Activity;
2024-04-08 00:33:00 -04:00
use App\Models\ActivityLog;
2024-04-20 21:01:41 -04:00
use App\Models\ApiKey;
2024-04-13 16:21:44 -04:00
use App\Models\User;
2024-05-31 01:26:28 -04:00
use App\Services\Users\ToggleTwoFactorService;
2024-04-18 03:51:25 -04:00
use App\Services\Users\TwoFactorSetupService;
use chillerlan\QRCode\Common\EccLevel;
use chillerlan\QRCode\Common\Version;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
2024-04-20 21:01:41 -04:00
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
2024-04-08 00:33:00 -04:00
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater;
2024-04-20 21:01:41 -04:00
use Filament\Forms\Components\Section;
2024-04-13 16:21:44 -04:00
use Filament\Forms\Components\Select;
2024-04-08 00:33:00 -04:00
use Filament\Forms\Components\Tabs;
2024-04-16 18:47:12 -04:00
use Filament\Forms\Components\TagsInput;
2024-04-08 00:33:00 -04:00
use Filament\Forms\Components\Tabs\Tab;
2024-06-01 12:49:36 -04:00
use Filament\Forms\Components\Textarea;
2024-04-08 00:33:00 -04:00
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
2024-05-31 01:26:28 -04:00
use Filament\Notifications\Notification;
2024-04-20 21:01:41 -04:00
use Illuminate\Database\Eloquent\Builder;
2024-04-08 00:33:00 -04:00
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\HtmlString;
use Illuminate\Validation\Rules\Password;
class EditProfile extends \Filament\Pages\Auth\EditProfile
{
protected function getForms(): array
{
return [
'form' => $this->form(
$this->makeForm()
->schema([
2024-04-20 20:22:17 -04:00
Tabs::make()->persistTabInQueryString()
->schema([
2024-04-21 01:00:48 -04:00
Tab::make('Account')
->label(trans('strings.account'))
2024-04-21 01:00:48 -04:00
->icon('tabler-user')
->schema([
TextInput::make('username')
->label(trans('strings.username'))
2024-04-21 01:00:48 -04:00
->disabled()
->readOnly()
->maxLength(191)
->unique(ignoreRecord: true)
->autofocus(),
TextInput::make('email')
->prefixIcon('tabler-mail')
->label(trans('strings.email'))
2024-04-21 01:00:48 -04:00
->email()
->required()
->maxLength(191)
->unique(ignoreRecord: true),
TextInput::make('password')
->label(trans('strings.password'))
2024-04-21 01:00:48 -04:00
->password()
->prefixIcon('tabler-password')
->revealable(filament()->arePasswordsRevealable())
->rule(Password::default())
->autocomplete('new-password')
->dehydrated(fn ($state): bool => filled($state))
->dehydrateStateUsing(fn ($state): string => Hash::make($state))
->live(debounce: 500)
->same('passwordConfirmation'),
TextInput::make('passwordConfirmation')
->label(trans('strings.password_confirmation'))
2024-04-21 01:00:48 -04:00
->password()
->prefixIcon('tabler-password-fingerprint')
->revealable(filament()->arePasswordsRevealable())
->required()
->visible(fn (Get $get): bool => filled($get('password')))
->dehydrated(false),
Select::make('language')
->label(trans('strings.language'))
2024-04-21 01:00:48 -04:00
->required()
->prefixIcon('tabler-flag')
->live()
->default('en')
->helperText(fn (User $user, $state) => new HtmlString($user->isLanguageTranslated($state) ? '' : "
2024-04-17 00:23:35 -04:00
Your language ($state) has not been translated yet!
But never fear, you can help fix that by
<a style='color: rgb(56, 189, 248)' href='https://crowdin.com/project/pelican-dev'>contributing directly here</a>.
")
2024-04-21 01:00:48 -04:00
)
->options(fn (User $user) => $user->getAvailableLanguages()),
]),
2024-04-18 03:51:25 -04:00
2024-04-21 01:00:48 -04:00
Tab::make('2FA')
->icon('tabler-shield-lock')
->schema(function () {
if ($this->getUser()->use_totp) {
return [
2024-05-31 01:26:28 -04:00
Placeholder::make('2fa-already-enabled')
->label('Two Factor Authentication is currently enabled!'),
2024-06-01 12:49:36 -04:00
Textarea::make('backup-tokens')
->hidden(fn () => !cache()->get("users.{$this->getUser()->id}.2fa.tokens"))
2024-06-01 12:49:36 -04:00
->rows(10)
->readOnly()
->formatStateUsing(fn () => cache()->get("users.{$this->getUser()->id}.2fa.tokens"))
->helperText('These will not be shown again!')
2024-05-31 01:42:02 -04:00
->label('Backup Tokens:'),
2024-05-31 01:26:28 -04:00
TextInput::make('2fa-disable-code')
->label('Disable 2FA')
->helperText('Enter your current 2FA code to disable Two Factor Authentication'),
2024-04-21 01:00:48 -04:00
];
}
$setupService = app(TwoFactorSetupService::class);
2024-05-31 01:26:28 -04:00
['image_url_data' => $url, 'secret' => $secret] = cache()->remember(
"users.{$this->getUser()->id}.2fa.state",
2024-05-31 01:42:02 -04:00
now()->addMinutes(5), fn () => $setupService->handle($this->getUser())
2024-05-31 01:26:28 -04:00
);
2024-04-21 01:00:48 -04:00
$options = new QROptions([
'svgLogo' => public_path('pelican.svg'),
'addLogoSpace' => true,
'logoSpaceWidth' => 13,
'logoSpaceHeight' => 13,
]);
// https://github.com/chillerlan/php-qrcode/blob/main/examples/svgWithLogo.php
// SVG logo options (see extended class)
$options->svgLogo = public_path('pelican.svg'); // logo from: https://github.com/simple-icons/simple-icons
$options->svgLogoScale = 0.05;
// $options->svgLogoCssClass = 'dark';
// QROptions
$options->version = Version::AUTO;
// $options->outputInterface = QRSvgWithLogo::class;
$options->outputBase64 = false;
$options->eccLevel = EccLevel::H; // ECC level H is necessary when using logos
$options->addQuietzone = true;
// $options->drawLightModules = true;
$options->connectPaths = true;
$options->drawCircularModules = true;
// $options->circleRadius = 0.45;
$options->svgDefs = '<linearGradient id="gradient" x1="100%" y2="100%">
2024-04-18 03:51:25 -04:00
<stop stop-color="#7dd4fc" offset="0"/>
<stop stop-color="#38bdf8" offset="0.5"/>
<stop stop-color="#0369a1" offset="1"/>
</linearGradient>
<style><![CDATA[
.dark{fill: url(#gradient);}
.light{fill: #000;}
]]></style>';
2024-04-21 01:00:48 -04:00
$image = (new QRCode($options))->render($url);
2024-04-18 03:51:25 -04:00
2024-04-21 01:00:48 -04:00
return [
Placeholder::make('qr')
->label('Scan QR Code')
->content(fn () => new HtmlString("
2024-06-01 12:49:19 -04:00
<div style='width: 300px; background-color: rgb(24, 24, 27);'>$image</div>
2024-04-18 03:51:25 -04:00
"))
2024-05-31 17:24:03 -04:00
->helperText('Setup Key: '. $secret),
2024-05-31 01:26:28 -04:00
TextInput::make('2facode')
2024-05-31 17:24:03 -04:00
->label('Code')
2024-05-31 01:26:28 -04:00
->requiredWith('2fapassword')
->helperText('Scan the QR code above using your two-step authentication app, then enter the code generated.'),
TextInput::make('2fapassword')
2024-05-31 17:24:03 -04:00
->label('Current Password')
2024-05-31 01:26:28 -04:00
->requiredWith('2facode')
->currentPassword()
->password()
->helperText('Enter your current password to verify.'),
2024-04-21 01:00:48 -04:00
];
}),
Tab::make('API Keys')
->icon('tabler-key')
->schema([
Grid::make('asdf')->columns(5)->schema([
Section::make('Create API Key')->columnSpan(3)->schema([
2024-05-31 16:39:23 -04:00
TextInput::make('description')->required(),
2024-04-21 01:00:48 -04:00
TagsInput::make('allowed_ips')
->splitKeys([',', ' ', 'Tab'])
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
->label('Whitelisted IP\'s')
->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
->columnSpanFull(),
])->headerActions([
Action::make('Create')
2024-05-11 21:56:12 -04:00
->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab']))
2024-04-21 01:00:48 -04:00
->action(function (Get $get, Action $action) {
$token = auth()->user()->createToken(
$get('description'),
$get('allowed_ips'),
);
Activity::event('user:api-key.create')
->subject($token->accessToken)
->property('identifier', $token->accessToken->identifier)
->log();
$action->success();
}),
]),
2024-05-31 16:39:23 -04:00
Section::make('Keys')->columnSpan(2)->schema([
2024-04-21 01:00:48 -04:00
Repeater::make('keys')
2024-05-31 16:39:23 -04:00
->label('')
2024-04-21 01:00:48 -04:00
->relationship('apiKeys')
->addable(false)
->itemLabel(fn ($state) => $state['identifier'])
->deleteAction(function (Action $action) {
$action->requiresConfirmation()->action(function (array $arguments, Repeater $component) {
$items = $component->getState();
$key = $items[$arguments['item']];
ApiKey::find($key['id'] ?? null)?->delete();
unset($items[$arguments['item']]);
$component->state($items);
$component->callAfterStateUpdated();
});
})
->schema(fn () => [
Placeholder::make('adf')->label(fn (ApiKey $key) => $key->memo),
]),
]),
2024-04-20 21:01:41 -04:00
]),
]),
2024-04-21 01:00:48 -04:00
Tab::make('SSH Keys')
->icon('tabler-lock-code')
->schema([
Placeholder::make('Coming soon!'),
]),
Tab::make('Activity')
->icon('tabler-history')
->schema([
Repeater::make('activity')
->deletable(false)
->addable(false)
->relationship(null, function (Builder $query) {
$query->orderBy('timestamp', 'desc');
})
->schema([
Placeholder::make('activity!')->label('')->content(fn (ActivityLog $log) => new HtmlString($log->htmlable())),
]),
]),
]),
2024-04-08 00:33:00 -04:00
])
->operation('edit')
->model($this->getUser())
->statePath('data')
2024-04-13 16:30:20 +03:00
->inlineLabel(!static::isSimple()),
2024-04-08 00:33:00 -04:00
),
];
}
2024-05-31 01:26:28 -04:00
protected function handleRecordUpdate($record, $data): \Illuminate\Database\Eloquent\Model
{
if ($token = $data['2facode'] ?? null) {
/** @var ToggleTwoFactorService $service */
$service = resolve(ToggleTwoFactorService::class);
$tokens = $service->handle($record, $token, true);
cache()->set("users.$record->id.2fa.tokens", implode("\n", $tokens), now()->addSeconds(15));
2024-06-01 13:03:46 -04:00
$this->redirectRoute('filament.admin.auth.profile', ['tab' => '-2fa-tab']);
2024-05-31 01:26:28 -04:00
}
if ($token = $data['2fa-disable-code'] ?? null) {
/** @var ToggleTwoFactorService $service */
$service = resolve(ToggleTwoFactorService::class);
$service->handle($record, $token, false);
cache()->forget("users.$record->id.2fa.state");
2024-05-31 01:26:28 -04:00
}
return parent::handleRecordUpdate($record, $data);
}
public function exception($e, $stopPropagation): void
{
if ($e instanceof TwoFactorAuthenticationTokenInvalid) {
Notification::make()
->title('Invalid 2FA Code')
->body($e->getMessage())
->color('danger')
->icon('tabler-2fa')
->danger()
->send();
$stopPropagation();
}
}
2024-04-08 00:33:00 -04:00
}