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 A909M\FilamentStateFusion\FilamentStateFusionPlugin;
|
||||||
use App\Models\User;
|
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\Authenticate;
|
||||||
use Filament\Http\Middleware\AuthenticateSession;
|
use Filament\Http\Middleware\AuthenticateSession;
|
||||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
@ -16,8 +20,13 @@ use Illuminate\Cookie\Middleware\EncryptCookies;
|
|||||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
use Illuminate\Session\Middleware\StartSession;
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
use Jeffgreco13\FilamentBreezy\BreezyCore;
|
use Jeffgreco13\FilamentBreezy\BreezyCore;
|
||||||
|
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class PartnerPanelProvider extends PanelProvider
|
class PartnerPanelProvider extends PanelProvider
|
||||||
{
|
{
|
||||||
@ -43,6 +52,13 @@ class PartnerPanelProvider extends PanelProvider
|
|||||||
)
|
)
|
||||||
->enableTwoFactorAuthentication()
|
->enableTwoFactorAuthentication()
|
||||||
->enableSanctumTokens(),
|
->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])
|
->pages([Dashboard::class])
|
||||||
->middleware([
|
->middleware([
|
||||||
@ -58,4 +74,104 @@ class PartnerPanelProvider extends PanelProvider
|
|||||||
])
|
])
|
||||||
->authMiddleware([Authenticate::class]);
|
->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>
|
<x-guest-layout>
|
||||||
<!-- Session Status -->
|
<!-- Session Status -->
|
||||||
<x-auth-session-status class="mb-4" :status="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') }}">
|
<form method="POST" action="{{ route('login') }}">
|
||||||
@csrf
|
@csrf
|
||||||
@ -43,5 +54,20 @@
|
|||||||
{{ __('Log in') }}
|
{{ __('Log in') }}
|
||||||
</x-primary-button>
|
</x-primary-button>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</x-guest-layout>
|
</x-guest-layout>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ use App\Http\Controllers\Auth\NewPasswordController;
|
|||||||
use App\Http\Controllers\Auth\PasswordController;
|
use App\Http\Controllers\Auth\PasswordController;
|
||||||
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
||||||
use App\Http\Controllers\Auth\RegisteredUserController;
|
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||||
|
use App\Http\Controllers\Auth\SocialAuthController;
|
||||||
use App\Http\Controllers\Auth\VerifyEmailController;
|
use App\Http\Controllers\Auth\VerifyEmailController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
@ -22,6 +23,14 @@ Route::middleware('guest')->group(function () {
|
|||||||
|
|
||||||
Route::post('login', [AuthenticatedSessionController::class, 'store']);
|
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'])
|
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
|
||||||
->name('password.request');
|
->name('password.request');
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user