Feat: Add modular OIDC app creation with logo support
- Introduced new components and services for OIDC app creation, enabling dynamic handling of app types, URIs, and user roles. - Added support for uploading and managing logos for connected apps. - Implemented `ClientCreationFactory` and related factories for modular OIDC client generation. - Updated UI to enhance app creation flow with post-creation credential display and improved validations. - Refactored backend with `OidcService` for streamlined OIDC app creation and management.
This commit is contained in:
parent
fb7b1e3721
commit
0b77f69818
18
app/Data/Application/ClientCredentialsData.php
Normal file
18
app/Data/Application/ClientCredentialsData.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Application;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
final class ClientCredentialsData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $clientId,
|
||||
public string $grantType,
|
||||
public ?string $clientSecret = null,
|
||||
public ?array $redirectUris = null,
|
||||
public ?array $scopes = null,
|
||||
) {}
|
||||
}
|
||||
16
app/Data/Application/CreatedApplicationData.php
Normal file
16
app/Data/Application/CreatedApplicationData.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Application;
|
||||
|
||||
use App\Data\ConnectedApp\ConnectedAppData;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class CreatedApplicationData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public ConnectedAppData $application,
|
||||
public ClientCredentialsData $clientCredentials,
|
||||
) {}
|
||||
}
|
||||
38
app/Data/Application/StoreOIDCAppData.php
Normal file
38
app/Data/Application/StoreOIDCAppData.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Application;
|
||||
|
||||
use App\Enums\{ApplicationTypeEnum, UserAccessTypeEnum};
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class StoreOIDCAppData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public ApplicationTypeEnum $type,
|
||||
public string $name,
|
||||
public UserAccessTypeEnum $userAccessType,
|
||||
/** @var array<string> */
|
||||
public array $signInUris = [],
|
||||
/** @var array<string> */
|
||||
public array $signOutUris = [],
|
||||
public ?TemporaryUploadedFile $logo = null,
|
||||
/** @var array<int> */
|
||||
public array $roleIds = [],
|
||||
) {}
|
||||
|
||||
public static function fromArray(ApplicationTypeEnum $applicationType, array $value): StoreOIDCAppData
|
||||
{
|
||||
return new self(
|
||||
type: $applicationType,
|
||||
name: $value['name'],
|
||||
userAccessType: UserAccessTypeEnum::from($value['userAccessOption']),
|
||||
signInUris: $value['signInUris'] ?? [],
|
||||
signOutUris: $value['signOutUris'] ?? [],
|
||||
logo: $value['logo'] ?? null,
|
||||
roleIds: $value['roleIds'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -13,10 +13,11 @@ class ConnectAppRequest extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public int $connectionProviderId,
|
||||
public int $connectionProtocolId,
|
||||
public ?int $connectionProviderId = null,
|
||||
public ?string $slug = null,
|
||||
public ?int $connectionStatusId = null,
|
||||
public ?array $settings = null,
|
||||
public ?string $logo = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -20,5 +20,6 @@ public function __construct(
|
||||
public int $statusId,
|
||||
public ?string $slug = null,
|
||||
public ?array $settings = null,
|
||||
public ?string $logo = null
|
||||
) {}
|
||||
}
|
||||
|
||||
25
app/Services/ApplicationLogoUploader.php
Normal file
25
app/Services/ApplicationLogoUploader.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
final readonly class ApplicationLogoUploader
|
||||
{
|
||||
public function store(?UploadedFile $logo): ?string
|
||||
{
|
||||
return $logo?->store('application-logos', 'public');
|
||||
}
|
||||
|
||||
public function delete(?string $path): bool
|
||||
{
|
||||
if (null !== $path) {
|
||||
return Storage::disk('public')->delete($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Applications\OIDC\Factory;
|
||||
|
||||
use App\Data\Application\{ClientCredentialsData, StoreOIDCAppData};
|
||||
use App\Enums\ApplicationTypeEnum;
|
||||
use Exception;
|
||||
use Laravel\Passport\ClientRepository;
|
||||
|
||||
readonly class AuthorizationCodeClientFactory implements ClientCreationContract
|
||||
{
|
||||
public function __construct(
|
||||
private ClientRepository $client,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(
|
||||
StoreOIDCAppData $data
|
||||
): ClientCredentialsData {
|
||||
$client = $this->client->createAuthorizationCodeGrantClient(
|
||||
name: $data->name,
|
||||
redirectUris: $data->signInUris,
|
||||
confidential: ApplicationTypeEnum::Web === $data->type,
|
||||
);
|
||||
|
||||
return new ClientCredentialsData(
|
||||
clientId: $client->id,
|
||||
grantType: 'authorization_code',
|
||||
clientSecret: $client->plainSecret,
|
||||
redirectUris: $client->getAttribute('redirect_uris'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Applications\OIDC\Factory;
|
||||
|
||||
use App\Data\Application\{ClientCredentialsData, StoreOIDCAppData};
|
||||
|
||||
interface ClientCreationContract
|
||||
{
|
||||
public function create(StoreOIDCAppData $data): ClientCredentialsData;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Applications\OIDC\Factory;
|
||||
|
||||
use App\Enums\ApplicationTypeEnum;
|
||||
|
||||
class ClientCreationFactory
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientCredentialsClientFactory $clientCredentialsClient,
|
||||
private readonly AuthorizationCodeClientFactory $authorizationCodeClient,
|
||||
) {}
|
||||
|
||||
public function for(ApplicationTypeEnum $applicationType): ClientCreationContract
|
||||
{
|
||||
return match ($applicationType) {
|
||||
ApplicationTypeEnum::Web, ApplicationTypeEnum::SPA => $this->authorizationCodeClient,
|
||||
ApplicationTypeEnum::Machine => $this->clientCredentialsClient,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Applications\OIDC\Factory;
|
||||
|
||||
use App\Data\Application\{ClientCredentialsData, StoreOIDCAppData};
|
||||
use Laravel\Passport\ClientRepository;
|
||||
|
||||
class ClientCredentialsClientFactory implements ClientCreationContract
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientRepository $client,
|
||||
) {}
|
||||
|
||||
public function create(StoreOIDCAppData $data): ClientCredentialsData
|
||||
{
|
||||
$client = $this->client->createClientCredentialsGrantClient(
|
||||
name: $data->name,
|
||||
);
|
||||
|
||||
return new ClientCredentialsData(
|
||||
clientId: $client->id,
|
||||
grantType: 'client_credentials',
|
||||
clientSecret: $client->plainSecret,
|
||||
redirectUris: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
59
app/Services/Applications/OIDC/OidcService.php
Normal file
59
app/Services/Applications/OIDC/OidcService.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Applications\OIDC;
|
||||
|
||||
use App\Data\Application\{CreatedApplicationData, StoreOIDCAppData};
|
||||
use App\Data\ConnectedApp\ConnectAppRequest;
|
||||
use App\Enums\{ConnectionProtocolEnum, ConnectionStatusEnum};
|
||||
use App\Models\{ConnectionProtocol, ConnectionStatus};
|
||||
use App\Services\{ApplicationLogoUploader, ConnectedAppService};
|
||||
use App\Services\Applications\OIDC\Factory\ClientCreationFactory;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
readonly class OidcService
|
||||
{
|
||||
public function __construct(
|
||||
private ClientCreationFactory $clientCreationFactory,
|
||||
private ApplicationLogoUploader $logoUploader,
|
||||
private ConnectedAppService $connectedAppService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function createApp(StoreOIDCAppData $data): CreatedApplicationData
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$logo = $this->logoUploader->store($data->logo);
|
||||
$appData = $this->connectedAppService->create(
|
||||
new ConnectAppRequest(
|
||||
name: $data->name,
|
||||
connectionProtocolId: ConnectionProtocol::query()->where('name', ConnectionProtocolEnum::OIDC->value)->first()->id,
|
||||
connectionProviderId: 0,
|
||||
slug: Str::slug($data->name),
|
||||
connectionStatusId: ConnectionStatus::query()->where('name', ConnectionStatusEnum::Connected->value)->first()->id,
|
||||
logo: $logo,
|
||||
)
|
||||
);
|
||||
|
||||
$clientData = $this->clientCreationFactory->for($data->type)->create($data);
|
||||
DB::commit();
|
||||
|
||||
// remove sensitive data
|
||||
$appData->settings = null;
|
||||
|
||||
return new CreatedApplicationData(
|
||||
application: $appData,
|
||||
clientCredentials: $clientData,
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,15 +26,17 @@ public function __construct(
|
||||
*
|
||||
* @throws Throwable when an error occurs during the creation.
|
||||
*/
|
||||
public function create(ConnectAppRequest $data): void
|
||||
public function create(ConnectAppRequest $data): ConnectedAppData
|
||||
{
|
||||
DB::transaction(
|
||||
function () use ($data): void {
|
||||
return DB::transaction(
|
||||
function () use ($data): ConnectedAppData {
|
||||
$connectedApp = ConnectedApp::query()->create(
|
||||
$data->toArray()
|
||||
);
|
||||
|
||||
$this->permissionService->add(ConnectedAppData::from($connectedApp), ConnectedAppPermissonEnum::Use);
|
||||
|
||||
return ConnectedAppData::from($connectedApp);
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -95,7 +97,7 @@ public function getConnectedApp(int $id): ConnectedAppData
|
||||
if ($id <= 0) {
|
||||
throw new InvalidArgumentException('Invalid id');
|
||||
}
|
||||
$data = ConnectedApp::with('provider:id', 'protocol:id', 'status:id')
|
||||
$data = ConnectedApp::with(['provider:id', 'protocol:id', 'status:id'])
|
||||
->findOrFail($id);
|
||||
|
||||
return ConnectedAppData::from($data);
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class() extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('connected_apps', function (Blueprint $table): void {
|
||||
$table->string('logo')->nullable()->after('name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('connected_apps', function (Blueprint $table): void {
|
||||
$table->dropColumn('logo');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -10,7 +10,7 @@
|
||||
<div>
|
||||
<x-shared.card title="Connected Apps" icon="lucide-grid-2x2-plus" class="p-0">
|
||||
<x-slot:actions>
|
||||
<x-shared.button :link="route('apps.create')">
|
||||
<x-shared.button>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<x-lucide-plus class="w-5 h-5"/>
|
||||
{{ __('Connect App') }}
|
||||
|
||||
@ -1,26 +1,19 @@
|
||||
<?php
|
||||
|
||||
use App\Concerns\HandlesOperations;
|
||||
use App\Data\ConnectedApp\{ConnectAppRequest, ConnectionStatusData};
|
||||
use App\Enums\{ApplicationTypeEnum, ConnectionProtocolEnum, ConnectionStatusEnum, UserAccessTypeEnum};
|
||||
use App\Livewire\Forms\StoreOIDCAppForm;
|
||||
use App\Data\Application\StoreOIDCAppData;
|
||||
use App\Data\ConnectedApp\ConnectedAppData;
|
||||
use App\Enums\{ApplicationTypeEnum, ConnectionProtocolEnum, UserAccessTypeEnum};
|
||||
use App\Livewire\Concerns\Choices\{ChoiceField, WithSearchableChoices};
|
||||
use App\Livewire\Concerns\WithRepeaterFields;
|
||||
use App\Models\{ConnectionProtocol, ConnectionProvider, ConnectionStatus};
|
||||
use App\Services\{ConnectedAppService,
|
||||
ConnectionProtocolService,
|
||||
ConnectionProviderService,
|
||||
ConnectionStatusService,
|
||||
RoleService
|
||||
};
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\{Computed, Title, Url};
|
||||
use App\Livewire\Forms\StoreOIDCAppForm;
|
||||
use App\Services\{Applications\OIDC\OidcService, RoleService};
|
||||
use Livewire\Attributes\{Url};
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Mary\Traits\Toast;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
|
||||
new #[Title("Create a service")]
|
||||
|
||||
new
|
||||
class extends Component {
|
||||
use HandlesOperations, WithRepeaterFields, WithSearchableChoices, WithFileUploads;
|
||||
|
||||
@ -30,25 +23,49 @@ class extends Component {
|
||||
#[Url]
|
||||
public ?string $type = null;
|
||||
|
||||
// For creation states
|
||||
public bool $created = false;
|
||||
public ?string $clientId = null;
|
||||
public ?string $clientSecret = null;
|
||||
public ?ConnectedAppData $app = null;
|
||||
|
||||
public StoreOIDCAppForm $form;
|
||||
public ConnectionProtocolEnum $connectionProtocol = ConnectionProtocolEnum::OIDC;
|
||||
public ?ApplicationTypeEnum $applicationType = null;
|
||||
public ApplicationTypeEnum $applicationType;
|
||||
private RoleService $roleService;
|
||||
private OidcService $oidcService;
|
||||
|
||||
public function boot(RoleService $roleService): void
|
||||
|
||||
public function boot(RoleService $roleService, OidcService $oidcService): void
|
||||
{
|
||||
$this->roleService = $roleService;
|
||||
$this->oidcService = $oidcService;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->applicationType = ApplicationTypeEnum::tryFrom($this->type);
|
||||
$this->applicationType = ApplicationTypeEnum::from($this->type);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->view()->title($this->title());
|
||||
}
|
||||
|
||||
public function save(bool $goBack = true): void
|
||||
{
|
||||
$this->form->validate();
|
||||
ds($this->form->pull());
|
||||
$this->attempt(
|
||||
function () {
|
||||
$data = StoreOIDCAppData::fromArray($this->applicationType, $this->form->pull());
|
||||
$appdata = $this->oidcService->createApp($data);
|
||||
$this->created = true;
|
||||
$this->clientId = $appdata->clientCredentials->clientId;
|
||||
$this->clientSecret = $appdata->clientCredentials->clientSecret;
|
||||
$this->app = $appdata->application;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +81,6 @@ public function title(): string
|
||||
ApplicationTypeEnum::Web => "Web Application ",
|
||||
ApplicationTypeEnum::SPA => "Single Page Application ",
|
||||
ApplicationTypeEnum::Machine => "Microservice Application ",
|
||||
null => ""
|
||||
};
|
||||
$title .= "Integration";
|
||||
return $title;
|
||||
@ -99,98 +115,143 @@ protected function choiceFields(): array
|
||||
?>
|
||||
|
||||
<div>
|
||||
<x-shared.card :title="$this->title()" icon="lucide-package-plus">
|
||||
<x-slot:actions>
|
||||
<x-mary-button wire:click="goBack" icon="lucide.corner-up-left" class="btn-ghost">Go Back
|
||||
</x-mary-button>
|
||||
</x-slot:actions>
|
||||
<x-mary-form wire:submit="save" class="p-4 gap-y-8">
|
||||
<!-- General settings start -->
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">General Settings</h2>
|
||||
@if($created && $clientId && $clientSecret)
|
||||
<x-shared.card
|
||||
title="Application Created"
|
||||
icon="lucide-check-circle"
|
||||
>
|
||||
<div class="flex w-full justify-center items-center pt-6">
|
||||
<article class="mx-auto flex items-center gap-4">
|
||||
<x-mary-avatar :image="$app->logo" alt="App logo" class="w-14! rounded-lg! p-2"/>
|
||||
<h1 class="text-2xl font-medium">{{$app->name}}</h1>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-mary-input required label="App Name" wire:model="form.name" placeholder="Keka, MS 365 etc."
|
||||
hint="A basic name that matches the actual application"/>
|
||||
<x-mary-file wire:model="form.logo" label="Logo" hint="Optional"
|
||||
accept="application/jpg, application/png, image/jpeg, image/png"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- General settings end -->
|
||||
<div class="space-y-6 p-6">
|
||||
|
||||
<!-- Sign-in uris start -->
|
||||
<hr>
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">Sign In Redirect URIs</h2>
|
||||
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
||||
{{config('app.name')}} sends response and ID Token back to these URIs.
|
||||
</p>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-shared.repeater-input model="form.signInUris" :items="$form->signInUris"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sign-in uris end -->
|
||||
<x-mary-alert
|
||||
title="Save these credentials"
|
||||
description="The client secret will only be displayed once."
|
||||
class="alert-warning alert-soft text-yellow-700"
|
||||
/>
|
||||
|
||||
<!-- Sign-out uris start -->
|
||||
<hr>
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">Sign out Redirect URIs</h2>
|
||||
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
||||
{{config('app.name')}} sends back the user to these URIs after logout.
|
||||
</p>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-shared.repeater-input model="form.signOutUris" :items="$form->signOutUris"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sign-out uris end -->
|
||||
<x-mary-input
|
||||
label="Client ID"
|
||||
:value="$clientId"
|
||||
readonly
|
||||
/>
|
||||
|
||||
<!-- Access start -->
|
||||
<hr>
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">User Access</h2>
|
||||
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
||||
Choose whether to allow all users be able to connect this app or selected ones.
|
||||
</p>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-mary-radio
|
||||
:options="UserAccessTypeEnum::asOptions()"
|
||||
option-value="value"
|
||||
wire:model.change.live="form.userAccessOption"
|
||||
/>
|
||||
<div class=""
|
||||
x-show="$wire.form.userAccessOption === '{{UserAccessTypeEnum::Role->value}}'"
|
||||
x-cloak
|
||||
<x-mary-input
|
||||
label="Client Secret"
|
||||
:value="$clientSecret"
|
||||
readonly
|
||||
/>
|
||||
|
||||
<div class="flex justify-end gap-4">
|
||||
<x-mary-button
|
||||
wire:click="goBack"
|
||||
class="btn-primary"
|
||||
>
|
||||
<x-shared.choices
|
||||
wire:model.live="form.roleIds"
|
||||
:options="$choicesSearchable['roles'] ?? []"
|
||||
search-function="searchRoles"
|
||||
select-all-action="selectAllRoles"
|
||||
clear-action="clearRoles"
|
||||
label="Roles"
|
||||
/>
|
||||
Done
|
||||
</x-mary-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</x-shared.card>
|
||||
@else
|
||||
<x-shared.card :title="$this->title()" icon="lucide-package-plus">
|
||||
<x-slot:actions>
|
||||
<x-mary-button wire:click="goBack" icon="lucide.corner-up-left" class="btn-ghost">Go Back
|
||||
</x-mary-button>
|
||||
</x-slot:actions>
|
||||
<x-mary-form wire:submit="save" class="p-4 gap-y-8">
|
||||
<!-- General settings start -->
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">General Settings</h2>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-mary-input required label="App Name" wire:model="form.name"
|
||||
placeholder="Keka, MS 365 etc."
|
||||
hint="A basic name that matches the actual application"/>
|
||||
<x-mary-file wire:model="form.logo" label="Logo" hint="Optional"
|
||||
accept="application/jpg, application/png, image/jpeg, image/png"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- General settings end -->
|
||||
|
||||
</div>
|
||||
<!-- Access end -->
|
||||
<!-- Sign-in uris start -->
|
||||
<hr>
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">Sign In Redirect URIs</h2>
|
||||
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
||||
{{config('app.name')}} sends response and ID Token back to these URIs.
|
||||
</p>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-shared.repeater-input model="form.signInUris" :items="$form->signInUris"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sign-in uris end -->
|
||||
|
||||
<div class="flex gap-x-4 font-medium justify-end">
|
||||
<x-mary-button wire:click.prevent="goBack" spinner="goBack">Cancel</x-mary-button>
|
||||
<x-mary-button wire:click.prevent="save(false)">Save & Add Another</x-mary-button>
|
||||
<x-mary-button type="submit"
|
||||
class="btn-primary">
|
||||
Save
|
||||
</x-mary-button>
|
||||
</div>
|
||||
</x-mary-form>
|
||||
</x-shared.card>
|
||||
<!-- Sign-out uris start -->
|
||||
<hr>
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">Sign out Redirect URIs</h2>
|
||||
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
||||
{{config('app.name')}} sends back the user to these URIs after logout.
|
||||
</p>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-shared.repeater-input model="form.signOutUris" :items="$form->signOutUris"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sign-out uris end -->
|
||||
|
||||
<!-- Access start -->
|
||||
<hr>
|
||||
<div class="flex gap-4 flex-col w-full md:flex-row">
|
||||
<article class="w-full md:w-2/5">
|
||||
<h2 class="font-bold">User Access</h2>
|
||||
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
||||
Choose whether to allow all users be able to connect this app or selected ones.
|
||||
</p>
|
||||
</article>
|
||||
<div class="w-full md:w-3/5">
|
||||
<x-mary-radio
|
||||
:options="UserAccessTypeEnum::asOptions()"
|
||||
option-value="value"
|
||||
wire:model.change.live="form.userAccessOption"
|
||||
/>
|
||||
<div class=""
|
||||
x-show="$wire.form.userAccessOption === '{{UserAccessTypeEnum::Role->value}}'"
|
||||
x-cloak
|
||||
>
|
||||
<x-shared.choices
|
||||
wire:model.live="form.roleIds"
|
||||
:options="$choicesSearchable['roles'] ?? []"
|
||||
search-function="searchRoles"
|
||||
select-all-action="selectAllRoles"
|
||||
clear-action="clearRoles"
|
||||
label="Roles"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Access end -->
|
||||
|
||||
<div class="flex gap-x-4 font-medium justify-end">
|
||||
<x-mary-button wire:click.prevent="goBack" spinner="goBack">Cancel</x-mary-button>
|
||||
<x-mary-button wire:click.prevent="save(false)">Save & Add Another</x-mary-button>
|
||||
<x-mary-button type="submit"
|
||||
class="btn-primary">
|
||||
Save
|
||||
</x-mary-button>
|
||||
</div>
|
||||
</x-mary-form>
|
||||
</x-shared.card>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user