diff --git a/app/Data/Application/ClientCredentialsData.php b/app/Data/Application/ClientCredentialsData.php new file mode 100644 index 0000000..1cea851 --- /dev/null +++ b/app/Data/Application/ClientCredentialsData.php @@ -0,0 +1,18 @@ + */ + public array $signInUris = [], + /** @var array */ + public array $signOutUris = [], + public ?TemporaryUploadedFile $logo = null, + /** @var array */ + 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'] ?? [], + ); + } +} diff --git a/app/Data/ConnectedApp/ConnectAppRequest.php b/app/Data/ConnectedApp/ConnectAppRequest.php index 518d8e7..0b38dcb 100644 --- a/app/Data/ConnectedApp/ConnectAppRequest.php +++ b/app/Data/ConnectedApp/ConnectAppRequest.php @@ -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, ) {} } diff --git a/app/Data/ConnectedApp/ConnectedAppData.php b/app/Data/ConnectedApp/ConnectedAppData.php index 658b532..a2a484a 100644 --- a/app/Data/ConnectedApp/ConnectedAppData.php +++ b/app/Data/ConnectedApp/ConnectedAppData.php @@ -20,5 +20,6 @@ public function __construct( public int $statusId, public ?string $slug = null, public ?array $settings = null, + public ?string $logo = null ) {} } diff --git a/app/Services/ApplicationLogoUploader.php b/app/Services/ApplicationLogoUploader.php new file mode 100644 index 0000000..621efed --- /dev/null +++ b/app/Services/ApplicationLogoUploader.php @@ -0,0 +1,25 @@ +store('application-logos', 'public'); + } + + public function delete(?string $path): bool + { + if (null !== $path) { + return Storage::disk('public')->delete($path); + } + + return false; + } +} diff --git a/app/Services/Applications/OIDC/Factory/AuthorizationCodeClientFactory.php b/app/Services/Applications/OIDC/Factory/AuthorizationCodeClientFactory.php new file mode 100644 index 0000000..7b7e2cc --- /dev/null +++ b/app/Services/Applications/OIDC/Factory/AuthorizationCodeClientFactory.php @@ -0,0 +1,37 @@ +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'), + ); + } +} diff --git a/app/Services/Applications/OIDC/Factory/ClientCreationContract.php b/app/Services/Applications/OIDC/Factory/ClientCreationContract.php new file mode 100644 index 0000000..0f4d3d3 --- /dev/null +++ b/app/Services/Applications/OIDC/Factory/ClientCreationContract.php @@ -0,0 +1,12 @@ + $this->authorizationCodeClient, + ApplicationTypeEnum::Machine => $this->clientCredentialsClient, + }; + } +} diff --git a/app/Services/Applications/OIDC/Factory/ClientCredentialsClientFactory.php b/app/Services/Applications/OIDC/Factory/ClientCredentialsClientFactory.php new file mode 100644 index 0000000..3e82340 --- /dev/null +++ b/app/Services/Applications/OIDC/Factory/ClientCredentialsClientFactory.php @@ -0,0 +1,29 @@ +client->createClientCredentialsGrantClient( + name: $data->name, + ); + + return new ClientCredentialsData( + clientId: $client->id, + grantType: 'client_credentials', + clientSecret: $client->plainSecret, + redirectUris: null, + ); + } +} diff --git a/app/Services/Applications/OIDC/OidcService.php b/app/Services/Applications/OIDC/OidcService.php new file mode 100644 index 0000000..3e8d72e --- /dev/null +++ b/app/Services/Applications/OIDC/OidcService.php @@ -0,0 +1,59 @@ +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; + } + } +} diff --git a/app/Services/ConnectedAppService.php b/app/Services/ConnectedAppService.php index 4c447c5..febdc94 100644 --- a/app/Services/ConnectedAppService.php +++ b/app/Services/ConnectedAppService.php @@ -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); diff --git a/database/migrations/2026_06_18_121251_add_logo_to_connected_apps.php b/database/migrations/2026_06_18_121251_add_logo_to_connected_apps.php new file mode 100644 index 0000000..55ef0c4 --- /dev/null +++ b/database/migrations/2026_06_18_121251_add_logo_to_connected_apps.php @@ -0,0 +1,24 @@ +string('logo')->nullable()->after('name'); + }); + } + + public function down(): void + { + Schema::table('connected_apps', function (Blueprint $table): void { + $table->dropColumn('logo'); + }); + } +}; diff --git a/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php b/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php index fb67f98..b7c3df5 100644 --- a/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php +++ b/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php @@ -10,7 +10,7 @@
- +
{{ __('Connect App') }} diff --git a/resources/views/pages/apps/oidc/⚡create.blade.php b/resources/views/pages/apps/oidc/⚡create.blade.php index 6a7cff0..a52ceaf 100644 --- a/resources/views/pages/apps/oidc/⚡create.blade.php +++ b/resources/views/pages/apps/oidc/⚡create.blade.php @@ -1,26 +1,19 @@ 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 ?>
- - - Go Back - - - - -
-
-

General Settings

+ @if($created && $clientId && $clientSecret) + +
+
+ +

{{$app->name}}

-
- - - -
- +
- -
-
-
-

Sign In Redirect URIs

-

- {{config('app.name')}} sends response and ID Token back to these URIs. -

-
-
- -
-
- + - -
-
-
-

Sign out Redirect URIs

-

- {{config('app.name')}} sends back the user to these URIs after logout. -

-
-
- -
-
- + - -
-
-
-

User Access

-

- Choose whether to allow all users be able to connect this app or selected ones. -

-
-
- -
+ +
+ - + Done + +
+ +
+ + @else + + + Go Back + + + + +
+
+

General Settings

+
+
+ + +
+ -
- + +
+
+
+

Sign In Redirect URIs

+

+ {{config('app.name')}} sends response and ID Token back to these URIs. +

+
+
+ +
+
+ -
- Cancel - Save & Add Another - - Save - -
- - + +
+
+
+

Sign out Redirect URIs

+

+ {{config('app.name')}} sends back the user to these URIs after logout. +

+
+
+ +
+
+ + + +
+
+
+

User Access

+

+ Choose whether to allow all users be able to connect this app or selected ones. +

+
+
+ +
+ +
+
+ +
+ + +
+ Cancel + Save & Add Another + + Save + +
+ + + @endif