diff --git a/app/Http/Controllers/Admin/SsoSettingController.php b/app/Http/Controllers/Admin/SsoSettingController.php new file mode 100644 index 0000000..43b5ec1 --- /dev/null +++ b/app/Http/Controllers/Admin/SsoSettingController.php @@ -0,0 +1,37 @@ +validate([ + 'base_url' => 'nullable|url|max:255', + 'client_id' => 'nullable|string|max:255', + 'client_secret' => 'nullable|string|max:255', + 'slug' => 'nullable|string|max:255', + ]); + + $sso = SsoSetting::get(); + + $sso->base_url = rtrim($request->base_url ?? '', '/') ?: null; + $sso->client_id = $request->client_id ?: null; + $sso->client_secret = $request->client_secret ?: null; + $sso->slug = $request->slug ?: null; + $sso->enabled = $request->boolean('enabled'); + $sso->save(); + + return redirect()->route('admin.sso.show')->with('success', 'SSO-Einstellungen gespeichert.'); + } +} diff --git a/app/Http/Controllers/Auth/SsoController.php b/app/Http/Controllers/Auth/SsoController.php new file mode 100644 index 0000000..5cffaaf --- /dev/null +++ b/app/Http/Controllers/Auth/SsoController.php @@ -0,0 +1,109 @@ +isConfigured()) { + return redirect()->route('login')->with('error', 'SSO ist nicht konfiguriert.'); + } + + $state = Str::random(40); + session(['sso_state' => $state]); + + $query = http_build_query([ + 'client_id' => $sso->client_id, + 'redirect_uri' => route('sso.callback'), + 'response_type' => 'code', + 'scope' => 'openid email profile', + 'state' => $state, + ]); + + return redirect($sso->authorizeUrl() . '?' . $query); + } + + public function callback(Request $request) + { + $sso = SsoSetting::get(); + + if (!$sso->isConfigured()) { + return redirect()->route('login')->with('error', 'SSO ist nicht konfiguriert.'); + } + + // State prüfen + if ($request->get('state') !== session('sso_state')) { + return redirect()->route('login')->with('error', 'Ungültige SSO-Anfrage (state mismatch).'); + } + session()->forget('sso_state'); + + if ($request->has('error')) { + return redirect()->route('login')->with('error', 'SSO-Anmeldung abgebrochen: ' . $request->get('error_description', $request->get('error'))); + } + + // Code gegen Token tauschen + $tokenResponse = Http::asForm()->post($sso->tokenUrl(), [ + 'grant_type' => 'authorization_code', + 'client_id' => $sso->client_id, + 'client_secret' => $sso->client_secret, + 'redirect_uri' => route('sso.callback'), + 'code' => $request->get('code'), + ]); + + if ($tokenResponse->failed()) { + return redirect()->route('login')->with('error', 'SSO-Token konnte nicht abgerufen werden.'); + } + + $accessToken = $tokenResponse->json('access_token'); + + // Userinfo abrufen + $userInfo = Http::withToken($accessToken)->get($sso->userInfoUrl()); + + if ($userInfo->failed()) { + return redirect()->route('login')->with('error', 'SSO-Benutzerinformationen konnten nicht abgerufen werden.'); + } + + $info = $userInfo->json(); + $providerId = $info['sub'] ?? null; + $email = $info['email'] ?? null; + $name = $info['name'] ?? $info['preferred_username'] ?? $email; + + if (!$providerId || !$email) { + return redirect()->route('login')->with('error', 'Unvollständige Benutzerinformationen vom SSO-Provider.'); + } + + // User suchen oder anlegen + $user = User::where('sso_provider_id', $providerId)->first() + ?? User::where('email', $email)->first(); + + if (!$user) { + $user = User::create([ + 'name' => $name, + 'email' => $email, + 'password' => bcrypt(Str::random(32)), + 'sso_provider_id' => $providerId, + 'is_sso_user' => true, + ]); + } elseif (!$user->sso_provider_id) { + // Bestehenden Account mit SSO verknüpfen + $user->sso_provider_id = $providerId; + $user->is_sso_user = true; + $user->save(); + } + + Auth::login($user, true); + + return redirect()->intended(route('user-nail-polishes.index')); + } +} diff --git a/app/Models/SsoSetting.php b/app/Models/SsoSetting.php new file mode 100644 index 0000000..d80e249 --- /dev/null +++ b/app/Models/SsoSetting.php @@ -0,0 +1,53 @@ + 'boolean', + ]; + } + + public static function get(): self + { + return static::firstOrCreate([]); + } + + public function isConfigured(): bool + { + return $this->enabled + && !empty($this->base_url) + && !empty($this->client_id) + && !empty($this->client_secret); + } + + public function authorizeUrl(): string + { + return rtrim($this->base_url, '/') . '/application/o/authorize/'; + } + + public function tokenUrl(): string + { + return rtrim($this->base_url, '/') . '/application/o/token/'; + } + + public function userInfoUrl(): string + { + return rtrim($this->base_url, '/') . '/application/o/userinfo/'; + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 385a0f6..8458ce0 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,6 +22,8 @@ class User extends Authenticatable 'email', 'password', 'is_admin', + 'sso_provider_id', + 'is_sso_user', ]; /** @@ -43,7 +45,8 @@ class User extends Authenticatable { return [ 'email_verified_at' => 'datetime', - 'password' => 'hashed', + 'password' => 'hashed', + 'is_sso_user' => 'boolean', ]; } diff --git a/database/migrations/2026_06_13_000001_create_sso_settings_table.php b/database/migrations/2026_06_13_000001_create_sso_settings_table.php new file mode 100644 index 0000000..a26c806 --- /dev/null +++ b/database/migrations/2026_06_13_000001_create_sso_settings_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('base_url')->nullable(); + $table->string('client_id')->nullable(); + $table->string('client_secret')->nullable(); + $table->string('slug')->nullable()->comment('Authentik Application Slug'); + $table->boolean('enabled')->default(false); + $table->timestamps(); + }); + + // Leere Zeile anlegen, damit immer genau eine Konfiguration existiert + DB::table('sso_settings')->insert([ + 'enabled' => false, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + public function down(): void + { + Schema::dropIfExists('sso_settings'); + } +}; diff --git a/database/migrations/2026_06_13_000002_add_sso_fields_to_users_table.php b/database/migrations/2026_06_13_000002_add_sso_fields_to_users_table.php new file mode 100644 index 0000000..c102943 --- /dev/null +++ b/database/migrations/2026_06_13_000002_add_sso_fields_to_users_table.php @@ -0,0 +1,23 @@ +string('sso_provider_id')->nullable()->unique()->after('email'); + $table->boolean('is_sso_user')->default(false)->after('sso_provider_id'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['sso_provider_id', 'is_sso_user']); + }); + } +}; diff --git a/resources/views/admin/sso/show.blade.php b/resources/views/admin/sso/show.blade.php new file mode 100644 index 0000000..bb4c026 --- /dev/null +++ b/resources/views/admin/sso/show.blade.php @@ -0,0 +1,222 @@ +@extends('layouts.app') + +@section('title', 'SSO-Konfiguration – NeoNail DB') + +@section('content') + +
Authentik OAuth2/OIDC Konfiguration
++ Diese URL musst du in Authentik als Redirect URI eintragen: +
+
+ {{ route('sso.callback') }}
+
+
+
+
+ Scopes: openid email profile
+