Refactor OauthProviders to use Singleton

This commit is contained in:
Vehikl 2025-05-01 16:40:21 -04:00
parent a53b3fda10
commit fa5fee50a0
16 changed files with 174 additions and 155 deletions

View File

@ -0,0 +1,41 @@
<?php
namespace App\Extensions\OAuth;
use Illuminate\Support\Facades\Event;
use SocialiteProviders\Manager\SocialiteWasCalled;
class OAuthProvider
{
/** @var OAuthSchemaInterface[] */
private array $providers = [];
/** @return OAuthSchemaInterface[] */
public function get(): array
{
return $this->providers;
}
/** @return OAuthSchemaInterface[] */
public function getEnabled(): array
{
return collect($this->providers)
->filter(fn (OAuthSchemaInterface $provider) => $provider->isEnabled())
->all();
}
public function register(OAuthSchemaInterface $provider): void
{
if (array_key_exists($provider->getId(), $this->providers)) {
return;
}
config()->set('services.' . $provider->getId(), array_merge($provider->getServiceConfig(), ['redirect' => '/auth/oauth/callback/' . $provider->getId()]));
if ($provider->getProviderClass()) {
Event::listen(fn (SocialiteWasCalled $event) => $event->extendSocialite($provider->getId(), $provider->getProviderClass()));
}
$this->providers[$provider->getId()] = $provider;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Extensions\OAuth;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Wizard\Step;
interface OAuthSchemaInterface
{
public function getId(): string;
/** @return ?class-string */
public function getProviderClass(): ?string;
/**
* @return array<string, string|string[]|bool|null>
*/
public function getServiceConfig(): array;
/** @return Component[] */
public function getSettingsForm(): array;
/** @return Step[] */
public function getSetupSteps(): array;
public function getName(): string;
public function getIcon(): ?string;
public function getHexColor(): ?string;
public function isEnabled(): bool;
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Extensions\OAuth\Providers;
use Illuminate\Foundation\Application;
final class CommonProvider extends OAuthProvider
{
protected function __construct(protected Application $app, private string $id, private ?string $providerClass, private ?string $icon, private ?string $hexColor)
{
parent::__construct($app);
}
public function getId(): string
{
return $this->id;
}
public function getProviderClass(): ?string
{
return $this->providerClass;
}
public function getIcon(): ?string
{
return $this->icon;
}
public function getHexColor(): ?string
{
return $this->hexColor;
}
public static function register(Application $app, string $id, ?string $providerClass = null, ?string $icon = null, ?string $hexColor = null): static
{
return new self($app, $id, $providerClass, $icon, $hexColor);
}
}

View File

@ -1,19 +1,13 @@
<?php
namespace App\Extensions\OAuth\Providers;
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\TextInput;
use Illuminate\Foundation\Application;
use SocialiteProviders\Authentik\Provider;
final class AuthentikProvider extends OAuthProvider
final class AuthentikSchema extends OAuthSchema
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
public function getId(): string
{
return 'authentik';
@ -66,9 +60,4 @@ final class AuthentikProvider extends OAuthProvider
{
return env('OAUTH_AUTHENTIK_DISPLAY_COLOR', '#fd4b2d');
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Extensions\OAuth\Schemas;
final class CommonSchema extends OAuthSchema
{
public function __construct(private string $id, private ?string $providerClass, private ?string $icon, private ?string $hexColor)
{
parent::__construct();
}
public function getId(): string
{
return $this->id;
}
public function getProviderClass(): ?string
{
return $this->providerClass;
}
public function getIcon(): ?string
{
return $this->icon;
}
public function getHexColor(): ?string
{
return $this->hexColor;
}
}

View File

@ -1,23 +1,17 @@
<?php
namespace App\Extensions\OAuth\Providers;
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use SocialiteProviders\Discord\Provider;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class DiscordProvider extends OAuthProvider
final class DiscordSchema extends OAuthSchema
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
public function getId(): string
{
return 'discord';
@ -56,9 +50,4 @@ final class DiscordProvider extends OAuthProvider
{
return '#5865F2';
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,22 +1,16 @@
<?php
namespace App\Extensions\OAuth\Providers;
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class GithubProvider extends OAuthProvider
final class GithubSchema extends OAuthSchema
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
public function getId(): string
{
return 'github';
@ -55,9 +49,4 @@ final class GithubProvider extends OAuthProvider
{
return '#4078c0';
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,22 +1,16 @@
<?php
namespace App\Extensions\OAuth\Providers;
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class GitlabProvider extends OAuthProvider
final class GitlabSchema extends OAuthSchema
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
public function getId(): string
{
return 'gitlab';
@ -68,9 +62,4 @@ final class GitlabProvider extends OAuthProvider
{
return '#fca326';
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,16 +1,16 @@
<?php
namespace App\Extensions\OAuth\Providers;
namespace App\Extensions\OAuth\Schemas;
use App\Extensions\OAuth\OAuthSchemaInterface;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use SocialiteProviders\Manager\SocialiteWasCalled;
abstract class OAuthProvider
abstract class OAuthSchema implements OAuthSchemaInterface
{
/**
* @var array<string, static>
@ -25,25 +25,13 @@ abstract class OAuthProvider
return $id ? static::$providers[$id] : static::$providers;
}
protected function __construct(protected Application $app)
public function __construct()
{
if (array_key_exists($this->getId(), static::$providers)) {
if (!$this->app->runningUnitTests()) {
logger()->warning("Tried to create duplicate OAuth provider with id '{$this->getId()}'");
}
return;
}
config()->set('services.' . $this->getId(), array_merge($this->getServiceConfig(), ['redirect' => '/auth/oauth/callback/' . $this->getId()]));
if ($this->getProviderClass()) {
Event::listen(function (SocialiteWasCalled $event) {
$event->extendSocialite($this->getId(), $this->getProviderClass());
});
}
static::$providers[$this->getId()] = $this;
}
abstract public function getId(): string;

View File

@ -1,22 +1,16 @@
<?php
namespace App\Extensions\OAuth\Providers;
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use SocialiteProviders\Steam\Provider;
final class SteamProvider extends OAuthProvider
final class SteamSchema extends OAuthSchema
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
public function getId(): string
{
return 'steam';
@ -73,9 +67,4 @@ final class SteamProvider extends OAuthProvider
{
return '#00adee';
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -4,7 +4,7 @@ namespace App\Filament\Admin\Pages;
use App\Extensions\Avatar\AvatarProvider;
use App\Extensions\Captcha\Providers\CaptchaProvider;
use App\Extensions\OAuth\Providers\OAuthProvider;
use App\Extensions\OAuth\OAuthProvider;
use App\Models\Backup;
use App\Notifications\MailTested;
use App\Traits\EnvironmentWriterTrait;
@ -51,6 +51,8 @@ class Settings extends Page implements HasForms
protected static string $view = 'filament.pages.settings';
protected OAuthProvider $oauthProvider;
/** @var array<mixed>|null */
public ?array $data = [];
@ -59,6 +61,11 @@ class Settings extends Page implements HasForms
$this->form->fill();
}
public function boot(OAuthProvider $oauthProvider): void
{
$this->oauthProvider = $oauthProvider;
}
public static function canAccess(): bool
{
return auth()->user()->can('view settings');
@ -523,7 +530,7 @@ class Settings extends Page implements HasForms
{
$formFields = [];
$oauthProviders = OAuthProvider::get();
$oauthProviders = $this->oauthProvider->get();
foreach ($oauthProviders as $oauthProvider) {
$id = Str::upper($oauthProvider->getId());
$name = Str::title($oauthProvider->getId());

View File

@ -3,7 +3,7 @@
namespace App\Filament\Pages\Auth;
use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
use App\Extensions\OAuth\Providers\OAuthProvider;
use App\Extensions\OAuth\OAuthProvider;
use App\Facades\Activity;
use App\Models\ActivityLog;
use App\Models\ApiKey;
@ -51,9 +51,12 @@ class EditProfile extends BaseEditProfile
{
private ToggleTwoFactorService $toggleTwoFactorService;
public function boot(ToggleTwoFactorService $toggleTwoFactorService): void
protected OAuthProvider $oauthProvider;
public function boot(ToggleTwoFactorService $toggleTwoFactorService, OAuthProvider $oauthProvider): void
{
$this->toggleTwoFactorService = $toggleTwoFactorService;
$this->oauthProvider = $oauthProvider;
}
public function getMaxWidth(): MaxWidth|string
@ -63,7 +66,7 @@ class EditProfile extends BaseEditProfile
protected function getForms(): array
{
$oauthProviders = collect(OAuthProvider::get())->filter(fn (OAuthProvider $provider) => $provider->isEnabled())->all();
$oauthProviders = $this->oauthProvider->getEnabled();
return [
'form' => $this->form(

View File

@ -3,7 +3,7 @@
namespace App\Filament\Pages\Auth;
use App\Extensions\Captcha\Providers\CaptchaProvider;
use App\Extensions\OAuth\Providers\OAuthProvider;
use App\Extensions\OAuth\OAuthProvider;
use App\Models\User;
use Filament\Facades\Filament;
use Filament\Forms\Components\Actions;
@ -25,9 +25,12 @@ class Login extends BaseLogin
public bool $verifyTwoFactor = false;
public function boot(Google2FA $google2FA): void
protected OAuthProvider $oauthProvider;
public function boot(Google2FA $google2FA, OAuthProvider $oauthProvider): void
{
$this->google2FA = $google2FA;
$this->oauthProvider = $oauthProvider;
}
public function authenticate(): ?LoginResponse
@ -147,7 +150,7 @@ class Login extends BaseLogin
{
$actions = [];
$oauthProviders = collect(OAuthProvider::get())->filter(fn (OAuthProvider $provider) => $provider->isEnabled())->all();
$oauthProviders = $this->oauthProvider->getEnabled();
foreach ($oauthProviders as $oauthProvider) {

View File

@ -2,17 +2,17 @@
namespace App\Http\Controllers\Auth;
use App\Extensions\OAuth\Providers\OAuthProvider;
use App\Extensions\OAuth\Schemas\OAuthSchema;
use App\Filament\Pages\Auth\EditProfile;
use Filament\Notifications\Notification;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\RedirectResponse;
use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\Users\UserUpdateService;
use Exception;
use Filament\Notifications\Notification;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
class OAuthController extends Controller
{
@ -27,7 +27,7 @@ class OAuthController extends Controller
public function redirect(string $driver): RedirectResponse
{
// Driver is disabled - redirect to normal login
if (!OAuthProvider::get($driver)->isEnabled()) {
if (!OAuthSchema::get($driver)->isEnabled()) {
return redirect()->route('auth.login');
}
@ -40,7 +40,7 @@ class OAuthController extends Controller
public function callback(Request $request, string $driver): RedirectResponse
{
// Driver is disabled - redirect to normal login
if (!OAuthProvider::get($driver)->isEnabled()) {
if (!OAuthSchema::get($driver)->isEnabled()) {
return redirect()->route('auth.login');
}

View File

@ -12,19 +12,20 @@ use App\Checks\ScheduleCheck;
use App\Checks\UsedDiskSpaceCheck;
use App\Extensions\Avatar\Providers\GravatarProvider;
use App\Extensions\Avatar\Providers\UiAvatarsProvider;
use App\Extensions\OAuth\Providers\GitlabProvider;
use App\Models;
use App\Extensions\Captcha\Providers\TurnstileProvider;
use App\Extensions\Features\GSLToken;
use App\Extensions\Features\JavaVersion;
use App\Extensions\Features\MinecraftEula;
use App\Extensions\Features\PIDLimit;
use App\Extensions\Features\SteamDiskSpace;
use App\Extensions\OAuth\Providers\AuthentikProvider;
use App\Extensions\OAuth\Providers\CommonProvider;
use App\Extensions\OAuth\Providers\DiscordProvider;
use App\Extensions\OAuth\Providers\GithubProvider;
use App\Extensions\OAuth\Providers\SteamProvider;
use App\Extensions\OAuth\OAuthProvider;
use App\Extensions\OAuth\Schemas\AuthentikSchema;
use App\Extensions\OAuth\Schemas\CommonSchema;
use App\Extensions\OAuth\Schemas\DiscordSchema;
use App\Extensions\OAuth\Schemas\GithubSchema;
use App\Extensions\OAuth\Schemas\GitlabSchema;
use App\Extensions\OAuth\Schemas\SteamSchema;
use App\Models;
use App\Services\Helpers\SoftwareVersionService;
use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
@ -104,20 +105,25 @@ class AppServiceProvider extends ServiceProvider
Scramble::registerApi('application', ['api_path' => 'api/application', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
Scramble::registerApi('client', ['api_path' => 'api/client', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
// Default OAuth providers included with Socialite
CommonProvider::register($app, 'facebook', null, 'tabler-brand-facebook-f', '#1877f2');
CommonProvider::register($app, 'x', null, 'tabler-brand-x-f', '#1da1f2');
CommonProvider::register($app, 'linkedin', null, 'tabler-brand-linkedin-f', '#0a66c2');
CommonProvider::register($app, 'google', null, 'tabler-brand-google-f', '#4285f4');
GithubProvider::register($app);
GitlabProvider::register($app);
CommonProvider::register($app, 'bitbucket', null, 'tabler-brand-bitbucket-f', '#205081');
CommonProvider::register($app, 'slack', null, 'tabler-brand-slack', '#6ecadc');
$this->app->singleton(OAuthProvider::class, function ($app) {
$provider = new OAuthProvider();
// Default OAuth providers included with Socialite
$provider->register(new CommonSchema('facebook', null, 'tabler-brand-facebook-f', '#1877f2'));
$provider->register(new CommonSchema('x', null, 'tabler-brand-x-f', '#1da1f2'));
$provider->register(new CommonSchema('linkedin', null, 'tabler-brand-linkedin-f', '#0a66c2'));
$provider->register(new CommonSchema('google', null, 'tabler-brand-google-f', '#4285f4'));
$provider->register(new GithubSchema());
$provider->register(new GitlabSchema());
$provider->register(new CommonSchema('bitbucket', null, 'tabler-brand-bitbucket-f', '#205081'));
$provider->register(new CommonSchema('slack', null, 'tabler-brand-slack', '#6ecadc'));
// Additional OAuth providers from socialiteproviders.com
AuthentikProvider::register($app);
DiscordProvider::register($app);
SteamProvider::register($app);
// Additional OAuth providers from socialiteproviders.com
$provider->register(new AuthentikSchema());
$provider->register(new DiscordSchema());
$provider->register(new SteamSchema());
return $provider;
});
// Default Captcha provider
TurnstileProvider::register($app);

View File

@ -21,5 +21,5 @@ parameters:
paths:
- app/Console/Commands/Environment/*.php
- app/Extensions/Captcha/Providers/*.php
- app/Extensions/OAuth/Providers/*.php
- app/Extensions/OAuth/Schemas/*.php
- app/Filament/Admin/Pages/Settings.php