mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 11:12:09 -05:00
Add Filament admin plugin stack
This commit is contained in:
parent
be1341b3e9
commit
024a21306c
@ -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<int, Provider>
|
||||
*/
|
||||
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()]);
|
||||
}
|
||||
}
|
||||
|
||||
116
app/Http/Controllers/Auth/SocialAuthController.php
Normal file
116
app/Http/Controllers/Auth/SocialAuthController.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Throwable;
|
||||
|
||||
class SocialAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,17 @@
|
||||
<x-guest-layout>
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
@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
|
||||
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
@ -43,5 +54,20 @@
|
||||
{{ __('Log in') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
|
||||
@if($socialProviders->isNotEmpty())
|
||||
<div class="mt-6 border-t pt-4">
|
||||
<p class="text-sm text-gray-600 mb-3">{{ __('Or continue with') }}</p>
|
||||
|
||||
<div class="grid gap-2">
|
||||
@foreach($socialProviders as $provider => $label)
|
||||
<a href="{{ route('auth.social.redirect', ['provider' => $provider]) }}"
|
||||
class="inline-flex items-center justify-center rounded-md border border-gray-300 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
{{ $label }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user