Refactor Providers to be a singleton (#1327)

This commit is contained in:
pelican-vehikl 2025-07-01 21:33:11 -04:00 committed by GitHub
parent 74bd7f9991
commit de4cb38766
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 645 additions and 569 deletions

View File

@ -1,42 +0,0 @@
<?php
namespace App\Extensions\Avatar;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
abstract class AvatarProvider
{
/**
* @var array<string, static>
*/
protected static array $providers = [];
public static function getProvider(string $id): ?self
{
return Arr::get(static::$providers, $id);
}
/**
* @return array<string, static>
*/
public static function getAll(): array
{
return static::$providers;
}
public function __construct()
{
static::$providers[$this->getId()] = $this;
}
abstract public function getId(): string;
abstract public function get(User $user): ?string;
public function getName(): string
{
return Str::title($this->getId());
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Extensions\Avatar;
use App\Models\User;
interface AvatarSchemaInterface
{
public function getId(): string;
public function getName(): string;
public function get(User $user): ?string;
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Extensions\Avatar;
use App\Models\User;
use Illuminate\Support\Facades\Storage;
class AvatarService
{
/** @var AvatarSchemaInterface[] */
private array $schemas = [];
public function __construct(
private readonly bool $allowUploadedAvatars,
private readonly string $activeSchema,
) {}
public function get(string $id): ?AvatarSchemaInterface
{
return array_get($this->schemas, $id);
}
public function getActiveSchema(): ?AvatarSchemaInterface
{
return $this->get($this->activeSchema);
}
public function getAvatarUrl(User $user): ?string
{
if ($this->allowUploadedAvatars) {
$path = "avatars/$user->id.png";
if (Storage::disk('public')->exists($path)) {
return Storage::url($path);
}
}
return $this->getActiveSchema()?->get($user);
}
public function register(AvatarSchemaInterface $schema): void
{
if (array_key_exists($schema->getId(), $this->schemas)) {
return;
}
$this->schemas[$schema->getId()] = $schema;
}
/** @return array<string, string> */
public function getMappings(): array
{
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
}
}

View File

@ -1,24 +1,24 @@
<?php
namespace App\Extensions\Avatar\Providers;
namespace App\Extensions\Avatar\Schemas;
use App\Extensions\Avatar\AvatarProvider;
use App\Extensions\Avatar\AvatarSchemaInterface;
use App\Models\User;
class GravatarProvider extends AvatarProvider
class GravatarSchema implements AvatarSchemaInterface
{
public function getId(): string
{
return 'gravatar';
}
public function getName(): string
{
return 'Gravatar';
}
public function get(User $user): string
{
return 'https://gravatar.com/avatar/' . md5($user->email);
}
public static function register(): self
{
return new self();
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace App\Extensions\Avatar\Providers;
namespace App\Extensions\Avatar\Schemas;
use App\Extensions\Avatar\AvatarProvider;
use App\Extensions\Avatar\AvatarSchemaInterface;
use App\Models\User;
class UiAvatarsProvider extends AvatarProvider
class UiAvatarsSchema implements AvatarSchemaInterface
{
public function getId(): string
{
@ -22,9 +22,4 @@ class UiAvatarsProvider extends AvatarProvider
// UI Avatars is the default of filament so just return null here
return null;
}
public static function register(): self
{
return new self();
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Extensions\Captcha;
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class CaptchaService
{
/** @var array<string, CaptchaSchemaInterface> */
private array $schemas = [];
/**
* @return CaptchaSchemaInterface[]
*/
public function getAll(): array
{
return $this->schemas;
}
public function get(string $id): ?CaptchaSchemaInterface
{
return array_get($this->schemas, $id);
}
public function register(CaptchaSchemaInterface $schema): void
{
if (array_key_exists($schema->getId(), $this->schemas)) {
return;
}
config()->set('captcha.' . Str::lower($schema->getId()), $schema->getConfig());
$this->schemas[$schema->getId()] = $schema;
}
/** @return Collection<CaptchaSchemaInterface> */
public function getActiveSchemas(): Collection
{
return collect($this->schemas)
->filter(fn (CaptchaSchemaInterface $schema) => $schema->isEnabled());
}
public function getActiveSchema(): ?CaptchaSchemaInterface
{
return $this->getActiveSchemas()->first();
}
}

View File

@ -1,45 +1,19 @@
<?php
namespace App\Extensions\Captcha\Providers;
namespace App\Extensions\Captcha\Schemas;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Illuminate\Foundation\Application;
use Illuminate\Support\Str;
abstract class CaptchaProvider
abstract class BaseSchema
{
/**
* @var array<string, static>
*/
protected static array $providers = [];
/**
* @return self|static[]
*/
public static function get(?string $id = null): array|self
{
return $id ? static::$providers[$id] : static::$providers;
}
protected function __construct(protected Application $app)
{
if (array_key_exists($this->getId(), static::$providers)) {
if (!$this->app->runningUnitTests()) {
logger()->warning("Tried to create duplicate Captcha provider with id '{$this->getId()}'");
}
return;
}
config()->set('captcha.' . Str::lower($this->getId()), $this->getConfig());
static::$providers[$this->getId()] = $this;
}
abstract public function getId(): string;
abstract public function getComponent(): Component;
public function getName(): string
{
return Str::upper($this->getId());
}
/**
* @return array<string, string|string[]|bool|null>
@ -83,34 +57,6 @@ abstract class CaptchaProvider
];
}
public function getName(): string
{
return Str::title($this->getId());
}
public function getIcon(): ?string
{
return null;
}
public function isEnabled(): bool
{
$id = Str::upper($this->getId());
return env("CAPTCHA_{$id}_ENABLED", false);
}
/**
* @return array<string, string|bool>
*/
public function validateResponse(?string $captchaResponse = null): array
{
return [
'success' => false,
'message' => 'validateResponse not defined',
];
}
public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
{
return true;

View File

@ -0,0 +1,35 @@
<?php
namespace App\Extensions\Captcha\Schemas;
use Filament\Forms\Components\Component;
interface CaptchaSchemaInterface
{
public function getId(): string;
public function getName(): string;
/**
* @return array<string, string|string[]|bool|null>
*/
public function getConfig(): array;
public function isEnabled(): bool;
public function getFormComponent(): Component;
/**
* @return Component[]
*/
public function getSettingsForm(): array;
public function getIcon(): ?string;
/**
* @return array<string, string|bool>
*/
public function validateResponse(?string $captchaResponse = null): array;
public function verifyDomain(string $hostname, ?string $requestUrl = null): bool;
}

View File

@ -1,11 +1,10 @@
<?php
namespace App\Filament\Components\Forms\Fields;
namespace App\Extensions\Captcha\Schemas\Turnstile;
use App\Rules\ValidTurnstileCaptcha;
use Filament\Forms\Components\Field;
class TurnstileCaptcha extends Field
class Component extends Field
{
protected string $viewIdentifier = 'turnstile';
@ -19,8 +18,6 @@ class TurnstileCaptcha extends Field
$this->required();
$this->after(function (TurnstileCaptcha $component) {
$component->rule(new ValidTurnstileCaptcha());
});
$this->rule(new Rule());
}
}

View File

@ -1,16 +1,17 @@
<?php
namespace App\Rules;
namespace App\Extensions\Captcha\Schemas\Turnstile;
use App\Extensions\Captcha\Providers\CaptchaProvider;
use App\Extensions\Captcha\CaptchaService;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\App;
class ValidTurnstileCaptcha implements ValidationRule
class Rule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$response = CaptchaProvider::get('turnstile')->validateResponse($value);
$response = App::call(fn (CaptchaService $service) => $service->getActiveSchema()->validateResponse($value));
if (!$response['success']) {
$fail($response['message'] ?? 'Unknown error occurred, please try again');

View File

@ -1,26 +1,31 @@
<?php
namespace App\Extensions\Captcha\Providers;
namespace App\Extensions\Captcha\Schemas\Turnstile;
use App\Filament\Components\Forms\Fields\TurnstileCaptcha;
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
use App\Extensions\Captcha\Schemas\BaseSchema;
use Exception;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Component as BaseComponent;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Toggle;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\HtmlString;
class TurnstileProvider extends CaptchaProvider
class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
{
public function getId(): string
{
return 'turnstile';
}
public function getComponent(): Component
public function isEnabled(): bool
{
return TurnstileCaptcha::make('turnstile');
return env('CAPTCHA_TURNSTILE_ENABLED', false);
}
public function getFormComponent(): BaseComponent
{
return Component::make('turnstile');
}
/**
@ -34,7 +39,7 @@ class TurnstileProvider extends CaptchaProvider
}
/**
* @return Component[]
* @return BaseComponent[]
*/
public function getSettingsForm(): array
{
@ -52,20 +57,14 @@ class TurnstileProvider extends CaptchaProvider
->label(trans('admin/setting.captcha.info_label'))
->columnSpan(2)
->content(new HtmlString(trans('admin/setting.captcha.info'))),
]);
}
public function getIcon(): string
public function getIcon(): ?string
{
return 'tabler-brand-cloudflare';
}
public static function register(Application $app): self
{
return new self($app);
}
/**
* @return array<string, string|bool>
*/

View File

@ -1,51 +0,0 @@
<?php
namespace App\Extensions\Features;
use Filament\Actions\Action;
use Illuminate\Foundation\Application;
abstract class FeatureProvider
{
/**
* @var array<string, static>
*/
protected static array $providers = [];
/**
* @param string[] $id
* @return self|static[]
*/
public static function getProviders(string|array|null $id = null): array|self
{
if (is_array($id)) {
return array_intersect_key(static::$providers, array_flip($id));
}
return $id ? static::$providers[$id] : static::$providers;
}
protected function __construct(protected Application $app)
{
if (array_key_exists($this->getId(), static::$providers)) {
if (!$this->app->runningUnitTests()) {
logger()->warning("Tried to create duplicate Feature provider with id '{$this->getId()}'");
}
return;
}
static::$providers[$this->getId()] = $this;
}
abstract public function getId(): string;
/**
* A matching subset string (case-insensitive) from the console output
*
* @return array<string>
*/
abstract public function getListeners(): array;
abstract public function getAction(): Action;
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Extensions\Features;
use Filament\Actions\Action;
interface FeatureSchemaInterface
{
/** @return string[] */
public function getListeners(): array;
public function getId(): string;
public function getAction(): Action;
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Extensions\Features;
class FeatureService
{
/** @var FeatureSchemaInterface[] */
private array $schemas = [];
/**
* @return FeatureSchemaInterface[]
*/
public function getAll(): array
{
return $this->schemas;
}
public function get(string $id): ?FeatureSchemaInterface
{
return array_get($this->schemas, $id);
}
/**
* @param string[] $features
* @return FeatureSchemaInterface[]
*/
public function getActiveSchemas(array $features): array
{
return collect($this->schemas)->only($features)->all();
}
public function register(FeatureSchemaInterface $schema): void
{
if (array_key_exists($schema->getId(), $this->schemas)) {
return;
}
$this->schemas[$schema->getId()] = $schema;
}
/**
* @param string[] $features
* @return array<string, array<string>>
*/
public function getMappings(array $features): array
{
return collect($this->getActiveSchemas($features))
->mapWithKeys(fn (FeatureSchemaInterface $schema) => [
$schema->getId() => $schema->getListeners(),
])->all();
}
}

View File

@ -1,7 +1,8 @@
<?php
namespace App\Extensions\Features;
namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface;
use App\Facades\Activity;
use App\Models\Permission;
use App\Models\Server;
@ -15,18 +16,12 @@ use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\HtmlString;
class GSLToken extends FeatureProvider
class GSLTokenSchema implements FeatureSchemaInterface
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
/** @return array<string> */
public function getListeners(): array
{
@ -119,9 +114,4 @@ class GSLToken extends FeatureProvider
}
});
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,7 +1,8 @@
<?php
namespace App\Extensions\Features;
namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface;
use App\Facades\Activity;
use App\Models\Permission;
use App\Models\Server;
@ -12,15 +13,9 @@ use Filament\Facades\Filament;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Notifications\Notification;
use Illuminate\Foundation\Application;
class JavaVersion extends FeatureProvider
class JavaVersionSchema implements FeatureSchemaInterface
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
/** @return array<string> */
public function getListeners(): array
{
@ -92,9 +87,4 @@ class JavaVersion extends FeatureProvider
}
});
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,7 +1,8 @@
<?php
namespace App\Extensions\Features;
namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface;
use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository;
use App\Repositories\Daemon\DaemonPowerRepository;
@ -9,17 +10,11 @@ use Exception;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
class MinecraftEula extends FeatureProvider
class MinecraftEulaSchema implements FeatureSchemaInterface
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
/** @return array<string> */
public function getListeners(): array
{
@ -63,9 +58,4 @@ class MinecraftEula extends FeatureProvider
}
});
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,19 +1,14 @@
<?php
namespace App\Extensions\Features;
namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface;
use Filament\Actions\Action;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
class PIDLimit extends FeatureProvider
class PIDLimitSchema implements FeatureSchemaInterface
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
/** @return array<string> */
public function getListeners(): array
{
@ -68,9 +63,4 @@ class PIDLimit extends FeatureProvider
->modalCancelActionLabel('Close')
->action(fn () => null);
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -1,19 +1,14 @@
<?php
namespace App\Extensions\Features;
namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface;
use Filament\Actions\Action;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
class SteamDiskSpace extends FeatureProvider
class SteamDiskSpaceSchema implements FeatureSchemaInterface
{
public function __construct(protected Application $app)
{
parent::__construct($app);
}
/** @return array<string> */
public function getListeners(): array
{
@ -56,9 +51,4 @@ class SteamDiskSpace extends FeatureProvider
->modalCancelActionLabel('Close')
->action(fn () => null);
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Extensions\OAuth;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Wizard\Step;
interface OAuthSchemaInterface
{
public function getId(): string;
public function getName(): string;
public function getConfigKey(): string;
/** @return ?class-string */
public function getSocialiteProvider(): ?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 getIcon(): ?string;
public function getHexColor(): ?string;
public function isEnabled(): bool;
}

View File

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

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,25 +1,19 @@
<?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';
}
public function getProviderClass(): string
public function getSocialiteProvider(): string
{
return Provider::class;
}
@ -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,39 @@
<?php
namespace App\Extensions\OAuth\Schemas;
final class CommonSchema extends OAuthSchema
{
public function __construct(
private readonly string $id,
private readonly ?string $name = null,
private readonly ?string $configName = null,
private readonly ?string $icon = null,
private readonly ?string $hexColor = null,
) {}
public function getId(): string
{
return $this->id;
}
public function getName(): string
{
return $this->name ?? parent::getName();
}
public function getConfigKey(): string
{
return $this->configName ?? parent::getConfigKey();
}
public function getIcon(): ?string
{
return $this->icon;
}
public function getHexColor(): ?string
{
return $this->hexColor;
}
}

View File

@ -1,29 +1,23 @@
<?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';
}
public function getProviderClass(): string
public function getSocialiteProvider(): string
{
return Provider::class;
}
@ -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,61 +1,22 @@
<?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>
*/
protected static array $providers = [];
/**
* @return self|static[]
*/
public static function get(?string $id = null): array|self
{
return $id ? static::$providers[$id] : static::$providers;
}
protected function __construct(protected Application $app)
{
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;
public function getProviderClass(): ?string
public function getSocialiteProvider(): ?string
{
return null;
}
/**
* @return array<string, string|string[]|bool|null>
*/
public function getServiceConfig(): array
{
$id = Str::upper($this->getId());
@ -112,6 +73,13 @@ abstract class OAuthProvider
return Str::title($this->getId());
}
public function getConfigKey(): string
{
$id = Str::upper($this->getId());
return "OAUTH_{$id}_ENABLED";
}
public function getIcon(): ?string
{
return null;

View File

@ -1,28 +1,22 @@
<?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';
}
public function getProviderClass(): string
public function getSocialiteProvider(): string
{
return Provider::class;
}
@ -73,9 +67,4 @@ final class SteamProvider extends OAuthProvider
{
return '#00adee';
}
public static function register(Application $app): self
{
return new self($app);
}
}

View File

@ -2,9 +2,9 @@
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\Avatar\AvatarService;
use App\Extensions\Captcha\CaptchaService;
use App\Extensions\OAuth\OAuthService;
use App\Models\Backup;
use App\Notifications\MailTested;
use App\Traits\EnvironmentWriterTrait;
@ -58,6 +58,12 @@ class Settings extends Page implements HasForms
protected static string $view = 'filament.pages.settings';
protected OAuthService $oauthService;
protected AvatarService $avatarService;
protected CaptchaService $captchaService;
/** @var array<mixed>|null */
public ?array $data = [];
@ -66,6 +72,13 @@ class Settings extends Page implements HasForms
$this->form->fill();
}
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService): void
{
$this->oauthService = $oauthService;
$this->avatarService = $avatarService;
$this->captchaService = $captchaService;
}
public static function canAccess(): bool
{
return auth()->user()->can('view settings');
@ -173,7 +186,7 @@ class Settings extends Page implements HasForms
Select::make('FILAMENT_AVATAR_PROVIDER')
->label(trans('admin/setting.general.avatar_provider'))
->native(false)
->options(collect(AvatarProvider::getAll())->mapWithKeys(fn ($provider) => [$provider->getId() => $provider->getName()]))
->options($this->avatarService->getMappings())
->selectablePlaceholder(false)
->default(env('FILAMENT_AVATAR_PROVIDER', config('panel.filament.avatar-provider'))),
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
@ -264,15 +277,14 @@ class Settings extends Page implements HasForms
{
$formFields = [];
$captchaProviders = CaptchaProvider::get();
foreach ($captchaProviders as $captchaProvider) {
$id = Str::upper($captchaProvider->getId());
$name = Str::title($captchaProvider->getId());
$captchaSchemas = $this->captchaService->getAll();
foreach ($captchaSchemas as $schema) {
$id = Str::upper($schema->getId());
$formFields[] = Section::make($name)
$formFields[] = Section::make($schema->getName())
->columns(5)
->icon($captchaProvider->getIcon() ?? 'tabler-shield')
->collapsed(fn () => !env("CAPTCHA_{$id}_ENABLED", false))
->icon($schema->getIcon() ?? 'tabler-shield')
->collapsed(fn () => !$schema->isEnabled())
->collapsible()
->schema([
Hidden::make("CAPTCHA_{$id}_ENABLED")
@ -283,21 +295,14 @@ class Settings extends Page implements HasForms
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
->label(trans('admin/setting.captcha.disable'))
->color('danger')
->action(function (Set $set) use ($id) {
$set("CAPTCHA_{$id}_ENABLED", false);
}),
->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", false)),
FormAction::make("enable_captcha_$id")
->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED"))
->label(trans('admin/setting.captcha.enable'))
->color('success')
->action(function (Set $set) use ($id, $captchaProviders) {
foreach ($captchaProviders as $captchaProvider) {
$loopId = Str::upper($captchaProvider->getId());
$set("CAPTCHA_{$loopId}_ENABLED", $loopId === $id);
}
}),
->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", true)),
])->columnSpan(1),
Group::make($captchaProvider->getSettingsForm())
Group::make($schema->getSettingsForm())
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
->columns(4)
->columnSpan(4),
@ -533,39 +538,37 @@ class Settings extends Page implements HasForms
{
$formFields = [];
$oauthProviders = OAuthProvider::get();
foreach ($oauthProviders as $oauthProvider) {
$id = Str::upper($oauthProvider->getId());
$name = Str::title($oauthProvider->getId());
$oauthSchemas = $this->oauthService->getAll();
foreach ($oauthSchemas as $schema) {
$id = Str::upper($schema->getId());
$key = $schema->getConfigKey();
$formFields[] = Section::make($name)
$formFields[] = Section::make($schema->getName())
->columns(5)
->icon($oauthProvider->getIcon() ?? 'tabler-brand-oauth')
->collapsed(fn () => !env("OAUTH_{$id}_ENABLED", false))
->icon($schema->getIcon() ?? 'tabler-brand-oauth')
->collapsed(fn () => !env($key, false))
->collapsible()
->schema([
Hidden::make("OAUTH_{$id}_ENABLED")
Hidden::make($key)
->live()
->default(env("OAUTH_{$id}_ENABLED")),
->default(env($key)),
Actions::make([
FormAction::make("disable_oauth_$id")
->visible(fn (Get $get) => $get("OAUTH_{$id}_ENABLED"))
->visible(fn (Get $get) => $get($key))
->label(trans('admin/setting.oauth.disable'))
->color('danger')
->action(function (Set $set) use ($id) {
$set("OAUTH_{$id}_ENABLED", false);
}),
->action(fn (Set $set) => $set($key, false)),
FormAction::make("enable_oauth_$id")
->visible(fn (Get $get) => !$get("OAUTH_{$id}_ENABLED"))
->visible(fn (Get $get) => !$get($key))
->label(trans('admin/setting.oauth.enable'))
->color('success')
->steps($oauthProvider->getSetupSteps())
->modalHeading(trans('admin/setting.oauth.enable') . ' ' . $name)
->steps($schema->getSetupSteps())
->modalHeading(trans('admin/setting.oauth.enable') . ' ' . $schema->getName())
->modalSubmitActionLabel(trans('admin/setting.oauth.enable'))
->modalCancelAction(false)
->action(function ($data, Set $set) use ($id) {
->action(function ($data, Set $set) use ($key) {
$data = array_merge([
"OAUTH_{$id}_ENABLED" => 'true',
$key => 'true',
], $data);
foreach ($data as $key => $value) {
@ -573,8 +576,8 @@ class Settings extends Page implements HasForms
}
}),
])->columnSpan(1),
Group::make($oauthProvider->getSettingsForm())
->visible(fn (Get $get) => $get("OAUTH_{$id}_ENABLED"))
Group::make($schema->getSettingsForm())
->visible(fn (Get $get) => $get($key))
->columns(4)
->columnSpan(4),
]);

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\OAuthService;
use App\Facades\Activity;
use App\Models\ActivityLog;
use App\Models\ApiKey;
@ -60,9 +60,12 @@ class EditProfile extends BaseEditProfile
private ToggleTwoFactorService $toggleTwoFactorService;
public function boot(ToggleTwoFactorService $toggleTwoFactorService): void
protected OAuthService $oauthService;
public function boot(ToggleTwoFactorService $toggleTwoFactorService, OAuthService $oauthService): void
{
$this->toggleTwoFactorService = $toggleTwoFactorService;
$this->oauthService = $oauthService;
}
public function getMaxWidth(): MaxWidth|string
@ -72,7 +75,7 @@ class EditProfile extends BaseEditProfile
protected function getForms(): array
{
$oauthProviders = collect(OAuthProvider::get())->filter(fn (OAuthProvider $provider) => $provider->isEnabled())->all();
$oauthSchemas = $this->oauthService->getEnabled();
return [
'form' => $this->form(
@ -155,21 +158,21 @@ class EditProfile extends BaseEditProfile
Tab::make(trans('profile.tabs.oauth'))
->icon('tabler-brand-oauth')
->visible(count($oauthProviders) > 0)
->schema(function () use ($oauthProviders) {
->visible(count($oauthSchemas) > 0)
->schema(function () use ($oauthSchemas) {
$actions = [];
foreach ($oauthProviders as $oauthProvider) {
foreach ($oauthSchemas as $schema) {
$id = $oauthProvider->getId();
$name = $oauthProvider->getName();
$id = $schema->getId();
$name = $schema->getName();
$unlink = array_key_exists($id, $this->getUser()->oauth ?? []);
$actions[] = Action::make("oauth_$id")
->label(($unlink ? trans('profile.unlink') : trans('profile.link')) . $name)
->icon($unlink ? 'tabler-unlink' : 'tabler-link')
->color(Color::hex($oauthProvider->getHexColor()))
->color(Color::hex($schema->getHexColor()))
->action(function (UserUpdateService $updateService) use ($id, $name, $unlink) {
if ($unlink) {
$oauth = auth()->user()->oauth;

View File

@ -3,8 +3,8 @@
namespace App\Filament\Pages\Auth;
use App\Events\Auth\ProvidedAuthenticationToken;
use App\Extensions\Captcha\Providers\CaptchaProvider;
use App\Extensions\OAuth\Providers\OAuthProvider;
use App\Extensions\Captcha\CaptchaService;
use App\Extensions\OAuth\OAuthService;
use App\Facades\Activity;
use App\Models\User;
use Filament\Facades\Filament;
@ -27,9 +27,15 @@ class Login extends BaseLogin
public bool $verifyTwoFactor = false;
public function boot(Google2FA $google2FA): void
protected OAuthService $oauthService;
protected CaptchaService $captchaService;
public function boot(Google2FA $google2FA, OAuthService $oauthService, CaptchaService $captchaService): void
{
$this->google2FA = $google2FA;
$this->oauthService = $oauthService;
$this->captchaService = $captchaService;
}
public function authenticate(): ?LoginResponse
@ -116,8 +122,8 @@ class Login extends BaseLogin
$this->getTwoFactorAuthenticationComponent(),
];
if ($captchaProvider = $this->getCaptchaComponent()) {
$schema = array_merge($schema, [$captchaProvider]);
if ($captchaComponent = $this->getCaptchaComponent()) {
$schema = array_merge($schema, [$captchaComponent]);
}
return [
@ -142,13 +148,7 @@ class Login extends BaseLogin
private function getCaptchaComponent(): ?Component
{
$captchaProvider = collect(CaptchaProvider::get())->filter(fn (CaptchaProvider $provider) => $provider->isEnabled())->first();
if (!$captchaProvider) {
return null;
}
return $captchaProvider->getComponent();
return $this->captchaService->getActiveSchema()?->getFormComponent();
}
protected function throwFailureValidationException(): never
@ -174,16 +174,16 @@ class Login extends BaseLogin
{
$actions = [];
$oauthProviders = collect(OAuthProvider::get())->filter(fn (OAuthProvider $provider) => $provider->isEnabled())->all();
$oauthSchemas = $this->oauthService->getEnabled();
foreach ($oauthProviders as $oauthProvider) {
foreach ($oauthSchemas as $schema) {
$id = $oauthProvider->getId();
$id = $schema->getId();
$actions[] = Action::make("oauth_$id")
->label($oauthProvider->getName())
->icon($oauthProvider->getIcon())
->color(Color::hex($oauthProvider->getHexColor()))
->label($schema->getName())
->icon($schema->getIcon())
->color(Color::hex($schema->getHexColor()))
->url(route('auth.oauth.redirect', ['driver' => $id], false));
}

View File

@ -5,7 +5,7 @@ namespace App\Filament\Server\Pages;
use App\Enums\ConsoleWidgetPosition;
use App\Enums\ContainerStatus;
use App\Exceptions\Http\Server\ServerStateConflictException;
use App\Extensions\Features\FeatureProvider;
use App\Extensions\Features\FeatureService;
use App\Filament\Server\Widgets\ServerConsole;
use App\Filament\Server\Widgets\ServerCpuChart;
use App\Filament\Server\Widgets\ServerMemoryChart;
@ -38,6 +38,8 @@ class Console extends Page
public ContainerStatus $status = ContainerStatus::Offline;
protected FeatureService $featureService;
public function mount(): void
{
/** @var Server $server */
@ -54,12 +56,12 @@ class Console extends Page
}
}
public function boot(): void
public function boot(FeatureService $featureService): void
{
$this->featureService = $featureService;
/** @var Server $server */
$server = Filament::getTenant();
/** @var FeatureProvider $feature */
foreach ($server->egg->features() as $feature) {
foreach ($featureService->getActiveSchemas($server->egg->features) as $feature) {
$this->cacheAction($feature->getAction());
}
}
@ -70,8 +72,8 @@ class Console extends Page
$data = json_decode($data);
$feature = data_get($data, 'key');
$feature = FeatureProvider::getProviders($feature);
if ($this->getMountedAction()) {
$feature = $this->featureService->get($feature);
if (!$feature || $this->getMountedAction()) {
return;
}
$this->mountAction($feature->getId());

View File

@ -2,23 +2,24 @@
namespace App\Http\Controllers\Auth;
use App\Extensions\OAuth\Providers\OAuthProvider;
use App\Extensions\OAuth\OAuthService;
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
{
public function __construct(
private readonly AuthManager $auth,
private readonly UserUpdateService $updateService
private readonly UserUpdateService $updateService,
private readonly OAuthService $oauthService
) {}
/**
@ -27,7 +28,7 @@ class OAuthController extends Controller
public function redirect(string $driver): RedirectResponse
{
// Driver is disabled - redirect to normal login
if (!OAuthProvider::get($driver)->isEnabled()) {
if (!$this->oauthService->get($driver)->isEnabled()) {
return redirect()->route('auth.login');
}
@ -40,7 +41,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 (!$this->oauthService->get($driver)?->isEnabled()) {
return redirect()->route('auth.login');
}

View File

@ -2,35 +2,35 @@
namespace App\Http\Middleware;
use App\Extensions\Captcha\CaptchaService;
use Closure;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Events\Auth\FailedCaptcha;
use App\Extensions\Captcha\Providers\CaptchaProvider;
use Symfony\Component\HttpKernel\Exception\HttpException;
readonly class VerifyCaptcha
{
public function __construct(private Application $app) {}
public function handle(Request $request, Closure $next): mixed
public function handle(Request $request, Closure $next, CaptchaService $captchaService): mixed
{
if ($this->app->isLocal()) {
return $next($request);
}
$captchaProviders = collect(CaptchaProvider::get())->filter(fn (CaptchaProvider $provider) => $provider->isEnabled())->all();
foreach ($captchaProviders as $captchaProvider) {
$response = $captchaProvider->validateResponse();
$schemas = $captchaService->getActiveSchemas();
foreach ($schemas as $schema) {
$response = $schema->validateResponse();
if ($response['success'] && $captchaProvider->verifyDomain($response['hostname'] ?? '', $request->url())) {
if ($response['success'] && $schema->verifyDomain($response['hostname'] ?? '', $request->url())) {
return $next($request);
}
event(new FailedCaptcha($request->ip(), $response['message'] ?? null));
throw new HttpException(Response::HTTP_BAD_REQUEST, "Failed to validate {$captchaProvider->getId()} captcha data.");
throw new HttpException(Response::HTTP_BAD_REQUEST, "Failed to validate {$schema->getId()} captcha data.");
}
// No captcha enabled

View File

@ -5,13 +5,12 @@ namespace App\Models;
use App\Contracts\Validatable;
use App\Exceptions\Service\Egg\HasChildrenException;
use App\Exceptions\Service\HasActiveServersException;
use App\Extensions\Features\FeatureProvider;
use App\Traits\HasValidation;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
/**
@ -161,12 +160,6 @@ class Egg extends Model implements Validatable
});
}
/** @return array<FeatureProvider> */
public function features(): array
{
return FeatureProvider::getProviders($this->features);
}
/**
* Returns the install script for the egg; if egg is copying from another
* it will return the copied script.

View File

@ -4,9 +4,8 @@ namespace App\Models;
use App\Contracts\Validatable;
use App\Exceptions\DisplayException;
use App\Extensions\Avatar\AvatarProvider;
use App\Extensions\Avatar\AvatarService;
use App\Rules\Username;
use App\Facades\Activity;
use App\Traits\HasValidation;
use DateTimeZone;
use Filament\Models\Contracts\FilamentUser;
@ -18,6 +17,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\In;
use Illuminate\Auth\Authenticatable;
@ -32,7 +32,6 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Support\Facades\Storage;
use ResourceBundle;
use Spatie\Permission\Traits\HasRoles;
@ -397,17 +396,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
public function getFilamentAvatarUrl(): ?string
{
if (config('panel.filament.uploadable-avatars')) {
$path = "avatars/$this->id.png";
if (Storage::disk('public')->exists($path)) {
return Storage::url($path);
}
}
$provider = AvatarProvider::getProvider(config('panel.filament.avatar-provider'));
return $provider?->get($this);
return App::call(fn (AvatarService $service) => $service->getAvatarUrl($this));
}
public function canTarget(Model $model): bool

View File

@ -10,21 +10,7 @@ use App\Checks\NodeVersionsCheck;
use App\Checks\PanelVersionCheck;
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\Services\Helpers\SoftwareVersionService;
use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
@ -105,35 +91,6 @@ 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');
// Additional OAuth providers from socialiteproviders.com
AuthentikProvider::register($app);
DiscordProvider::register($app);
SteamProvider::register($app);
// Default Captcha provider
TurnstileProvider::register($app);
// Default Avatar providers
GravatarProvider::register();
UiAvatarsProvider::register();
// Default Feature providers
GSLToken::register($app);
JavaVersion::register($app);
MinecraftEula::register($app);
PIDLimit::register($app);
SteamDiskSpace::register($app);
FilamentColor::register([
'danger' => Color::Red,
'gray' => Color::Zinc,

View File

@ -0,0 +1,24 @@
<?php
namespace App\Providers\Extensions;
use App\Extensions\Avatar\AvatarService;
use App\Extensions\Avatar\Schemas\GravatarSchema;
use App\Extensions\Avatar\Schemas\UiAvatarsSchema;
use Illuminate\Support\ServiceProvider;
class AvatarServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(AvatarService::class, function ($app) {
$service = new AvatarService(config('panel.filament.uploadable-avatars', false), config('panel.filament.avatar-provider', 'gravatar'));
// Default Avatar providers
$service->register(new GravatarSchema());
$service->register(new UiAvatarsSchema());
return $service;
});
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Providers\Extensions;
use App\Extensions\Captcha\CaptchaService;
use App\Extensions\Captcha\Schemas\Turnstile\TurnstileSchema;
use Illuminate\Support\ServiceProvider;
class CaptchaServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(CaptchaService::class, function ($app) {
$service = new CaptchaService();
// Default Captcha providers
$service->register(new TurnstileSchema());
return $service;
});
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Providers\Extensions;
use App\Extensions\Features\FeatureService;
use App\Extensions\Features\Schemas\GSLTokenSchema;
use App\Extensions\Features\Schemas\JavaVersionSchema;
use App\Extensions\Features\Schemas\MinecraftEulaSchema;
use App\Extensions\Features\Schemas\PIDLimitSchema;
use App\Extensions\Features\Schemas\SteamDiskSpaceSchema;
use Illuminate\Support\ServiceProvider;
class FeatureServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(FeatureService::class, function ($app) {
$provider = new FeatureService();
// Default Feature providers
$provider->register(new GSLTokenSchema());
$provider->register(new JavaVersionSchema());
$provider->register(new MinecraftEulaSchema());
$provider->register(new PIDLimitSchema());
$provider->register(new SteamDiskSpaceSchema());
return $provider;
});
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Providers\Extensions;
use App\Extensions\OAuth\OAuthService;
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 Illuminate\Support\ServiceProvider;
class OAuthServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(OAuthService::class, function ($app) {
$service = new OAuthService();
// Default OAuth providers included with Socialite
$service->register(new CommonSchema('facebook', icon: 'tabler-brand-facebook-f', hexColor: '#1877f2'));
$service->register(new CommonSchema('x', icon: 'tabler-brand-x-f', hexColor: '#1da1f2'));
$service->register(new CommonSchema('linkedin', icon: 'tabler-brand-linkedin-f', hexColor: '#0a66c2'));
$service->register(new CommonSchema('google', icon: 'tabler-brand-google-f', hexColor: '#4285f4'));
$service->register(new GithubSchema());
$service->register(new GitlabSchema());
$service->register(new CommonSchema('bitbucket', icon: 'tabler-brand-bitbucket-f', hexColor: '#205081'));
$service->register(new CommonSchema('slack', icon: 'tabler-brand-slack', hexColor: '#6ecadc'));
// Additional OAuth providers from socialiteproviders.com
$service->register(new AuthentikSchema());
$service->register(new DiscordSchema());
$service->register(new SteamSchema());
return $service;
});
}
}

View File

@ -2,12 +2,14 @@
namespace App\Services\Servers;
use App\Extensions\Features\FeatureService;
use App\Models\Egg;
use App\Models\Mount;
use App\Models\Server;
class ServerConfigurationStructureService
{
public function __construct(private EnvironmentService $environment) {}
public function __construct(private EnvironmentService $environment, private FeatureService $featureService) {}
/**
* Return a configuration array for a specific server when passed a server model.
@ -58,7 +60,7 @@ class ServerConfigurationStructureService
* default: array{ip: string, port: int},
* mappings: array<string, array<int>>,
* },
* egg: array{id: string, file_denylist: string[]},
* egg: array{id: string, file_denylist: string[], features: string[][]},
* labels?: string[],
* mounts: array{source: string, target: string, read_only: bool},
* }
@ -101,9 +103,7 @@ class ServerConfigurationStructureService
'egg' => [
'id' => $server->egg->uuid,
'file_denylist' => $server->egg->inherit_file_denylist,
'features' => collect($server->egg->features())->mapWithKeys(fn ($feature) => [
$feature->getId() => $feature->getListeners(),
])->all(),
'features' => $this->featureService->getMappings($server->egg->features),
],
];

View File

@ -8,6 +8,10 @@ return [
App\Providers\Filament\AdminPanelProvider::class,
App\Providers\Filament\AppPanelProvider::class,
App\Providers\Filament\ServerPanelProvider::class,
App\Providers\Extensions\AvatarServiceProvider::class,
App\Providers\Extensions\CaptchaServiceProvider::class,
App\Providers\Extensions\FeatureServiceProvider::class,
App\Providers\Extensions\OAuthServiceProvider::class,
App\Providers\RouteServiceProvider::class,
SocialiteProviders\Manager\ServiceProvider::class,
];

View File

@ -20,6 +20,6 @@ parameters:
identifier: larastan.noEnvCallsOutsideOfConfig
paths:
- app/Console/Commands/Environment/*.php
- app/Extensions/Captcha/Providers/*.php
- app/Extensions/OAuth/Providers/*.php
- app/Extensions/Captcha/Schemas/*.php
- app/Extensions/OAuth/Schemas/*.php
- app/Filament/Admin/Pages/Settings.php