From 024a21306cc521ab54d781310726cd411f677c52 Mon Sep 17 00:00:00 2001 From: fatihalp Date: Tue, 3 Mar 2026 13:38:08 +0300 Subject: [PATCH] Add Filament admin plugin stack --- .../Providers/PartnerPanelProvider.php | 116 ++++++++++++++++++ .../Controllers/Auth/SocialAuthController.php | 116 ++++++++++++++++++ resources/views/auth/login.blade.php | 26 ++++ routes/auth.php | 9 ++ 4 files changed, 267 insertions(+) create mode 100644 app/Http/Controllers/Auth/SocialAuthController.php diff --git a/Modules/Partner/Providers/PartnerPanelProvider.php b/Modules/Partner/Providers/PartnerPanelProvider.php index f4f959c21..139049d5b 100644 --- a/Modules/Partner/Providers/PartnerPanelProvider.php +++ b/Modules/Partner/Providers/PartnerPanelProvider.php @@ -3,6 +3,10 @@ namespace Modules\Partner\Providers; use A909M\FilamentStateFusion\FilamentStateFusionPlugin; use App\Models\User; +use App\Settings\GeneralSettings; +use DutchCodingCompany\FilamentDeveloperLogins\FilamentDeveloperLoginsPlugin; +use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; +use DutchCodingCompany\FilamentSocialite\Provider; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; @@ -16,8 +20,13 @@ use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\StartSession; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; use Illuminate\View\Middleware\ShareErrorsFromSession; use Jeffgreco13\FilamentBreezy\BreezyCore; +use Laravel\Socialite\Contracts\User as SocialiteUserContract; +use Spatie\Permission\Models\Role; +use Throwable; class PartnerPanelProvider extends PanelProvider { @@ -43,6 +52,13 @@ class PartnerPanelProvider extends PanelProvider ) ->enableTwoFactorAuthentication() ->enableSanctumTokens(), + FilamentDeveloperLoginsPlugin::make() + ->enabled(fn (): bool => app()->environment('local')) + ->users([ + 'Partner (Add Listing)' => 'b@b.com', + ]) + ->redirectTo(fn (): ?string => self::partnerCreateListingUrl()), + self::socialitePlugin(), ]) ->pages([Dashboard::class]) ->middleware([ @@ -58,4 +74,104 @@ class PartnerPanelProvider extends PanelProvider ]) ->authMiddleware([Authenticate::class]); } + + private static function socialitePlugin(): FilamentSocialitePlugin + { + return FilamentSocialitePlugin::make() + ->providers(self::socialiteProviders()) + ->registration(true) + ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser): ?User { + if (! filled($oauthUser->getEmail())) { + return null; + } + + return User::query()->where('email', strtolower(trim((string) $oauthUser->getEmail())))->first(); + }) + ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser): User { + $email = filled($oauthUser->getEmail()) + ? strtolower(trim((string) $oauthUser->getEmail())) + : sprintf('%s_%s@social.local', $provider, $oauthUser->getId()); + + $user = User::query()->firstOrCreate( + ['email' => $email], + [ + 'name' => trim((string) ($oauthUser->getName() ?: $oauthUser->getNickname() ?: ucfirst($provider).' User')), + 'password' => Hash::make(Str::random(40)), + 'status' => 'active', + ], + ); + + if (class_exists(Role::class)) { + $partnerRole = Role::firstOrCreate(['name' => 'partner', 'guard_name' => 'web']); + $user->syncRoles([$partnerRole->name]); + } + + return $user; + }); + } + + /** + * @return array + */ + private static function socialiteProviders(): array + { + $providers = []; + + if (self::providerEnabled('google')) { + $providers[] = Provider::make('google') + ->label('Google') + ->icon('heroicon-o-globe-alt') + ->color(Color::hex('#4285F4')); + } + + if (self::providerEnabled('facebook')) { + $providers[] = Provider::make('facebook') + ->label('Facebook') + ->icon('heroicon-o-users') + ->color(Color::hex('#1877F2')); + } + + if (self::providerEnabled('apple')) { + $providers[] = Provider::make('apple') + ->label('Apple') + ->icon('heroicon-o-device-phone-mobile') + ->color(Color::Gray) + ->stateless(true); + } + + return $providers; + } + + private static function providerEnabled(string $provider): bool + { + try { + $settings = app(GeneralSettings::class); + + $enabled = match ($provider) { + 'google' => (bool) $settings->enable_google_login, + 'facebook' => (bool) $settings->enable_facebook_login, + 'apple' => (bool) $settings->enable_apple_login, + default => false, + }; + + return $enabled + && filled(config("services.{$provider}.client_id")) + && filled(config("services.{$provider}.client_secret")); + } catch (Throwable) { + return (bool) config("services.{$provider}.enabled", false) + && filled(config("services.{$provider}.client_id")) + && filled(config("services.{$provider}.client_secret")); + } + } + + private static function partnerCreateListingUrl(): ?string + { + $partner = User::query()->where('email', 'b@b.com')->first(); + + if (! $partner) { + return null; + } + + return route('filament.partner.resources.listings.create', ['tenant' => $partner->getKey()]); + } } diff --git a/app/Http/Controllers/Auth/SocialAuthController.php b/app/Http/Controllers/Auth/SocialAuthController.php new file mode 100644 index 000000000..60bf340d3 --- /dev/null +++ b/app/Http/Controllers/Auth/SocialAuthController.php @@ -0,0 +1,116 @@ + + */ + private array $allowedProviders = ['google', 'facebook', 'apple']; + + public function redirect(string $provider): RedirectResponse + { + abort_unless($this->isProviderAllowed($provider), 404); + abort_unless($this->isProviderEnabled($provider), 404); + + return $this->driver($provider)->redirect(); + } + + public function callback(Request $request, string $provider): RedirectResponse + { + abort_unless($this->isProviderAllowed($provider), 404); + abort_unless($this->isProviderEnabled($provider), 404); + + try { + $oauthUser = $this->driver($provider)->user(); + } catch (Throwable) { + return redirect()->route('login') + ->withErrors(['email' => __('Social login failed. Please try again.')]); + } + + if (! filled($oauthUser->getId())) { + return redirect()->route('login') + ->withErrors(['email' => __('Unable to read social account identity.')]); + } + + $socialiteUser = DB::table('socialite_users') + ->where('provider', $provider) + ->where('provider_id', (string) $oauthUser->getId()) + ->first(); + + $user = null; + + if ($socialiteUser?->user_id) { + $user = User::query()->find($socialiteUser->user_id); + } + + if (! $user) { + $email = filled($oauthUser->getEmail()) + ? strtolower(trim((string) $oauthUser->getEmail())) + : sprintf('%s_%s@social.local', $provider, $oauthUser->getId()); + + $user = User::query()->firstOrCreate( + ['email' => $email], + [ + 'name' => trim((string) ($oauthUser->getName() ?: $oauthUser->getNickname() ?: ucfirst($provider).' User')), + 'password' => Hash::make(Str::random(40)), + 'status' => 'active', + 'email_verified_at' => now(), + ], + ); + } + + DB::table('socialite_users')->updateOrInsert( + [ + 'provider' => $provider, + 'provider_id' => (string) $oauthUser->getId(), + ], + [ + 'user_id' => $user->getKey(), + 'updated_at' => now(), + 'created_at' => $socialiteUser?->created_at ?? now(), + ], + ); + + Auth::guard('web')->login($user, true); + $request->session()->regenerate(); + + return redirect()->intended(route('dashboard', absolute: false)); + } + + private function driver(string $provider) + { + $driver = Socialite::driver($provider) + ->redirectUrl(route('auth.social.callback', ['provider' => $provider], absolute: true)); + + if ($provider === 'apple' || (bool) config("services.{$provider}.stateless", false)) { + return $driver->stateless(); + } + + return $driver; + } + + private function isProviderAllowed(string $provider): bool + { + return in_array($provider, $this->allowedProviders, true); + } + + private function isProviderEnabled(string $provider): bool + { + return (bool) config("services.{$provider}.enabled", false) + && filled(config("services.{$provider}.client_id")) + && filled(config("services.{$provider}.client_secret")); + } +} diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 78b684f7d..278082297 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,6 +1,17 @@ + @php + $socialProviders = collect([ + 'google' => 'Google', + 'facebook' => 'Facebook', + 'apple' => 'Apple', + ])->filter( + fn ($label, $provider) => (bool) config("services.{$provider}.enabled") + && filled(config("services.{$provider}.client_id")) + && filled(config("services.{$provider}.client_secret")) + ); + @endphp
@csrf @@ -43,5 +54,20 @@ {{ __('Log in') }} + + @if($socialProviders->isNotEmpty()) +
+

{{ __('Or continue with') }}

+ +
+ @foreach($socialProviders as $provider => $label) + + {{ $label }} + + @endforeach +
+
+ @endif
diff --git a/routes/auth.php b/routes/auth.php index 12e77f40f..f9b9c5b00 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -8,6 +8,7 @@ use App\Http\Controllers\Auth\NewPasswordController; use App\Http\Controllers\Auth\PasswordController; use App\Http\Controllers\Auth\PasswordResetLinkController; use App\Http\Controllers\Auth\RegisteredUserController; +use App\Http\Controllers\Auth\SocialAuthController; use App\Http\Controllers\Auth\VerifyEmailController; use Illuminate\Support\Facades\Route; @@ -22,6 +23,14 @@ Route::middleware('guest')->group(function () { Route::post('login', [AuthenticatedSessionController::class, 'store']); + Route::get('auth/{provider}/redirect', [SocialAuthController::class, 'redirect']) + ->whereIn('provider', ['google', 'facebook', 'apple']) + ->name('auth.social.redirect'); + + Route::get('auth/{provider}/callback', [SocialAuthController::class, 'callback']) + ->whereIn('provider', ['google', 'facebook', 'apple']) + ->name('auth.social.callback'); + Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) ->name('password.request');